Making tab-complete.c easier to maintain

Started by Thomas Munroover 10 years ago86 messages
#1Thomas Munro
thomas.munro@enterprisedb.com
1 attachment(s)

Hi hackers,

After spending some time in tab-complete.c responding to a bug report, I
had very strong urge to find a way to make it a bit easier to maintain. Do
you think it would be an improvement if we changed all the code that looks
a bit like this:

/* Complete CREATE TRIGGER <name> BEFORE|AFTER with an event */
else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
(pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
pg_strcasecmp(prev_wd, "AFTER") == 0))
{
static const char *const list_CREATETRIGGER_EVENTS[] =
{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};

COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
}

... into code that looks a bit like this?

/* Complete CREATE TRIGGER <name> BEFORE|AFTER with an event */
else if (MATCHES4("CREATE", "TRIGGER", "<name>", "BEFORE|AFTER"))
COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");

See attached a proof-of-concept patch. It reduces the size of
tab-complete.c by a bit over a thousand lines. I realise that changing so
many lines just to refactor code may may be a difficult sell! But I think
this would make it easier to improve the tab completion code in future, and
although it's large it's a superficial and mechanical change.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology.patchapplication/octet-stream; name=tab-complete-macrology.patchDownload
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 816deda..6108ce3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -228,6 +228,62 @@ do { \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -872,6 +928,54 @@ initialize_readline(void)
 	 */
 }
 
+/*
+ * Check if 'word' matches any of the '|'-separated strings in 'pattern', using
+ * case-insensitive comparison, or the pattern begins with '<' indicating a
+ * wildcard that matches any word.
+ */
+static bool
+word_matches(const char *pattern, const char *word)
+{
+	const char *c = word;
+
+	if (pattern[0] == '<')
+		return true;
+
+	while (*pattern != '\0')
+	{
+		if (*c == '\0' && *pattern == '|')
+			return true;
+		else if (*c == '\0' || tolower(*pattern) != tolower(*c))
+		{
+			/* Skip to next word in pattern and rewind word. */
+			while (*pattern != '\0' && *pattern != '|')
+				++pattern;
+			if (*pattern == '\0')
+				return false;
+			else
+				++pattern;
+			c = word;
+		}
+		else
+		{
+			++pattern;
+			++c;
+		}
+	}
+
+	return *c == '\0';
+}
+
+/*
+ * Check if the final character of 's' is 'c'.
+ */
+static bool
+ends_with(const char *s, char c)
+{
+	size_t length = strlen(s);
+
+	return length > 0 && s[length - 1] == c;
+}
 
 /*
  * The completion function.
@@ -898,6 +1002,58 @@ psql_completion(const char *text, int start, int end)
 #define prev5_wd  (previous_words[4])
 #define prev6_wd  (previous_words[5])
 
+#define MATCHES1(p1) \
+(word_matches(p1, prev_wd)) \
+
+#define MATCHES2(p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd))
+
+#define MATCHES3(p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd))
+
+#define MATCHES4(p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd))
+
+#define MATCHES5(p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd))
+
+#define MATCHES6(p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd))
+
+#define MATCHES7(p7, p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd) && \
+ word_matches(p7, prev7_wd))
+
+#define MATCHES8(p8, p7, p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd) && \
+ word_matches(p7, prev7_wd) && \
+ word_matches(p8, prev8_wd))
+
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
@@ -931,6 +1087,22 @@ psql_completion(const char *text, int start, int end)
 	rl_completion_append_character = ' ';
 #endif
 
+	/* TODO:TM -- begin temporary, not part of the patch! */
+	Assert(word_matches("<foo>", ""));
+	Assert(word_matches("<foo>", "walrus"));
+	Assert(word_matches("", ""));
+	Assert(word_matches("a", "A"));
+	Assert(word_matches("ab", "Ab"));
+	Assert(!word_matches("ab", "b"));
+	Assert(word_matches("a|b", "b"));
+	Assert(word_matches("a|b|c", "b"));
+	Assert(!word_matches("a|b|c", "d"));
+	Assert(!word_matches("a|b|c", "bb"));
+	Assert(word_matches("|foo", ""));
+	Assert(word_matches("foo|", ""));
+	Assert(!word_matches("foo", ""));
+	/* TODO:TM -- end temorary */
+
 	/* Clear a few things. */
 	completion_charp = NULL;
 	completion_charpp = NULL;
@@ -960,36 +1132,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (MATCHES1(""))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (MATCHES1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES2("", "DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (MATCHES2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (MATCHES3("TABLE", "<sth>", "ALTER"))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1002,9 +1169,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCHES4("ALL", "IN", "TABLESPACE", "<name>"))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1017,20 +1182,14 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (MATCHES3("ALTER", "AGGREGATE|FUNCTION", "<name>"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (MATCHES4("ALTER", "AGGREGATE|FUNCTION", "<name>", "<...>"))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
+		if (ends_with(prev_wd, ')'))
 		{
 			static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1042,38 +1201,19 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (MATCHES3("ALTER", "SCHEMA", "<name>"))
+		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (MATCHES3("ALTER", "COLLATION", "<name>"))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (MATCHES3("ALTER", "CONVERSION", "<name>"))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCHES3("ALTER", "DATABASE", "<name>"))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1083,72 +1223,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (MATCHES3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
-		static const char *const list_ALTER_EVENT_TRIGGER[] =
-		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER);
-	}
+	else if (MATCHES4("ALTER", "EVENT", "TRIGGER", "<name>"))
+		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
-	{
-		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
-		{"REPLICA", "ALWAYS", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER_ENABLE);
-	}
+	else if (MATCHES5("ALTER", "EVENT", "TRIGGER", "<name>", "ENABLE"))
+		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
-	{
-		static const char *const list_ALTEREXTENSION[] =
-		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTEREXTENSION);
-	}
+	else if (MATCHES3("ALTER", "EXTENSION", "NAME"))
+		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_ALTER_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
-	}
+	else if (MATCHES2("ALTER", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		static const char *const list_ALTER_FDW[] =
-		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_FDW);
-	}
+	else if (MATCHES5("ALTER", "FOREIGN", "DATA", "WRAPPER", "<name>"))
+		COMPLETE_WITH_LIST4("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCHES4("ALTER", "FOREIGN", "TABLE", "<name>"))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1159,84 +1258,37 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
-	{
+	else if (MATCHES2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 	/* ALTER INDEX <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
-	{
-		static const char *const list_ALTERINDEX[] =
-		{"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERINDEX);
-	}
+	else if (MATCHES3("ALTER", "INDEX", "<name>"))
+		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_ALTERINDEXSET[] =
-		{"(", "TABLESPACE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
-	}
+	else if (MATCHES4("ALTER", "INDEX", "<name>", "SET"))
+		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCHES4("ALTER", "INDEX", "<name>", "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_INDEXOPTIONS[] =
-		{"fillfactor", "fastupdate", "gin_pending_list_limit", NULL};
-
-		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
-	}
+	else if (MATCHES5("ALTER", "INDEX", "<name>", "SET|RESET", "("))
+		COMPLETE_WITH_LIST3("fillfactor", "fastupdate", "gin_pending_list_limit");
 
 	/* ALTER LANGUAGE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
-	{
-		static const char *const list_ALTERLANGUAGE[] =
-		{"OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
-	}
+	else if (MATCHES3("ALTER", "LANGUAGE", "<name>"))
+		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OBJECT") == 0)
-	{
-		static const char *const list_ALTERLARGEOBJECT[] =
-		{"OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
-	}
+	else if (MATCHES4("ALTER", "LARGE", "OBJECT", "<oid>"))
+		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (MATCHES3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/* ALTER USER,ROLE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
+	else if (MATCHES3("ALTER", "USER|ROLE", "<name>") && !MATCHES2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1250,10 +1302,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "USER") == 0 ||
-			   pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCHES4("ALTER", "USER|ROLE", "<name>", "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1268,78 +1317,32 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCHES4("ALTER", "USER|ROLE", "<name>", "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
-		{"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
-	}
+	else if (MATCHES3("ALTER", "DEFAULT", "PRIVILEGES"))
+		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
-		{"ROLE", "USER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
-	}
+	else if (MATCHES4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-			  pg_strcasecmp(prev3_wd, "IN") == 0))
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
-		{"GRANT", "REVOKE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
-	}
+	else if (MATCHES5("DEFAULT", "PRIVILEGES", "FOR", "ROLE", "<name>") ||
+			 MATCHES5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", "<name>"))
+		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
-	{
-		static const char *const list_ALTERDOMAIN[] =
-		{"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
-	}
+	else if (MATCHES3("ALTER", "DOMAIN", "<name>"))
+		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_ALTERDOMAIN2[] =
-		{"CONSTRAINT", "DEFAULT", "NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
-	}
+	else if (MATCHES4("ALTER", "DOMAIN", "<sth>", "DROP"))
+		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+	else if (MATCHES5("ALTER", "DOMAIN", "<sth>", "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCHES4("ALTER", "DOMAIN", "<name>", "RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"CONSTRAINT", "TO", NULL};
@@ -1347,24 +1350,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+	else if (MATCHES3("RENAME", "CONSTRAINT", "<sth>"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_ALTERDOMAIN3[] =
-		{"DEFAULT", "NOT NULL", "SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
-	}
+	else if (MATCHES4("ALTER", "DOMAIN", "<sth>", "SET"))
+		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
+	else if (MATCHES3("ALTER", "SEQUENCE", "<name>"))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1373,123 +1366,63 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
-	{
-		static const char *const list_ALTERSEQUENCE2[] =
-		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
-	}
+	else if (MATCHES4("ALTER", "SEQUEMCE", "<name>", "NO"))
+		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
-	{
-		static const char *const list_ALTER_SERVER[] =
-		{"VERSION", "OPTIONS", "OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_SERVER);
-	}
+	else if (MATCHES3("ALTER", "SERVER", "<name>"))
+		COMPLETE_WITH_LIST3("VERSION", "OPTIONS", "OWNER TO");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "SYSTEM") == 0)
-	{
-		static const char *const list_ALTERSYSTEM[] =
-		{"SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
-	}
+	else if (MATCHES2("ALTER", "SYSTEM"))
+		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCHES4("ALTER", "SYSTEM", "SET|RESET", "<name>"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
-	{
-		static const char *const list_ALTERVIEW[] =
-		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERVIEW);
-	}
+	else if (MATCHES3("ALTER", "VIEW", "<name>"))
+		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
-	{
-		static const char *const list_ALTERMATVIEW[] =
-		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
-	}
+	else if (MATCHES4("ALTER", "MATERIALIZED", "VIEW", "<name>"))
+		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCHES3("ALTER", "POLICY", "<name>"))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("ALTER", "POLICY", "<name>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_ALTERPOLICY[] =
-		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERPOLICY);
-	}
+	else if (MATCHES5("ALTER", "POLICY", "<name>", "ON", "<table>"))
+		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING", "WITH CHECK");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCHES6("ALTER", "POLICY", "<name>", "ON", "<table>", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCHES6("ALTER", "POLICY", "<name>", "ON", "<table>", "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+	else if (MATCHES6("POLICY", "<name>", "ON", "<table>", "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCHES3("ALTER", "RULE", "<name>"))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("ALTER", "RULE", "<name>", "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0)
+	else if (MATCHES5("ALTER", "RULE", "<name>", "ON", "<name>"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCHES3("ALTER", "TRIGGER", "<name>"))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
+	else if (MATCHES4("ALTER", "TRIGGER", "<name>", "<sth>"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1498,22 +1431,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("ALTER", "TRIGGER", "<sth>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCHES5("ALTER", "TRIGGER", "<name>", "ON", "<name>"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCHES3("ALTER", "TABLE", "<name>"))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1523,279 +1451,144 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (MATCHES4("ALTER", "TABLE", "<name>", "ENABLE"))
 	{
 		static const char *const list_ALTERENABLE[] =
 		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
-			  pg_strcasecmp(prev_wd, "ALWAYS") == 0))
+	else if (MATCHES4("TABLE", "<name>", "ENABLE", "REPLICA|ALWAYS"))
 	{
 		static const char *const list_ALTERENABLE2[] =
 		{"RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE2);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (MATCHES6("ALTER", "TABLE", "<name>", "ENABLE", "<sth>", "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (MATCHES6("ALTER", "TABLE", "<name>", "ENABLE", "<sth>", "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCHES4("ALTER", "TABLE", "<name>", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "NO") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx DISABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
-	{
-		static const char *const list_ALTERDISABLE[] =
-		{"ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDISABLE);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (MATCHES4("ALTER", "TABLE", "<name>", "DISABLE"))
+		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
-	{
-		static const char *const list_DISABLERLS[] =
-		{"CASCADE", NULL};
-
-		COMPLETE_WITH_LIST(list_DISABLERLS);
-	}
+	else if (MATCHES4("DISABLE", "ROW", "LEVEL", "SECURITY"))
+		COMPLETE_WITH_CONST("CASCADE");
 
 	/* ALTER TABLE xxx ALTER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALTER") == 0)
+	else if (MATCHES4("ALTER", "TABLE", "<name>", "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCHES4("ALTER", "TABLE", "<name>", "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
+	else if (MATCHES4("TABLE", "<name>", "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "RENAME", "<name>") &&
+			 !MATCHES1("CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCHES6("ALTER", "TABLE", "<name>", "RENAME", "COLUMN|CONSTRAINT", "<name>") &&
+			 !MATCHES1("TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_TABLEDROP[] =
-		{"COLUMN", "CONSTRAINT", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLEDROP);
-	}
+	else if (MATCHES3("TABLE", "<name>", "DROP"))
+		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+	else if (MATCHES5("ALTER", "TABLE", "<name>", "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
-	{
-		static const char *const list_COLUMNALTER[] =
-		{"TYPE", "SET", "RESET", "DROP", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNALTER);
-	}
+	else if (MATCHES3("ALTER", "COLUMN", "<name>") ||
+			 MATCHES4("TABLE", "<name>", "ALTER", "<name>"))
+		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_COLUMNSET[] =
-		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNSET);
-	}
+	else if (MATCHES4("ALTER", "COLUMN", "<name>", "SET") ||
+			 MATCHES5("TABLE", "<name>", "ALTER", "<name>", "SET"))
+		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_COLUMNOPTIONS[] =
-		{"n_distinct", "n_distinct_inherited", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
-	}
+	else if (MATCHES5("ALTER", "COLUMN", "<name>", "SET", "(") ||
+			 MATCHES4("ALTER", "<name>", "SET", "("))
+		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
-	{
-		static const char *const list_COLUMNSTORAGE[] =
-		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
-	}
+	else if (MATCHES5("ALTER", "COLUMN", "<name>", "SET", "STORAGE") ||
+			 MATCHES4("ALTER", "<name>", "SET", "STORAGE"))
+		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_COLUMNDROP[] =
-		{"DEFAULT", "NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNDROP);
-	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
+	else if (MATCHES4("ALTER", "COLUMN", "<name>", "DROP") ||
+			 MATCHES5("TABLE", "<name>", "ALTER", "<name>", "DROP"))
+		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
+	else if (MATCHES3("TABLE", "<name>", "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("TABLE", "<name>", "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_TABLESET[] =
-		{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESET);
-	}
+	else if (MATCHES3("TABLE", "<name>", "SET"))
+		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT");
 	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+	else if (MATCHES4("TABLE", "<name>", "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITHOUT") == 0)
-	{
-		static const char *const list_TABLESET2[] =
-		{"CLUSTER", "OIDS", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESET2);
-	}
+	else if (MATCHES4("TABLE", "<name>", "SET", "WITHOUT"))
+		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCHES3("TABLE", "<name>", "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCHES4("TBALE", "<name>", "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1832,246 +1625,109 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
+	else if (MATCHES4("REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
+	else if (MATCHES5("TABLE", "<name>", "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
-	{
-		static const char *const list_REPLICAID[] =
-		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_REPLICAID);
-	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
-	{
+	else if (MATCHES4("TABLE", "<name>", "REPLICA", "IDENTITY"))
+		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
+	else if (MATCHES3("TABLE", "<name>", "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
-	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-	{
-		static const char *const list_ALTERTSPC[] =
-		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTSPC);
-	}
+	else if (MATCHES3("ALTER", "TABLESPACE", "<name>"))
+		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCHES4("ALTER", "TABLESPACE", "<name>", "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_TABLESPACEOPTIONS[] =
-		{"seq_page_cost", "random_page_cost", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
-	}
+	else if (MATCHES5("ALTER", "TABLESPACE", "<name>", "SET|RESET", "("))
+		COMPLETE_WITH_LIST2("seq_page_cost", "random_page_cost");
 
 	/* ALTER TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "PARSER") == 0))
-	{
-		static const char *const list_ALTERTEXTSEARCH2[] =
-		{"RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
-	}
-
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH3[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
-	}
-
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH4[] =
-		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
-	}
+	else if (MATCHES3("ALTER", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (MATCHES5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", "<name>"))
+		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
+	else if (MATCHES5("ALTER", "TEXT", "SEARCH", "DICTIONARY", "<name>"))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (MATCHES5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", "<name>"))
+		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR",
+							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TYPE") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
-		"OWNER TO", "RENAME", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (MATCHES3("ALTER", "TYPE", "<name>"))
+		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
+							"DROP ATTRIBUTE", "OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ADD") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ATTRIBUTE", "VALUE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (MATCHES4("ALTER", "TYPE", "<name>", "ADD"))
+		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ATTRIBUTE", "TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (MATCHES4("ALTER", "TYPE", "<name>", "RENAME"))
+		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+	else if (MATCHES5("TYPE", "<name>", "RENAME", "ATTRIBUTE", "<name>"))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
 	 * attributes
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
+	else if (MATCHES4("TYPE", "<name>", "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
-	{
+	else if (MATCHES3("ALTER", "ATTRIBUTE", "<name>"))
 		COMPLETE_WITH_CONST("TYPE");
-	}
 	/* complete ALTER GROUP <foo> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "GROUP") == 0)
-	{
-		static const char *const list_ALTERGROUP[] =
-		{"ADD USER", "DROP USER", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGROUP);
-	}
+	else if (MATCHES3("ALTER", "GROUP", "<name>"))
+		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev_wd, "DROP") == 0))
+	else if (MATCHES4("ALTER", "GROUP", "<name>", "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
-	else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev_wd, "USER") == 0)
+	else if (MATCHES4("GROUP", "<name>", "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
-			 pg_strcasecmp(prev_wd, "END") == 0 ||
-			 pg_strcasecmp(prev_wd, "ABORT") == 0)
-	{
-		static const char *const list_TRANS[] =
-		{"WORK", "TRANSACTION", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS);
-	}
+	else if (MATCHES1("BEGIN|END|ABORT"))
+		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
-	{
-		static const char *const list_COMMIT[] =
-		{"WORK", "TRANSACTION", "PREPARED", NULL};
-
-		COMPLETE_WITH_LIST(list_COMMIT);
-	}
+	else if (MATCHES1("COMMIT"))
+		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (pg_strcasecmp(prev_wd, "RELEASE") == 0)
+	else if (MATCHES1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0)
-	{
-		static const char *const list_TRANS[] =
-		{"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS);
-	}
+	else if (MATCHES1("ROLLBACK"))
+		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 
 	/*
 	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
+	else if (MATCHES1("CLUSTER") && !MATCHES2("WITHOUT", "CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
 
 	/*
 	 * If the previous words are CLUSTER VERBOSE produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+	else if (MATCHES2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") != 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-	{
+	else if (MATCHES2("CLUSTER", "<sth>") && !MATCHES1("VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
-	}
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
-	{
+	else if (MATCHES3("CLUSTER", "VERBOSE", "<sth>"))
 		COMPLETE_WITH_CONST("USING");
-	}
 
 	/*
 	 * If we have CLUSTER <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCHES3("CLUSTER", "<sth>", "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
@@ -2080,19 +1736,16 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCHES4("CLUSTER", "VERBOSE", "<sth>", "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 
 /* COMMENT */
-	else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
+	else if (MATCHES1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -2104,66 +1757,27 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_TRANS2[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS2);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_TRANS2[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS2);
-	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
+	else if (MATCHES3("COMMENT", "ON", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (MATCHES4("COMMENT", "ON", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (MATCHES3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
-	{
+	else if (MATCHES4("COMMENT", "ON", "CONSTRAINT", "<name>"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES5("COMMENT", "ON", "CONSTRAINT", "<name>", "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (MATCHES4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (MATCHES4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
-	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev4_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev5_wd, "ON") == 0)) &&
-			 pg_strcasecmp(prev_wd, "IS") != 0)
+	else if ((MATCHES4("COMMENT", "ON", "<sth>", "<sth>") ||
+			  MATCHES5("COMMENT", "ON", "<sth>", "<sth>", "<sth>") ||
+			  MATCHES6("COMMENT", "ON", "<sth>", "<sth>", "<sth>", "<sth>")) &&
+			 !MATCHES1("IS"))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2172,59 +1786,28 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (pg_strcasecmp(prev_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev_wd, "\\copy") == 0 ||
-			 (pg_strcasecmp(prev2_wd, "COPY") == 0 &&
-			  pg_strcasecmp(prev_wd, "BINARY") == 0))
+	else if (MATCHES1("COPY|\\copy") || MATCHES2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
-	else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
-			 pg_strcasecmp(prev2_wd, "BINARY") == 0)
-	{
-		static const char *const list_FROMTO[] =
-		{"FROM", "TO", NULL};
-
-		COMPLETE_WITH_LIST(list_FROMTO);
-	}
+	else if (MATCHES2("COPY|\\copy|BINARY", "<sth>"))
+		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
-	else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev_wd, "TO") == 0))
+	else if (MATCHES3("COPY|\\copy|BINARY", "<sth>", "FROM|TO"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename */
-	else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev4_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev2_wd, "TO") == 0))
-	{
-		static const char *const list_COPY[] =
-		{"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
-
-		COMPLETE_WITH_LIST(list_COPY);
-	}
+	else if (MATCHES4("COPY|\\copy|BINARY", "<sth>", "FROM|TO", "<filename>"))
+		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING");
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-	else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev3_wd, "TO") == 0))
-	{
-		static const char *const list_CSV[] =
-		{"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_CSV);
-	}
+	else if (MATCHES3("FROM|TO", "<filename>", "CSV"))
+		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL");
 
 	/* CREATE DATABASE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCHES3("CREATE", "DATABASE", "<name>"))
 	{
 		static const char *const list_DATABASE[] =
 		{"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS_TEMPLATE",
@@ -2234,403 +1817,194 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DATABASE);
 	}
 
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+	else if (MATCHES4("CREATE", "DATABASE", "<name>", "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+	else if (MATCHES2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (MATCHES3("CREATE", "EXTENSION", "<name>"))
 		COMPLETE_WITH_CONST("WITH SCHEMA");
 
 	/* CREATE FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_CREATE_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
-	}
+	else if (MATCHES2("CREATE", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
-		{"HANDLER", "VALIDATOR", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_FOREIGN_DATA_WRAPPER);
-	}
+	else if (MATCHES5("CREATE", "FOREIGN", "DATA", "WRAPPER", "<name>"))
+		COMPLETE_WITH_LIST2("HANDLER", "VALIDATOR");
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
+	else if (MATCHES2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
-	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+	else if (MATCHES2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX [<name>] ON with a list of tables  */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES3("INDEX", "<name>", "ON") ||
+			 MATCHES2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCHES3("INDEX", "<name>", "CONCURRENTLY") ||
+			 MATCHES2("INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
-	{
-		static const char *const list_CREATE_INDEX[] =
-		{"CONCURRENTLY", "ON", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_INDEX);
-	}
+	else if (MATCHES3("CREATE|UNIQUE", "INDEX", "<sth>"))
+		COMPLETE_WITH_LIST2("CONCURRENTLY", "ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_CREATE_INDEX2[] =
-		{"(", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
-	}
-	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCHES4("INDEX", "<name>", "ON", "<name>") ||
+			 MATCHES3("INDEX|CONCURRENTLY", "ON", "<name>"))
+		COMPLETE_WITH_LIST2("(", "USING");
+	else if (MATCHES5("INDEX", "<name>", "ON", "<name>", "(") ||
+			 MATCHES4("INDEX|CONCURRENTLY", "ON", "<name>", "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCHES5("ON", "<name>", "USING", "<name>", "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCHES6("INDEX", "<sth>", "<sth>", "ON", "<name>", "USING") ||
+			 MATCHES5("INDEX", "<sth>", "ON", "<name>", "USING") ||
+			 MATCHES4("INDEX", "ON", "<name>", "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
-			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0)
+	else if (MATCHES4("ON", "<sth>", "USING", "<sth>") &&
+			 !MATCHES6("POLICY", "<sth>", "<sth>", "<sth>", "<sth>", "<sth>") &&
+			 !MATCHES4("FOR", "<sth>", "<sth>", "<sth>"))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCHES3("CREATE", "POLICY", "<name>"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("CREATE", "POLICY", "<name>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"FOR", "TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (MATCHES5("CREATE", "POLICY", "<name>", "ON", "<table>"))
+		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
-	{
-		static const char *const list_POLICYCMDS[] =
-		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYCMDS);
-	}
+	else if (MATCHES6("CREATE", "POLICY", "<name>", "ON", "<table>", "FOR"))
+		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 pg_strcasecmp(prev_wd, "INSERT") == 0)
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (MATCHES6("POLICY", "<name>", "ON", "<table>", "FOR", "INSERT"))
+		COMPLETE_WITH_LIST2("TO", "WITH CHECK");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
 	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
-			  pg_strcasecmp(prev_wd, "DELETE") == 0))
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (MATCHES6("POLICY", "<name>", "ON", "<table>", "FOR", "SELECT|DELETE"))
+		COMPLETE_WITH_LIST2("TO", "USING");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK"
 	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
 	 * CHECK"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
-			  pg_strcasecmp(prev_wd, "UPDATE") == 0))
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (MATCHES6("POLICY", "<name>", "ON", "<table>", "FOR", "ALL|UPDATE"))
+		COMPLETE_WITH_LIST3("TO", "USING", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCHES6("CREATE", "POLICY", "<name>", "ON", "<table>", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCHES6("CREATE", "POLICY", "<name>", "ON", "<table>", "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCHES3("CREATE", "RULE", "<sth>"))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS with "ON" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCHES4("CREATE", "RULE", "<sth>", "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
-	else if (pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		static const char *const rule_events[] =
-		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(rule_events);
-	}
+	else if (MATCHES4("RULE", "<sth>", "AS", "ON"))
+		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
-	else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
-			  pg_toupper((unsigned char) prev_wd[5]) == 'T'))
+	else if (MATCHES3("AS", "ON", "SELECT|UPDATE|DELETE|INSERT"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCHES4("AS", "ON", "SELECT|UPDATE|DELETE|INSERT", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0))
-	{
-		static const char *const list_CREATESEQUENCE[] =
-		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
-		 "CYCLE", "OWNED BY", "START WITH", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
-	}
+	else if (MATCHES3("CREATE", "SEQUENCE", "<name>") ||
+			 MATCHES4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", "<name>"))
+		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
+							"CYCLE", "OWNED BY", "START WITH");
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev4_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev4_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0)) &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
-	{
-		static const char *const list_CREATESEQUENCE2[] =
-		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATESEQUENCE2);
-	}
+	else if (MATCHES4("CREATE", "SEQUENCE", "<name>", "NO") ||
+			 MATCHES5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", "<name>", "NO"))
+		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
-	{
-		static const char *const list_CREATE_SERVER[] =
-		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_SERVER);
-	}
+	else if (MATCHES3("CREATE", "SERVER", "<name>"))
+		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
-			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
-	{
-		static const char *const list_TEMP[] =
-		{"SEQUENCE", "TABLE", "VIEW", NULL};
-
-		COMPLETE_WITH_LIST(list_TEMP);
-	}
+	else if (MATCHES2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
-	{
-		static const char *const list_UNLOGGED[] =
-		{"TABLE", "MATERIALIZED VIEW", NULL};
-
-		COMPLETE_WITH_LIST(list_UNLOGGED);
-	}
+	else if (MATCHES2("CREATE", "UNLOGGED"))
+		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-	{
-		static const char *const list_CREATETABLESPACE[] =
-		{"OWNER", "LOCATION", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
-	}
+	else if (MATCHES3("CREATE", "TABLESPACE", "<name>"))
+		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNER") == 0)
-	{
+	else if (MATCHES5("CREATE", "TABLESPACE", "<name>", "OWNER", "<name>"))
 		COMPLETE_WITH_CONST("LOCATION");
-	}
 
 /* CREATE TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_CREATETEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
-	}
-	else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (MATCHES3("CREATE", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (MATCHES4("TEXT", "SEARCH", "CONFIGURATION", "<sth>"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
-		static const char *const list_CREATETRIGGER[] =
-		{"BEFORE", "AFTER", "INSTEAD OF", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER);
-	}
+	else if (MATCHES3("CREATE", "TRIGGER", "<name>"))
+		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev_wd, "AFTER") == 0))
-	{
-		static const char *const list_CREATETRIGGER_EVENTS[] =
-		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
-	}
+	else if (MATCHES4("CREATE", "TRIGGER", "<name>", "BEFORE|AFTER"))
+		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev_wd, "OF") == 0)
-	{
-		static const char *const list_CREATETRIGGER_EVENTS[] =
-		{"INSERT", "DELETE", "UPDATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
-	}
+	else if (MATCHES5("CREATE", "TRIGGER", "<name>", "INSTEAD", "OF"))
+		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0)) ||
-			 (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			  pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
-			  pg_strcasecmp(prev2_wd, "OF") == 0))
-	{
-		static const char *const list_CREATETRIGGER2[] =
-		{"ON", "OR", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
-	}
+	else if (MATCHES5("CREATE", "TRIGGER", "<name>", "BEFORE|AFTER", "<sth>") ||
+			 MATCHES6("CREATE", "TRIGGER", "<name>", "INSTEAD", "OF", "<sth>"))
+		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES5("TRIGGER", "<name>", "BEFORE|AFTER", "<event>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OF") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("INSTEAD", "OF", "<event>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "GRANT") == 0 && prev3_wd[0] == '\0') &&
-			 prev2_wd[0] != '\0')
+	else if (MATCHES1("EXECUTE") && !MATCHES3("", "|GRANT", "EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+	else if (MATCHES3("CREATE", "ROLE|GROUP|USER", "<name>") &&
+			 !MATCHES3("CREATE", "USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2644,11 +2018,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			   pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCHES4("CREATE", "ROLE|GROUP|USER", "<name>", "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2666,91 +2036,47 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCHES4("CREATE", "ROLE|USER|GROUP", "<name>", "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 pg_strcasecmp(prev_wd, "IN") == 0)
-	{
-		static const char *const list_CREATEROLE3[] =
-		{"GROUP", "ROLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATEROLE3);
-	}
+	else if (MATCHES4("CREATE", "ROLE|USER|GROUP", "<name>", "IN"))
+		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCHES3("CREATE", "VIEW", "<name>"))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCHES4("CREATE", "VIEW", "<name>", "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCHES2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCHES3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCHES5("CREATE", "MATERIALIZED", "VIEW", "<name>", "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
+	else if (MATCHES2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCHES4("CREATE", "EVENT", "TRIGGER", "<name>"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
-		{"ddl_command_start", "ddl_command_end", "sql_drop", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_EVENT_TRIGGER_ON);
-	}
+	else if (MATCHES5("CREATE", "EVENT", "TRIGGER", "<name>", "ON"))
+		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
-	{
-		static const char *const list_DECLARE[] =
-		{"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
-
-		COMPLETE_WITH_LIST(list_DECLARE);
-	}
+	else if (MATCHES2("DECLARE", "<name>"))
+		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR");
 
 /* CURSOR */
-	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
-	{
-		static const char *const list_DECLARECURSOR[] =
-		{"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
-
-		COMPLETE_WITH_LIST(list_DECLARECURSOR);
-	}
-
+	else if (MATCHES1("CURSOR"))
+		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE */
 
@@ -2758,231 +2084,109 @@ psql_completion(const char *text, int start, int end)
 	 * Complete DELETE with FROM (only if the word before that is not "ON"
 	 * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
 	 */
-	else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-			   pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0))
+	else if (MATCHES1("DELETE") && !MATCHES2("ON|GRANT|BEFORE|AFTER", "DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
+	else if (MATCHES2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FROM") == 0)
-	{
-		static const char *const list_DELETE[] =
-		{"USING", "WHERE", "SET", NULL};
-
-		COMPLETE_WITH_LIST(list_DELETE);
-	}
+	else if (MATCHES3("DELETE", "FROM", "<table>"))
+		COMPLETE_WITH_LIST3("USING", "WHERE", "SET");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
-	{
-		static const char *const list_DISCARD[] =
-		{"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
-
-		COMPLETE_WITH_LIST(list_DISCARD);
-	}
+	else if (MATCHES1("DISCARD"))
+		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
 
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (pg_strcasecmp(prev_wd, "DO") == 0)
-	{
-		static const char *const list_DO[] =
-		{"LANGUAGE", NULL};
-
-		COMPLETE_WITH_LIST(list_DO);
-	}
+	else if (MATCHES1("DO"))
+		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP (when not the previous word) */
 	/* DROP AGGREGATE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+	else if (MATCHES3("DROP", "AGGREGATE", "<name>"))
 		COMPLETE_WITH_CONST("(");
 
 	/* DROP object with CASCADE / RESTRICT */
-	else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
-			   pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-			  prev_wd[strlen(prev_wd) - 1] == ')') ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			  pg_strcasecmp(prev2_wd, "TRIGGER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
-			   pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
-		)
-	{
-		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
-		{
+	else if (MATCHES3("DROP",
+					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|FUNCTION|INDEX|LANGUAGE|SCHEMA|SEQUENCES|SERVER|TABLE|TYPE|VIEW",
+					  "<name>") ||
+			 (MATCHES4("DROP", "AGGREGATE", "<name>", "<sth>") && ends_with(prev_wd, ')')) ||
+			 MATCHES4("DROP", "EVENT", "TRIGGER", "<name>") ||
+			 MATCHES5("DROP", "FOREIGN", "DATA", "WRAPPER", "<name>") ||
+			 MATCHES5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", "<name>"))
+	{
+		if (MATCHES3("DROP", "FUNCTION", "<name>"))
 			COMPLETE_WITH_CONST("(");
-		}
 		else
-		{
-			static const char *const list_DROPCR[] =
-			{"CASCADE", "RESTRICT", NULL};
-
-			COMPLETE_WITH_LIST(list_DROPCR);
-		}
-	}
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const drop_CREATE_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
+			COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	}
+	else if (MATCHES2("DROP", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
-	{
+	else if (MATCHES2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (MATCHES3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
 
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCHES4("DROP", "AGGREGATE|FUNCTION", "<name>", "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	/* DROP OWNED BY */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "OWNED") == 0)
+	else if (MATCHES2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (MATCHES3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-
-		static const char *const list_ALTERTEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
-	}
+	else if (MATCHES3("DROP", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
+	else if (MATCHES3("DROP", "TRIGGER", "<name>"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("DROP", "TRIGGER", "<name>", "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_DROPCR[] =
-		{"CASCADE", "RESTRICT", NULL};
-
-		COMPLETE_WITH_LIST(list_DROPCR);
-	}
+	else if (MATCHES5("DROP", "TRIGGER", "<name>", "ON", "<name>"))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
-	{
+	else if (MATCHES2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (MATCHES3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
 
 	/* DROP POLICY <name>  */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "POLICY") == 0)
-	{
+	else if (MATCHES2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	}
 	/* DROP POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
-	{
+	else if (MATCHES3("DROP", "POLICY", "<name>"))
 		COMPLETE_WITH_CONST("ON");
-	}
 	/* DROP POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("DROP", "POLICY", "<name>", "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
-	{
+	else if (MATCHES3("DROP", "RULE", "<name>"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES4("DROP", "RULE", "<name>", "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_DROPCR[] =
-		{"CASCADE", "RESTRICT", NULL};
-
-		COMPLETE_WITH_LIST(list_DROPCR);
-	}
+	else if (MATCHES5("DROP", "RULE", "<name>", "ON", "<name>"))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE, but not EXECUTE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES2("", "EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2990,85 +2194,45 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
-	else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
-	else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0))
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
+	else if (MATCHES1("EXPLAIN"))
+		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"ANALYZE", "VERBOSE");
+	else if (MATCHES2("EXPLAIN", "ANALYZE|ANALYSE"))
+		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE");
+	else if (MATCHES2("EXPLAIN", "VERBOSE") ||
+			 MATCHES3("EXPLAIN", "ANALYZE|ANALYSE", "VERBOSE"))
+		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (pg_strcasecmp(prev_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev_wd, "MOVE") == 0)
-	{
-		static const char *const list_FETCH1[] =
-		{"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL};
-
-		COMPLETE_WITH_LIST(list_FETCH1);
-	}
+	else if (MATCHES1("FETCH|MOVE"))
+		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev2_wd, "MOVE") == 0)
-	{
-		static const char *const list_FETCH2[] =
-		{"ALL", "NEXT", "PRIOR", NULL};
-
-		COMPLETE_WITH_LIST(list_FETCH2);
-	}
+	else if (MATCHES2("FETCH|MOVE", "<sth>"))
+		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
 	 * Complete FETCH <sth1> <sth2> with "FROM" or "IN". These are equivalent,
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev3_wd, "MOVE") == 0)
-	{
-		static const char *const list_FROMIN[] =
-		{"FROM", "IN", NULL};
-
-		COMPLETE_WITH_LIST(list_FROMIN);
-	}
+	else if (MATCHES3("FETCH|MOVE", "<sth1>", "<sth2>"))
+		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+	else if (MATCHES3("FOREIGN", "DATA", "WRAPPER") &&
+			 !MATCHES4("CREATE", "FOREIGN", "DATA", "WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
 /* FOREIGN TABLE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
+	else if (MATCHES2("FOREIGN", "TABLE") &&
+			 !MATCHES3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
+	else if (MATCHES1("GRANT|REVOKE"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
@@ -3090,27 +2254,13 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev2_wd, "REVOKE") == 0)
+	else if (MATCHES2("GRANT|REVOKE", "<privilege>"))
 	{
-		if (pg_strcasecmp(prev_wd, "SELECT") == 0
-			|| pg_strcasecmp(prev_wd, "INSERT") == 0
-			|| pg_strcasecmp(prev_wd, "UPDATE") == 0
-			|| pg_strcasecmp(prev_wd, "DELETE") == 0
-			|| pg_strcasecmp(prev_wd, "TRUNCATE") == 0
-			|| pg_strcasecmp(prev_wd, "REFERENCES") == 0
-			|| pg_strcasecmp(prev_wd, "TRIGGER") == 0
-			|| pg_strcasecmp(prev_wd, "CREATE") == 0
-			|| pg_strcasecmp(prev_wd, "CONNECT") == 0
-			|| pg_strcasecmp(prev_wd, "TEMPORARY") == 0
-			|| pg_strcasecmp(prev_wd, "TEMP") == 0
-			|| pg_strcasecmp(prev_wd, "EXECUTE") == 0
-			|| pg_strcasecmp(prev_wd, "USAGE") == 0
-			|| pg_strcasecmp(prev_wd, "ALL") == 0)
+		if (MATCHES1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
 		else
 		{
-			if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
+			if (MATCHES2("GRANT", "<privilege>"))
 				COMPLETE_WITH_CONST("TO");
 			else
 				COMPLETE_WITH_CONST("FROM");
@@ -3128,9 +2278,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege
 	 */
-	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCHES3("GRANT|REVOKE", "<sth>", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'DATABASE'"
 								   " UNION SELECT 'DOMAIN'"
@@ -3142,195 +2290,138 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'SCHEMA'"
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_privilege_foreign[] =
-		{"DATA WRAPPER", "SERVER", NULL};
-
-		COMPLETE_WITH_LIST(list_privilege_foreign);
-	}
+	else if (MATCHES4("GRANT|REVOKE", "<sth>", "ON", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCHES4("GRANT|REVOKE", "<sth>", "ON", "<name>"))
 	{
-		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		if (MATCHES1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
+		else if (MATCHES1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
+		else if (MATCHES1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
+		else if (MATCHES1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (MATCHES1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+		else if (MATCHES1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
+		else if (MATCHES1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+		else if (MATCHES4("GRANT", "<sth>", "<sth>", "<sth>"))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
-	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCHES5("GRANT", "<sth>", "ON", "<sth>", "<sth>"))
 	{
-		if (pg_strcasecmp(prev_wd, "TO") == 0)
+		if (MATCHES1("TO"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 		else
 			COMPLETE_WITH_CONST("TO");
 	}
-	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCHES5("REVOKE", "<sth>", "ON", "<sth>", "<sth>")) 
 	{
-		if (pg_strcasecmp(prev_wd, "FROM") == 0)
+		if (MATCHES1("FROM"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * TO/FROM" with username, GROUP, or PUBLIC */
-	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
-	{
+	else if (MATCHES3("GRANT", "<sth>", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
-	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
-	{
+	else if (MATCHES3("REVOKE", "<sth>", "FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
 
 /* GROUP BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "GROUP") == 0)
+	else if (MATCHES3("FROM", "<sth>", "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+	else if (MATCHES1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCHES2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (MATCHES1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev_wd, "INTO") == 0)
+	else if (MATCHES2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCHES4("INSERT", "INTO", "<table>", "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INTO") == 0)
-	{
-		static const char *const list_INSERT[] =
-		{"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
-
-		COMPLETE_WITH_LIST(list_INSERT);
-	}
+	else if (MATCHES3("INSERT", "INTO", "<table>"))
+		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 prev_wd[strlen(prev_wd) - 1] == ')')
-	{
-		static const char *const list_INSERT[] =
-		{"SELECT", "TABLE", "VALUES", NULL};
-
-		COMPLETE_WITH_LIST(list_INSERT);
-	}
+	else if (MATCHES4("INSERT", "INTO", "<table>", "<?>") && ends_with(prev_wd, ')'))
+		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (pg_strcasecmp(prev_wd, "VALUES") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
+	else if (MATCHES1("VALUES") && !MATCHES2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+	else if (MATCHES1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LOCK") == 0)
+	else if (MATCHES2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-			  pg_strcasecmp(prev_wd, "TABLE") != 0) ||
-			 (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "LOCK") == 0))
+	else if ((MATCHES2("LOCK", "<table>") && !MATCHES2("LOCK", "TABLE")) ||
+			 MATCHES3("LOCK", "TABLE", "<table>"))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (pg_strcasecmp(prev_wd, "IN") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "LOCK") == 0 ||
-			  (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev4_wd, "LOCK") == 0)))
-	{
-		static const char *const lock_modes[] =
-		{"ACCESS SHARE MODE",
-			"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
-			"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
-			"SHARE ROW EXCLUSIVE MODE",
-		"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE", NULL};
-
-		COMPLETE_WITH_LIST(lock_modes);
-	}
+	else if (MATCHES3("LOCK", "<table>", "IN") ||
+			 MATCHES4("LOCK", "TABLE", "<table>", "IN"))
+		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
+							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
+							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
+							"SHARE ROW EXCLUSIVE MODE",
+							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY */
-	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
+	else if (MATCHES1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+	else if (MATCHES1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCHES2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ORDER") == 0)
+	else if (MATCHES3("FROM", "<sth>", "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (MATCHES4("FROM", "<sth>", "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "PREPARE") == 0)
-	{
-		static const char *const list_PREPARE[] =
-		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(list_PREPARE);
-	}
+	else if (MATCHES3("PREPARE", "<name>", "AS"))
+		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3338,116 +2429,72 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+	else if (MATCHES1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED");
-	else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+	else if (MATCHES2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+	else if (MATCHES3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+	else if (MATCHES4("REASSIGN", "OWNED", "BY", "<name>"))
 		COMPLETE_WITH_CONST("TO");
-	else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
-			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+	else if (MATCHES5("REASSIGN", "OWNED", "BY", "<name>", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+	else if (MATCHES1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCHES2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (MATCHES3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCHES4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCHES4("REFRESH", "MATERIALIZED", "VIEW", "<name>"))
 		COMPLETE_WITH_CONST("WITH");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+	else if (MATCHES5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", "<name>"))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCHES5("REFRESH", "MATERIALIZED", "VIEW", "<name>", "WITH"))
 	{
 		static const char *const list_WITH_DATA[] =
 		{"NO DATA", "DATA", NULL};
 
 		COMPLETE_WITH_LIST(list_WITH_DATA);
 	}
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCHES6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", "<name>", "WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCHES6("REFRESH", "MATERIALIZED", "VIEW", "<name>", "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
+	else if (MATCHES1("REINDEX"))
 	{
 		static const char *const list_REINDEX[] =
 		{"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
 
 		COMPLETE_WITH_LIST(list_REINDEX);
 	}
-	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
+	else if (MATCHES2("REINDEX", "<sth>"))
 	{
-		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
+		if (MATCHES1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
+		else if (MATCHES1("INDEX"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (MATCHES1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
-				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		else if (MATCHES1("SYSTEM|DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
 
 /* SECURITY LABEL */
-	else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (MATCHES1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "LABEL") == 0)
-	{
-		static const char *const list_SECURITY_LABEL_preposition[] =
-		{"ON", "FOR"};
-
-		COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
-	}
-	else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (MATCHES2("SECURITY", "LABEL"))
+		COMPLETE_WITH_LIST2("ON", "FOR");
+	else if (MATCHES4("SECURITY", "LABEL", "FOR", "<name>"))
 		COMPLETE_WITH_CONST("ON");
-	else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev3_wd, "FOR") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0))
+	else if (MATCHES3("SECURITY", "LABEL", "ON") ||
+			 MATCHES5("SECURITY", "LABEL", "FOR", "<sth>", "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
@@ -3456,9 +2503,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCHES5("SECURITY", "LABEL", "ON", "<sth>", "<sth>"))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3466,135 +2511,51 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCHES2("", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+	else if (MATCHES1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "START") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "WORK") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev4_wd, "SESSION") == 0
-				 && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0
-				 && pg_strcasecmp(prev2_wd, "AS") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
-	{
-		static const char *const my_list[] =
-		{"ISOLATION LEVEL", "READ", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0
-			  || pg_strcasecmp(prev3_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev3_wd, "START") == 0
-			  || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0
-				  && pg_strcasecmp(prev3_wd, "AS") == 0))
-			 && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev2_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev_wd, "ISOLATION") == 0)
+	else if (MATCHES2("SET|BEGIN|START", "TRANSACTION") ||
+			 MATCHES2("BEGIN", "WORK") ||
+			 MATCHES4("SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
+	else if (MATCHES3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+			 MATCHES4("CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if ((pg_strcasecmp(prev4_wd, "SET") == 0
-			  || pg_strcasecmp(prev4_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev4_wd, "START") == 0
-			  || pg_strcasecmp(prev4_wd, "AS") == 0)
-			 && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev3_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev2_wd, "ISOLATION") == 0
-			 && pg_strcasecmp(prev_wd, "LEVEL") == 0)
-	{
-		static const char *const my_list[] =
-		{"READ", "REPEATABLE", "SERIALIZABLE", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
-	{
-		static const char *const my_list[] =
-		{"UNCOMMITTED", "COMMITTED", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPEATABLE") == 0)
+	else if (MATCHES4("SET|BEGIN|START|AS", "TRANSACTION|WORK", "ISOLATION", "LEVEL"))
+		COMPLETE_WITH_LIST3("READ", "REPEATABLE", "SERIALIZABLE");
+	else if (MATCHES4("TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ"))
+		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+	else if (MATCHES4("TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BEGIN") == 0 ||
-			  pg_strcasecmp(prev3_wd, "START") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AS") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev2_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
-	{
-		static const char *const my_list[] =
-		{"ONLY", "WRITE", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
+	else if (MATCHES3("SET|BEGIN|START|AS", "TRANSACTION|WORK", "READ"))
+		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
-	{
+	else if (MATCHES2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
-	}
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
-	{
-		static const char *const constraint_list[] =
-		{"DEFERRED", "IMMEDIATE", NULL};
-
-		COMPLETE_WITH_LIST(constraint_list);
-	}
+	else if (MATCHES3("SET", "CONSTRAINTS", "<foo>"))
+		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "ROLE") == 0)
+	else if (MATCHES2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
-	{
-		static const char *const my_list[] =
-		{"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
+	else if (MATCHES2("SET", "SESSION"))
+		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0
-			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
-			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
+	else if (MATCHES3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (MATCHES2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
-			 pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
-			 prev_wd[strlen(prev_wd) - 1] != ')' &&
-			 prev_wd[strlen(prev_wd) - 1] != '=' &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
+	else if (MATCHES3("", "SET", "<var>"))
 		COMPLETE_WITH_CONST("TO");
 	/* Suggest possible variable values */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+	else if (MATCHES3("SET", "<var>", "TO|="))
 	{
-		if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
+		if (MATCHES2("DateStyle", "TO|="))
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3604,66 +2565,48 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0)
-		{
-			static const char *const my_list[] =
-			{"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
-		else if (pg_strcasecmp(prev2_wd, "GEQO") == 0)
-		{
-			static const char *const my_list[] =
-			{"ON", "OFF", "DEFAULT", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
-		else if (pg_strcasecmp(prev2_wd, "search_path") == 0)
-		{
+		else if (MATCHES2("IntervalStyle", "TO|="))
+			COMPLETE_WITH_LIST4("postgres", "postgres_verbose", "sql_standard", "iso_8601");
+		else if (MATCHES2("GEQO", "TO|="))
+			COMPLETE_WITH_LIST3("ON", "OFF", "DEFAULT");
+		else if (MATCHES2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
-		}
 		else
-		{
-			static const char *const my_list[] =
-			{"DEFAULT", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
+			COMPLETE_WITH_CONST("DEFAULT");
 	}
 
 /* START TRANSACTION */
-	else if (pg_strcasecmp(prev_wd, "START") == 0)
+	else if (MATCHES1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES2("", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
+	else if (MATCHES1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
 
-	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
+	else if (MATCHES2("TABLESTAMPLE", "<method>"))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
+	else if (MATCHES1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
+	else if (MATCHES1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
+	else if (MATCHES1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
+	else if (MATCHES2("UPDATE", "<table>"))
 		COMPLETE_WITH_CONST("SET");
 
 	/*
@@ -3671,83 +2614,54 @@ psql_completion(const char *text, int start, int end)
 	 * word) the word before it was (hopefully) a table name and we'll now
 	 * make a list of attributes.
 	 */
-	else if (pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCHES1("SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* UPDATE xx SET yy = */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+	else if (MATCHES4("UPDATE", "<table>", "SET", "<sth>"))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+	else if (MATCHES3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCHES4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCHES4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (MATCHES5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", "<sth>"))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
+	else if (MATCHES1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
+	else if (MATCHES2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCHES3("VACUUM", "FULL|FREEZE", "ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCHES3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+	else if MATCHES2("VACUUM", "VERBOSE")
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCHES2("VACUUM", "ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+	else if (MATCHES2("VERBOSE", "ANALYZE|ANALYSE") ||
+			 MATCHES2("ANALYZE|ANALYSE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3756,29 +2670,26 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES2("", "WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
-	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCHES1("ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
+	else if (MATCHES1("WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
-			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+	else if (MATCHES3("COPY|\\copy", "<sth>", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+	else if (MATCHES1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thomas Munro (#1)
Re: Making tab-complete.c easier to maintain

Thomas Munro <thomas.munro@enterprisedb.com> writes:

See attached a proof-of-concept patch. It reduces the size of
tab-complete.c by a bit over a thousand lines. I realise that changing so
many lines just to refactor code may may be a difficult sell! But I think
this would make it easier to improve the tab completion code in future, and
although it's large it's a superficial and mechanical change.

I really dislike the magical "<" business. Maybe you could use NULL
instead of having to reserve a class of real strings as placeholders.
(That would require finding another way to denote the end of the list,
but a separate count argument seems like a possible answer.)
Or perhaps use empty strings as placeholders?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Tom Lane (#2)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Sat, Sep 5, 2015 at 1:40 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Thomas Munro <thomas.munro@enterprisedb.com> writes:

See attached a proof-of-concept patch. It reduces the size of
tab-complete.c by a bit over a thousand lines. I realise that changing

so

many lines just to refactor code may may be a difficult sell! But I

think

this would make it easier to improve the tab completion code in future,

and

although it's large it's a superficial and mechanical change.

I really dislike the magical "<" business. Maybe you could use NULL
instead of having to reserve a class of real strings as placeholders.

Thanks, good point. Here's a version that uses NULL via a macro ANY.
Aside from a few corrections it also now distinguishes between
TAIL_MATCHESn (common) and MATCHESn (rarely used for now), for example:

        /* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-       else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-                        pg_strcasecmp(prev3_wd, "IN") == 0 &&
-                        pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-       {
-               static const char *const list_ALTERALLINTSPC[] =
-               {"SET TABLESPACE", "OWNED BY", NULL};
-
-               COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
-       }
+       else if (TAIL_MATCHES4("ALL", "IN", "TABLESPACE", ANY))
+               COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");

... versus:

 /* EXECUTE, but not EXECUTE embedded in other commands */
-       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-                        prev2_wd[0] == '\0')
+       else if (MATCHES1("EXECUTE"))
                COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v2.patchapplication/octet-stream; name=tab-complete-macrology-v2.patchDownload
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 816deda..08029f9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -228,6 +228,62 @@ do { \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_LIST2(s1, s2) \
+do { \
+	static const char *const list[] = { s1, s2, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST3(s1, s2, s3)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST6(s1, s2, s3, s4, s5, s6)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST7(s1, s2, s3, s4, s5, s6, s7)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, NULL }; \
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
+#define COMPLETE_WITH_LIST8(s1, s2, s3, s4, s5, s6, s7, s8)	\
+do { \
+	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, NULL };	\
+	completion_charpp = list; \
+	completion_case_sensitive = false; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -872,6 +928,59 @@ initialize_readline(void)
 	 */
 }
 
+/*
+ * Check if 'word' matches any of the '|'-separated strings in 'pattern',
+ * using case-insensitive comparison, or the pattern is NULL indicating a
+ * wildcard that matches any word.
+ */
+static bool
+word_matches(const char *pattern, const char *word)
+{
+	const char *c = word;
+
+	/*
+	 * NULL means match any word, but we don't match empty strings because
+	 * they indicate that the [END_]MATCHESn macro is trying to look before
+	 * the start of the user's input string.
+	 */
+	if (pattern == NULL)
+		return word[0] != '\0';
+
+	while (*pattern != '\0')
+	{
+		if (*c == '\0' && *pattern == '|')
+			return true;
+		else if (*c == '\0' || tolower(*pattern) != tolower(*c))
+		{
+			/* Skip to next word in pattern and rewind word. */
+			while (*pattern != '\0' && *pattern != '|')
+				++pattern;
+			if (*pattern == '\0')
+				return false;
+			else
+				++pattern;
+			c = word;
+		}
+		else
+		{
+			++pattern;
+			++c;
+		}
+	}
+
+	return *c == '\0';
+}
+
+/*
+ * Check if the final character of 's' is 'c'.
+ */
+static bool
+ends_with(const char *s, char c)
+{
+	size_t length = strlen(s);
+
+	return length > 0 && s[length - 1] == c;
+}
 
 /*
  * The completion function.
@@ -898,6 +1007,66 @@ psql_completion(const char *text, int start, int end)
 #define prev5_wd  (previous_words[4])
 #define prev6_wd  (previous_words[5])
 
+#define ANY NULL
+
+#define TAIL_MATCHES1(p1) \
+(word_matches(p1, prev_wd)) \
+
+#define TAIL_MATCHES2(p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd))
+
+#define TAIL_MATCHES3(p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd))
+
+#define TAIL_MATCHES4(p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd))
+
+#define TAIL_MATCHES5(p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd))
+
+#define TAIL_MATCHES6(p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd))
+
+#define TAIL_MATCHES7(p7, p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd) && \
+ word_matches(p7, prev7_wd))
+
+#define TAIL_MATCHES8(p8, p7, p6, p5, p4, p3, p2, p1) \
+(word_matches(p1, prev_wd) && \
+ word_matches(p2, prev2_wd) && \
+ word_matches(p3, prev3_wd) && \
+ word_matches(p4, prev4_wd) && \
+ word_matches(p5, prev5_wd) && \
+ word_matches(p6, prev6_wd) && \
+ word_matches(p7, prev7_wd) && \
+ word_matches(p8, prev8_wd))
+
+#define MATCHES1(p1) TAIL_MATCHES2("", p1)
+#define MATCHES2(p1, p2) TAIL_MATCHES3("", p1, p2)
+#define MATCHES3(p1, p2, p3) TAIL_MATCHES4("", p1, p2, p3)
+#define MATCHES4(p1, p2, p3, p4) TAIL_MATCHES5("", p1, p2, p3, p4)
+#define MATCHES5(p1, p2, p3, p4, p5) TAIL_MATCHES6("", p1, p2, p3, p4, p5)
+
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
@@ -931,6 +1100,22 @@ psql_completion(const char *text, int start, int end)
 	rl_completion_append_character = ' ';
 #endif
 
+	/* TODO:TM -- begin temporary, not part of the patch! */
+	Assert(!word_matches(NULL, ""));
+	Assert(word_matches(NULL, "walrus"));
+	Assert(word_matches("", ""));
+	Assert(word_matches("a", "A"));
+	Assert(word_matches("ab", "Ab"));
+	Assert(!word_matches("ab", "b"));
+	Assert(word_matches("a|b", "b"));
+	Assert(word_matches("a|b|c", "b"));
+	Assert(!word_matches("a|b|c", "d"));
+	Assert(!word_matches("a|b|c", "bb"));
+	Assert(word_matches("|foo", ""));
+	Assert(word_matches("foo|", ""));
+	Assert(!word_matches("foo", ""));
+	/* TODO:TM -- end temporary */
+
 	/* Clear a few things. */
 	completion_charp = NULL;
 	completion_charpp = NULL;
@@ -960,36 +1145,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (TAIL_MATCHES1(""))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (TAIL_MATCHES1("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES1("DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (TAIL_MATCHES2("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (TAIL_MATCHES1("ALTER") && !TAIL_MATCHES3("TABLE", ANY, "ALTER"))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1002,153 +1182,67 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-	{
-		static const char *const list_ALTERALLINTSPC[] =
-		{"SET TABLESPACE", "OWNED BY", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
-	}
+	else if (TAIL_MATCHES4("ALL", "IN", "TABLESPACE", ANY))
+		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (TAIL_MATCHES6("ALL", "IN", "TABLESPACE", ANY, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (TAIL_MATCHES3("ALTER", "AGGREGATE|FUNCTION", ANY))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (TAIL_MATCHES4("ALTER", "AGGREGATE|FUNCTION", ANY, ANY))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
-			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
+		if (ends_with(prev_wd, ')'))
+			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 		else
 			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (TAIL_MATCHES3("ALTER", "SCHEMA", ANY))
+		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (TAIL_MATCHES3("ALTER", "COLLATION", ANY))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
+	else if (TAIL_MATCHES3("ALTER", "CONVERSION", ANY))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
-	{
-		static const char *const list_ALTERDATABASE[] =
-		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
-		"ALLOW_CONNECTIONS", "CONNECTION LIMIT", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDATABASE);
-	}
+	else if (TAIL_MATCHES3("ALTER", "DATABASE", ANY))
+		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
+							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
+							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (TAIL_MATCHES3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
-		static const char *const list_ALTER_EVENT_TRIGGER[] =
-		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER);
-	}
+	else if (TAIL_MATCHES4("ALTER", "EVENT", "TRIGGER", ANY))
+		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
-	{
-		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
-		{"REPLICA", "ALWAYS", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER_ENABLE);
-	}
+	else if (TAIL_MATCHES5("ALTER", "EVENT", "TRIGGER", ANY, "ENABLE"))
+		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
-	{
-		static const char *const list_ALTEREXTENSION[] =
-		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTEREXTENSION);
-	}
+	else if (TAIL_MATCHES3("ALTER", "EXTENSION", ANY))
+		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_ALTER_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
-	}
+	else if (TAIL_MATCHES2("ALTER", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		static const char *const list_ALTER_FDW[] =
-		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_FDW);
-	}
+	else if (TAIL_MATCHES5("ALTER", "FOREIGN", "DATA", "WRAPPER", ANY))
+		COMPLETE_WITH_LIST4("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (TAIL_MATCHES4("ALTER", "FOREIGN", "TABLE", ANY))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1159,84 +1253,37 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
-	{
+	else if (TAIL_MATCHES2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 	/* ALTER INDEX <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
-	{
-		static const char *const list_ALTERINDEX[] =
-		{"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERINDEX);
-	}
+	else if (TAIL_MATCHES3("ALTER", "INDEX", ANY))
+		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_ALTERINDEXSET[] =
-		{"(", "TABLESPACE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
-	}
+	else if (TAIL_MATCHES4("ALTER", "INDEX", ANY, "SET"))
+		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (TAIL_MATCHES4("ALTER", "INDEX", ANY, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_INDEXOPTIONS[] =
-		{"fillfactor", "fastupdate", "gin_pending_list_limit", NULL};
-
-		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
-	}
+	else if (TAIL_MATCHES5("ALTER", "INDEX", ANY, "SET|RESET", "("))
+		COMPLETE_WITH_LIST3("fillfactor", "fastupdate", "gin_pending_list_limit");
 
 	/* ALTER LANGUAGE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
-	{
-		static const char *const list_ALTERLANGUAGE[] =
-		{"OWNER TO", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
-	}
+	else if (TAIL_MATCHES3("ALTER", "LANGUAGE", ANY))
+		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OBJECT") == 0)
-	{
-		static const char *const list_ALTERLARGEOBJECT[] =
-		{"OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
-	}
+	else if (TAIL_MATCHES4("ALTER", "LARGE", "OBJECT", ANY))
+		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (TAIL_MATCHES3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/* ALTER USER,ROLE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
+	else if (TAIL_MATCHES3("ALTER", "USER|ROLE", ANY) && !TAIL_MATCHES2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1250,10 +1297,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "USER") == 0 ||
-			   pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (TAIL_MATCHES4("ALTER", "USER|ROLE", ANY, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1268,78 +1312,32 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (TAIL_MATCHES4("ALTER", "USER|ROLE", ANY, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
-		{"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
-	}
+	else if (TAIL_MATCHES3("ALTER", "DEFAULT", "PRIVILEGES"))
+		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
-		{"ROLE", "USER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
-	}
+	else if (TAIL_MATCHES4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-			  pg_strcasecmp(prev3_wd, "IN") == 0))
-	{
-		static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
-		{"GRANT", "REVOKE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
-	}
+	else if (TAIL_MATCHES5("DEFAULT", "PRIVILEGES", "FOR", "ROLE", ANY) ||
+			 TAIL_MATCHES5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", ANY))
+		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
-	{
-		static const char *const list_ALTERDOMAIN[] =
-		{"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
-	}
+	else if (TAIL_MATCHES3("ALTER", "DOMAIN", ANY))
+		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_ALTERDOMAIN2[] =
-		{"CONSTRAINT", "DEFAULT", "NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
-	}
+	else if (TAIL_MATCHES4("ALTER", "DOMAIN", ANY, "DROP"))
+		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+	else if (TAIL_MATCHES5("ALTER", "DOMAIN", ANY, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (TAIL_MATCHES4("ALTER", "DOMAIN", ANY, "RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"CONSTRAINT", "TO", NULL};
@@ -1347,24 +1345,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+	else if (TAIL_MATCHES3("RENAME", "CONSTRAINT", ANY))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_ALTERDOMAIN3[] =
-		{"DEFAULT", "NOT NULL", "SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
-	}
+	else if (TAIL_MATCHES4("ALTER", "DOMAIN", ANY, "SET"))
+		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
+	else if (TAIL_MATCHES3("ALTER", "SEQUENCE", ANY))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1373,123 +1361,63 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
-	{
-		static const char *const list_ALTERSEQUENCE2[] =
-		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
-	}
+	else if (TAIL_MATCHES4("ALTER", "SEQUEMCE", ANY, "NO"))
+		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
-	{
-		static const char *const list_ALTER_SERVER[] =
-		{"VERSION", "OPTIONS", "OWNER TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTER_SERVER);
-	}
+	else if (TAIL_MATCHES3("ALTER", "SERVER", ANY))
+		COMPLETE_WITH_LIST3("VERSION", "OPTIONS", "OWNER TO");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "SYSTEM") == 0)
-	{
-		static const char *const list_ALTERSYSTEM[] =
-		{"SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
-	}
+	else if (TAIL_MATCHES2("ALTER", "SYSTEM"))
+		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (TAIL_MATCHES4("ALTER", "SYSTEM", "SET|RESET", ANY))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
-	{
-		static const char *const list_ALTERVIEW[] =
-		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERVIEW);
-	}
+	else if (TAIL_MATCHES3("ALTER", "VIEW", ANY))
+		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
-	{
-		static const char *const list_ALTERMATVIEW[] =
-		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
-	}
+	else if (TAIL_MATCHES4("ALTER", "MATERIALIZED", "VIEW", ANY))
+		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (TAIL_MATCHES3("ALTER", "POLICY", ANY))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("ALTER", "POLICY", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_ALTERPOLICY[] =
-		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERPOLICY);
-	}
+	else if (TAIL_MATCHES5("ALTER", "POLICY", ANY, "ON", ANY))
+		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING", "WITH CHECK");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (TAIL_MATCHES6("ALTER", "POLICY", ANY, "ON", ANY, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (TAIL_MATCHES6("ALTER", "POLICY", ANY, "ON", ANY, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+	else if (TAIL_MATCHES6("POLICY", ANY, "ON", ANY, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (TAIL_MATCHES3("ALTER", "RULE", ANY))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("ALTER", "RULE", ANY, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0)
+	else if (TAIL_MATCHES5("ALTER", "RULE", ANY, "ON", ANY))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES3("ALTER", "TRIGGER", ANY))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TRIGGER", ANY, ANY))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1498,22 +1426,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TRIGGER", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("ALTER", "TRIGGER", ANY, "ON", ANY))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (TAIL_MATCHES3("ALTER", "TABLE", ANY))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1523,279 +1446,135 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
-	{
-		static const char *const list_ALTERENABLE[] =
-		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERENABLE);
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
-			  pg_strcasecmp(prev_wd, "ALWAYS") == 0))
-	{
-		static const char *const list_ALTERENABLE2[] =
-		{"RULE", "TRIGGER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERENABLE2);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TABLE", ANY, "ENABLE"))
+		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
+							"TRIGGER");
+	else if (TAIL_MATCHES4("TABLE", ANY, "ENABLE", "REPLICA|ALWAYS"))
+		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (TAIL_MATCHES6("ALTER", "TABLE", ANY, "ENABLE", ANY, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES6("ALTER", "TABLE", ANY, "ENABLE", ANY, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (TAIL_MATCHES4("ALTER", "TABLE", ANY, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "NO") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx DISABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
-	{
-		static const char *const list_ALTERDISABLE[] =
-		{"ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERDISABLE);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TABLE", ANY, "DISABLE"))
+		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
-	{
-		static const char *const list_DISABLERLS[] =
-		{"CASCADE", NULL};
-
-		COMPLETE_WITH_LIST(list_DISABLERLS);
-	}
+	else if (TAIL_MATCHES4("DISABLE", "ROW", "LEVEL", "SECURITY"))
+		COMPLETE_WITH_CONST("CASCADE");
 
 	/* ALTER TABLE xxx ALTER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALTER") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TABLE", ANY, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (TAIL_MATCHES4("ALTER", "TABLE", ANY, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
+	else if (TAIL_MATCHES4("TABLE", ANY, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "RENAME", ANY) &&
+			 !TAIL_MATCHES1("CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (TAIL_MATCHES6("ALTER", "TABLE", ANY, "RENAME", "COLUMN|CONSTRAINT", ANY) &&
+			 !TAIL_MATCHES1("TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_TABLEDROP[] =
-		{"COLUMN", "CONSTRAINT", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLEDROP);
-	}
+	else if (TAIL_MATCHES3("TABLE", ANY, "DROP"))
+		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+	else if (TAIL_MATCHES5("ALTER", "TABLE", ANY, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
-	{
-		static const char *const list_COLUMNALTER[] =
-		{"TYPE", "SET", "RESET", "DROP", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNALTER);
-	}
+	else if (TAIL_MATCHES3("ALTER", "COLUMN", ANY) ||
+			 TAIL_MATCHES4("TABLE", ANY, "ALTER", ANY))
+		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_COLUMNSET[] =
-		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNSET);
-	}
+	else if (TAIL_MATCHES4("ALTER", "COLUMN", ANY, "SET") ||
+			 TAIL_MATCHES5("TABLE", ANY, "ALTER", ANY, "SET"))
+		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_COLUMNOPTIONS[] =
-		{"n_distinct", "n_distinct_inherited", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
-	}
+	else if (TAIL_MATCHES5("ALTER", "COLUMN", ANY, "SET", "(") ||
+			 TAIL_MATCHES4("ALTER", ANY, "SET", "("))
+		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
-	{
-		static const char *const list_COLUMNSTORAGE[] =
-		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
-	}
+	else if (TAIL_MATCHES5("ALTER", "COLUMN", ANY, "SET", "STORAGE") ||
+			 TAIL_MATCHES4("ALTER", ANY, "SET", "STORAGE"))
+		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
-	{
-		static const char *const list_COLUMNDROP[] =
-		{"DEFAULT", "NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_COLUMNDROP);
-	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
+	else if (TAIL_MATCHES4("ALTER", "COLUMN", ANY, "DROP") ||
+			 TAIL_MATCHES5("TABLE", ANY, "ALTER", ANY, "DROP"))
+		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
+	else if (TAIL_MATCHES3("TABLE", ANY, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("TABLE", ANY, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
-	{
-		static const char *const list_TABLESET[] =
-		{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESET);
-	}
+	else if (TAIL_MATCHES3("TABLE", ANY, "SET"))
+		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT");
 	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+	else if (TAIL_MATCHES4("TABLE", ANY, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITHOUT") == 0)
-	{
-		static const char *const list_TABLESET2[] =
-		{"CLUSTER", "OIDS", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESET2);
-	}
+	else if (TAIL_MATCHES4("TABLE", ANY, "SET", "WITHOUT"))
+		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (TAIL_MATCHES3("TABLE", ANY, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (TAIL_MATCHES4("TABLE", ANY, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1832,246 +1611,109 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
+	else if (TAIL_MATCHES4("REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
+	else if (TAIL_MATCHES5("TABLE", ANY, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
-	{
-		static const char *const list_REPLICAID[] =
-		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_REPLICAID);
-	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
-	{
+	else if (TAIL_MATCHES4("TABLE", ANY, "REPLICA", "IDENTITY"))
+		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
+	else if (TAIL_MATCHES3("TABLE", ANY, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
-	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-	{
-		static const char *const list_ALTERTSPC[] =
-		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTSPC);
-	}
+	else if (TAIL_MATCHES3("ALTER", "TABLESPACE", ANY))
+		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (TAIL_MATCHES4("ALTER", "TABLESPACE", ANY, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-	{
-		static const char *const list_TABLESPACEOPTIONS[] =
-		{"seq_page_cost", "random_page_cost", NULL};
-
-		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
-	}
+	else if (TAIL_MATCHES5("ALTER", "TABLESPACE", ANY, "SET|RESET", "("))
+		COMPLETE_WITH_LIST2("seq_page_cost", "random_page_cost");
 
 	/* ALTER TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "PARSER") == 0))
-	{
-		static const char *const list_ALTERTEXTSEARCH2[] =
-		{"RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
-	}
-
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH3[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
-	}
-
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
-	{
-		static const char *const list_ALTERTEXTSEARCH4[] =
-		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
-	}
+	else if (TAIL_MATCHES3("ALTER", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (TAIL_MATCHES5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", ANY))
+		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
+	else if (TAIL_MATCHES5("ALTER", "TEXT", "SEARCH", "DICTIONARY", ANY))
+		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (TAIL_MATCHES5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", ANY))
+		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR",
+							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TYPE") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
-		"OWNER TO", "RENAME", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (TAIL_MATCHES3("ALTER", "TYPE", ANY))
+		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
+							"DROP ATTRIBUTE", "OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ADD") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ATTRIBUTE", "VALUE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (TAIL_MATCHES4("ALTER", "TYPE", ANY, "ADD"))
+		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
-	{
-		static const char *const list_ALTERTYPE[] =
-		{"ATTRIBUTE", "TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTYPE);
-	}
+	else if (TAIL_MATCHES4("ALTER", "TYPE", ANY, "RENAME"))
+		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+	else if (TAIL_MATCHES5("TYPE", ANY, "RENAME", "ATTRIBUTE", ANY))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
 	 * attributes
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
+	else if (TAIL_MATCHES4("TYPE", ANY, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
-	{
+	else if (TAIL_MATCHES3("ALTER", "ATTRIBUTE", ANY))
 		COMPLETE_WITH_CONST("TYPE");
-	}
 	/* complete ALTER GROUP <foo> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "GROUP") == 0)
-	{
-		static const char *const list_ALTERGROUP[] =
-		{"ADD USER", "DROP USER", "RENAME TO", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGROUP);
-	}
+	else if (TAIL_MATCHES3("ALTER", "GROUP", ANY))
+		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev_wd, "DROP") == 0))
+	else if (TAIL_MATCHES4("ALTER", "GROUP", ANY, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
-	else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev_wd, "USER") == 0)
+	else if (TAIL_MATCHES4("GROUP", ANY, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
-			 pg_strcasecmp(prev_wd, "END") == 0 ||
-			 pg_strcasecmp(prev_wd, "ABORT") == 0)
-	{
-		static const char *const list_TRANS[] =
-		{"WORK", "TRANSACTION", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS);
-	}
+	else if (TAIL_MATCHES1("BEGIN|END|ABORT"))
+		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
-	{
-		static const char *const list_COMMIT[] =
-		{"WORK", "TRANSACTION", "PREPARED", NULL};
-
-		COMPLETE_WITH_LIST(list_COMMIT);
-	}
+	else if (TAIL_MATCHES1("COMMIT"))
+		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (pg_strcasecmp(prev_wd, "RELEASE") == 0)
+	else if (TAIL_MATCHES1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0)
-	{
-		static const char *const list_TRANS[] =
-		{"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS);
-	}
+	else if (TAIL_MATCHES1("ROLLBACK"))
+		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 
 	/*
 	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
+	else if (TAIL_MATCHES1("CLUSTER") && !TAIL_MATCHES2("WITHOUT", "CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
 
 	/*
 	 * If the previous words are CLUSTER VERBOSE produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+	else if (TAIL_MATCHES2("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") != 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-	{
+	else if (TAIL_MATCHES2("CLUSTER", ANY) && !TAIL_MATCHES1("VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
-	}
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
-	{
+	else if (TAIL_MATCHES3("CLUSTER", "VERBOSE", ANY))
 		COMPLETE_WITH_CONST("USING");
-	}
 
 	/*
 	 * If we have CLUSTER <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (TAIL_MATCHES3("CLUSTER", ANY, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
@@ -2080,19 +1722,16 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (TAIL_MATCHES4("CLUSTER", "VERBOSE", ANY, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 
 /* COMMENT */
-	else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
+	else if (TAIL_MATCHES1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -2104,66 +1743,27 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_TRANS2[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS2);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_TRANS2[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_TRANS2);
-	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
+	else if (TAIL_MATCHES3("COMMENT", "ON", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+	else if (TAIL_MATCHES4("COMMENT", "ON", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (TAIL_MATCHES3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
-	{
+	else if (TAIL_MATCHES4("COMMENT", "ON", "CONSTRAINT", ANY))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("COMMENT", "ON", "CONSTRAINT", ANY, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (TAIL_MATCHES4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (TAIL_MATCHES4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
-	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev4_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev5_wd, "ON") == 0)) &&
-			 pg_strcasecmp(prev_wd, "IS") != 0)
+	else if ((TAIL_MATCHES4("COMMENT", "ON", ANY, ANY) ||
+			  TAIL_MATCHES5("COMMENT", "ON", ANY, ANY, ANY) ||
+			  TAIL_MATCHES6("COMMENT", "ON", ANY, ANY, ANY, ANY)) &&
+			 !TAIL_MATCHES1("IS"))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2172,59 +1772,28 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (pg_strcasecmp(prev_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev_wd, "\\copy") == 0 ||
-			 (pg_strcasecmp(prev2_wd, "COPY") == 0 &&
-			  pg_strcasecmp(prev_wd, "BINARY") == 0))
+	else if (TAIL_MATCHES1("COPY|\\copy") || TAIL_MATCHES2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
-	else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
-			 pg_strcasecmp(prev2_wd, "BINARY") == 0)
-	{
-		static const char *const list_FROMTO[] =
-		{"FROM", "TO", NULL};
-
-		COMPLETE_WITH_LIST(list_FROMTO);
-	}
+	else if (TAIL_MATCHES2("COPY|\\copy|BINARY", ANY))
+		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
-	else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev_wd, "TO") == 0))
+	else if (TAIL_MATCHES3("COPY|\\copy|BINARY", ANY, "FROM|TO"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename */
-	else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev4_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev2_wd, "TO") == 0))
-	{
-		static const char *const list_COPY[] =
-		{"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
-
-		COMPLETE_WITH_LIST(list_COPY);
-	}
+	else if (TAIL_MATCHES4("COPY|\\copy|BINARY", ANY, "FROM|TO", ANY))
+		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING");
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-	else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev3_wd, "TO") == 0))
-	{
-		static const char *const list_CSV[] =
-		{"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
-
-		COMPLETE_WITH_LIST(list_CSV);
-	}
+	else if (TAIL_MATCHES3("FROM|TO", ANY, "CSV"))
+		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL");
 
 	/* CREATE DATABASE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (TAIL_MATCHES3("CREATE", "DATABASE", ANY))
 	{
 		static const char *const list_DATABASE[] =
 		{"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS_TEMPLATE",
@@ -2234,403 +1803,194 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DATABASE);
 	}
 
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+	else if (TAIL_MATCHES4("CREATE", "DATABASE", ANY, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+	else if (TAIL_MATCHES2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (TAIL_MATCHES3("CREATE", "EXTENSION", ANY))
 		COMPLETE_WITH_CONST("WITH SCHEMA");
 
 	/* CREATE FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_CREATE_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
-	}
+	else if (TAIL_MATCHES2("CREATE", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
-		{"HANDLER", "VALIDATOR", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_FOREIGN_DATA_WRAPPER);
-	}
+	else if (TAIL_MATCHES5("CREATE", "FOREIGN", "DATA", "WRAPPER", ANY))
+		COMPLETE_WITH_LIST2("HANDLER", "VALIDATOR");
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
+	else if (TAIL_MATCHES2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
-	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+	else if (TAIL_MATCHES2("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX [<name>] ON with a list of tables  */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES3("INDEX", ANY, "ON") ||
+			 TAIL_MATCHES2("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (TAIL_MATCHES3("INDEX", ANY, "CONCURRENTLY") ||
+			 TAIL_MATCHES2("INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
-	{
-		static const char *const list_CREATE_INDEX[] =
-		{"CONCURRENTLY", "ON", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_INDEX);
-	}
+	else if (TAIL_MATCHES3("CREATE|UNIQUE", "INDEX", ANY))
+		COMPLETE_WITH_LIST2("CONCURRENTLY", "ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_CREATE_INDEX2[] =
-		{"(", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
-	}
-	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (TAIL_MATCHES4("INDEX", ANY, "ON", ANY) ||
+			 TAIL_MATCHES3("INDEX|CONCURRENTLY", "ON", ANY))
+		COMPLETE_WITH_LIST2("(", "USING");
+	else if (TAIL_MATCHES5("INDEX", ANY, "ON", ANY, "(") ||
+			 TAIL_MATCHES4("INDEX|CONCURRENTLY", "ON", ANY, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (TAIL_MATCHES5("ON", ANY, "USING", ANY, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (TAIL_MATCHES6("INDEX", ANY, ANY, "ON", ANY, "USING") ||
+			 TAIL_MATCHES5("INDEX", ANY, "ON", ANY, "USING") ||
+			 TAIL_MATCHES4("INDEX", "ON", ANY, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
-			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0)
+	else if (TAIL_MATCHES4("ON", ANY, "USING", ANY) &&
+			 !TAIL_MATCHES6("POLICY", ANY, ANY, ANY, ANY, ANY) &&
+			 !TAIL_MATCHES4("FOR", ANY, ANY, ANY))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (TAIL_MATCHES3("CREATE", "POLICY", ANY))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("CREATE", "POLICY", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"FOR", "TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (TAIL_MATCHES5("CREATE", "POLICY", ANY, "ON", ANY))
+		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
-	{
-		static const char *const list_POLICYCMDS[] =
-		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYCMDS);
-	}
+	else if (TAIL_MATCHES6("CREATE", "POLICY", ANY, "ON", ANY, "FOR"))
+		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 pg_strcasecmp(prev_wd, "INSERT") == 0)
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (TAIL_MATCHES6("POLICY", ANY, "ON", ANY, "FOR", "INSERT"))
+		COMPLETE_WITH_LIST2("TO", "WITH CHECK");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
 	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
-			  pg_strcasecmp(prev_wd, "DELETE") == 0))
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "USING", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (TAIL_MATCHES6("POLICY", ANY, "ON", ANY, "FOR", "SELECT|DELETE"))
+		COMPLETE_WITH_LIST2("TO", "USING");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK"
 	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
 	 * CHECK"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
-			  pg_strcasecmp(prev_wd, "UPDATE") == 0))
-	{
-		static const char *const list_POLICYOPTIONS[] =
-		{"TO", "USING", "WITH CHECK", NULL};
-
-		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
-	}
+	else if (TAIL_MATCHES6("POLICY", ANY, "ON", ANY, "FOR", "ALL|UPDATE"))
+		COMPLETE_WITH_LIST3("TO", "USING", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (TAIL_MATCHES6("CREATE", "POLICY", ANY, "ON", ANY, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (TAIL_MATCHES6("CREATE", "POLICY", ANY, "ON", ANY, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (TAIL_MATCHES3("CREATE", "RULE", ANY))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS with "ON" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (TAIL_MATCHES4("CREATE", "RULE", ANY, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
-	else if (pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		static const char *const rule_events[] =
-		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(rule_events);
-	}
+	else if (TAIL_MATCHES4("RULE", ANY, "AS", "ON"))
+		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
-	else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
-			  pg_toupper((unsigned char) prev_wd[5]) == 'T'))
+	else if (TAIL_MATCHES3("AS", "ON", "SELECT|UPDATE|DELETE|INSERT"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (TAIL_MATCHES4("AS", "ON", "SELECT|UPDATE|DELETE|INSERT", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0))
-	{
-		static const char *const list_CREATESEQUENCE[] =
-		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
-		 "CYCLE", "OWNED BY", "START WITH", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
-	}
+	else if (TAIL_MATCHES3("CREATE", "SEQUENCE", ANY) ||
+			 TAIL_MATCHES4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", ANY))
+		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
+							"CYCLE", "OWNED BY", "START WITH");
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev4_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev4_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0)) &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
-	{
-		static const char *const list_CREATESEQUENCE2[] =
-		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATESEQUENCE2);
-	}
+	else if (TAIL_MATCHES4("CREATE", "SEQUENCE", ANY, "NO") ||
+			 TAIL_MATCHES5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", ANY, "NO"))
+		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
-	{
-		static const char *const list_CREATE_SERVER[] =
-		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_SERVER);
-	}
+	else if (TAIL_MATCHES3("CREATE", "SERVER", ANY))
+		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
-			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
-	{
-		static const char *const list_TEMP[] =
-		{"SEQUENCE", "TABLE", "VIEW", NULL};
-
-		COMPLETE_WITH_LIST(list_TEMP);
-	}
+	else if (TAIL_MATCHES2("CREATE", "TEMP|TEMPORARY"))
+		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
-	{
-		static const char *const list_UNLOGGED[] =
-		{"TABLE", "MATERIALIZED VIEW", NULL};
-
-		COMPLETE_WITH_LIST(list_UNLOGGED);
-	}
+	else if (TAIL_MATCHES2("CREATE", "UNLOGGED"))
+		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
-	{
-		static const char *const list_CREATETABLESPACE[] =
-		{"OWNER", "LOCATION", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
-	}
+	else if (TAIL_MATCHES3("CREATE", "TABLESPACE", ANY))
+		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNER") == 0)
-	{
+	else if (TAIL_MATCHES5("CREATE", "TABLESPACE", ANY, "OWNER", ANY))
 		COMPLETE_WITH_CONST("LOCATION");
-	}
 
 /* CREATE TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-		static const char *const list_CREATETEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
-	}
-	else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (TAIL_MATCHES3("CREATE", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+	else if (TAIL_MATCHES4("TEXT", "SEARCH", "CONFIGURATION", ANY))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
-		static const char *const list_CREATETRIGGER[] =
-		{"BEFORE", "AFTER", "INSTEAD OF", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER);
-	}
+	else if (TAIL_MATCHES3("CREATE", "TRIGGER", ANY))
+		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev_wd, "AFTER") == 0))
-	{
-		static const char *const list_CREATETRIGGER_EVENTS[] =
-		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
-	}
+	else if (TAIL_MATCHES4("CREATE", "TRIGGER", ANY, "BEFORE|AFTER"))
+		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev_wd, "OF") == 0)
-	{
-		static const char *const list_CREATETRIGGER_EVENTS[] =
-		{"INSERT", "DELETE", "UPDATE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
-	}
+	else if (TAIL_MATCHES5("CREATE", "TRIGGER", ANY, "INSTEAD", "OF"))
+		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0)) ||
-			 (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			  pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
-			  pg_strcasecmp(prev2_wd, "OF") == 0))
-	{
-		static const char *const list_CREATETRIGGER2[] =
-		{"ON", "OR", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
-	}
+	else if (TAIL_MATCHES5("CREATE", "TRIGGER", ANY, "BEFORE|AFTER", ANY) ||
+			 TAIL_MATCHES6("CREATE", "TRIGGER", ANY, "INSTEAD", "OF", ANY))
+		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("TRIGGER", ANY, "BEFORE|AFTER", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OF") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("INSTEAD", "OF", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "GRANT") == 0 && prev3_wd[0] == '\0') &&
-			 prev2_wd[0] != '\0')
+	else if (TAIL_MATCHES1("EXECUTE") && !TAIL_MATCHES3("", "|GRANT", "EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+	else if (TAIL_MATCHES3("CREATE", "ROLE|GROUP|USER", ANY) &&
+			 !TAIL_MATCHES3("CREATE", "USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2644,11 +2004,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			   pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (TAIL_MATCHES4("CREATE", "ROLE|GROUP|USER", ANY, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2666,91 +2022,47 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (TAIL_MATCHES4("CREATE", "ROLE|USER|GROUP", ANY, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 pg_strcasecmp(prev_wd, "IN") == 0)
-	{
-		static const char *const list_CREATEROLE3[] =
-		{"GROUP", "ROLE", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATEROLE3);
-	}
+	else if (TAIL_MATCHES4("CREATE", "ROLE|USER|GROUP", ANY, "IN"))
+		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (TAIL_MATCHES3("CREATE", "VIEW", ANY))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (TAIL_MATCHES4("CREATE", "VIEW", ANY, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (TAIL_MATCHES2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (TAIL_MATCHES3("CREATE", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (TAIL_MATCHES5("CREATE", "MATERIALIZED", "VIEW", ANY, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
+	else if (TAIL_MATCHES2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (TAIL_MATCHES4("CREATE", "EVENT", "TRIGGER", ANY))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
-		{"ddl_command_start", "ddl_command_end", "sql_drop", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_EVENT_TRIGGER_ON);
-	}
+	else if (TAIL_MATCHES5("CREATE", "EVENT", "TRIGGER", ANY, "ON"))
+		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
-	{
-		static const char *const list_DECLARE[] =
-		{"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
-
-		COMPLETE_WITH_LIST(list_DECLARE);
-	}
+	else if (TAIL_MATCHES2("DECLARE", ANY))
+		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR");
 
 /* CURSOR */
-	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
-	{
-		static const char *const list_DECLARECURSOR[] =
-		{"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
-
-		COMPLETE_WITH_LIST(list_DECLARECURSOR);
-	}
-
+	else if (TAIL_MATCHES1("CURSOR"))
+		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE */
 
@@ -2758,231 +2070,109 @@ psql_completion(const char *text, int start, int end)
 	 * Complete DELETE with FROM (only if the word before that is not "ON"
 	 * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
 	 */
-	else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-			   pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0))
+	else if (TAIL_MATCHES1("DELETE") && !TAIL_MATCHES2("ON|GRANT|BEFORE|AFTER", "DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
+	else if (TAIL_MATCHES2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FROM") == 0)
-	{
-		static const char *const list_DELETE[] =
-		{"USING", "WHERE", "SET", NULL};
-
-		COMPLETE_WITH_LIST(list_DELETE);
-	}
+	else if (TAIL_MATCHES3("DELETE", "FROM", ANY))
+		COMPLETE_WITH_LIST3("USING", "WHERE", "SET");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
-	{
-		static const char *const list_DISCARD[] =
-		{"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
-
-		COMPLETE_WITH_LIST(list_DISCARD);
-	}
+	else if (TAIL_MATCHES1("DISCARD"))
+		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
 
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (pg_strcasecmp(prev_wd, "DO") == 0)
-	{
-		static const char *const list_DO[] =
-		{"LANGUAGE", NULL};
-
-		COMPLETE_WITH_LIST(list_DO);
-	}
+	else if (TAIL_MATCHES1("DO"))
+		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP (when not the previous word) */
 	/* DROP AGGREGATE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+	else if (TAIL_MATCHES3("DROP", "AGGREGATE", ANY))
 		COMPLETE_WITH_CONST("(");
 
 	/* DROP object with CASCADE / RESTRICT */
-	else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
-			   pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-			  prev_wd[strlen(prev_wd) - 1] == ')') ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			  pg_strcasecmp(prev2_wd, "TRIGGER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
-			   pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
-		)
-	{
-		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
-		{
+	else if (TAIL_MATCHES3("DROP",
+					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|FUNCTION|INDEX|LANGUAGE|SCHEMA|SEQUENCES|SERVER|TABLE|TYPE|VIEW",
+					  ANY) ||
+			 (TAIL_MATCHES4("DROP", "AGGREGATE", ANY, ANY) && ends_with(prev_wd, ')')) ||
+			 TAIL_MATCHES4("DROP", "EVENT", "TRIGGER", ANY) ||
+			 TAIL_MATCHES5("DROP", "FOREIGN", "DATA", "WRAPPER", ANY) ||
+			 TAIL_MATCHES5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", ANY))
+	{
+		if (TAIL_MATCHES3("DROP", "FUNCTION", ANY))
 			COMPLETE_WITH_CONST("(");
-		}
 		else
-		{
-			static const char *const list_DROPCR[] =
-			{"CASCADE", "RESTRICT", NULL};
-
-			COMPLETE_WITH_LIST(list_DROPCR);
-		}
-	}
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const drop_CREATE_FOREIGN[] =
-		{"DATA WRAPPER", "TABLE", NULL};
-
-		COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
+			COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 	}
+	else if (TAIL_MATCHES2("DROP", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
-	{
+	else if (TAIL_MATCHES2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (TAIL_MATCHES3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
 
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (TAIL_MATCHES4("DROP", "AGGREGATE|FUNCTION", ANY, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	/* DROP OWNED BY */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "OWNED") == 0)
+	else if (TAIL_MATCHES2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (TAIL_MATCHES3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
-	{
-
-		static const char *const list_ALTERTEXTSEARCH[] =
-		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
-	}
+	else if (TAIL_MATCHES3("DROP", "TEXT", "SEARCH"))
+		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
+	else if (TAIL_MATCHES3("DROP", "TRIGGER", ANY))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("DROP", "TRIGGER", ANY, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_DROPCR[] =
-		{"CASCADE", "RESTRICT", NULL};
-
-		COMPLETE_WITH_LIST(list_DROPCR);
-	}
+	else if (TAIL_MATCHES5("DROP", "TRIGGER", ANY, "ON", ANY))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
-	{
+	else if (TAIL_MATCHES2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (TAIL_MATCHES3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
 
 	/* DROP POLICY <name>  */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "POLICY") == 0)
-	{
+	else if (TAIL_MATCHES2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	}
 	/* DROP POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
-	{
+	else if (TAIL_MATCHES3("DROP", "POLICY", ANY))
 		COMPLETE_WITH_CONST("ON");
-	}
 	/* DROP POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("DROP", "POLICY", ANY, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
-	{
+	else if (TAIL_MATCHES3("DROP", "RULE", ANY))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("DROP", "RULE", ANY, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
-	{
-		static const char *const list_DROPCR[] =
-		{"CASCADE", "RESTRICT", NULL};
-
-		COMPLETE_WITH_LIST(list_DROPCR);
-	}
+	else if (TAIL_MATCHES5("DROP", "RULE", ANY, "ON", ANY))
+		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE, but not EXECUTE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES1("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2990,85 +2180,45 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
-	else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
-	else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0))
-	{
-		static const char *const list_EXPLAIN[] =
-		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
-
-		COMPLETE_WITH_LIST(list_EXPLAIN);
-	}
+	else if (TAIL_MATCHES1("EXPLAIN"))
+		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+							"ANALYZE", "VERBOSE");
+	else if (TAIL_MATCHES2("EXPLAIN", "ANALYZE|ANALYSE"))
+		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE");
+	else if (TAIL_MATCHES2("EXPLAIN", "VERBOSE") ||
+			 TAIL_MATCHES3("EXPLAIN", "ANALYZE|ANALYSE", "VERBOSE"))
+		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (pg_strcasecmp(prev_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev_wd, "MOVE") == 0)
-	{
-		static const char *const list_FETCH1[] =
-		{"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL};
-
-		COMPLETE_WITH_LIST(list_FETCH1);
-	}
+	else if (TAIL_MATCHES1("FETCH|MOVE"))
+		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev2_wd, "MOVE") == 0)
-	{
-		static const char *const list_FETCH2[] =
-		{"ALL", "NEXT", "PRIOR", NULL};
-
-		COMPLETE_WITH_LIST(list_FETCH2);
-	}
+	else if (TAIL_MATCHES2("FETCH|MOVE", ANY))
+		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
 	 * Complete FETCH <sth1> <sth2> with "FROM" or "IN". These are equivalent,
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev3_wd, "MOVE") == 0)
-	{
-		static const char *const list_FROMIN[] =
-		{"FROM", "IN", NULL};
-
-		COMPLETE_WITH_LIST(list_FROMIN);
-	}
+	else if (TAIL_MATCHES3("FETCH|MOVE", ANY, ANY))
+		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+	else if (TAIL_MATCHES3("FOREIGN", "DATA", "WRAPPER") &&
+			 !TAIL_MATCHES4("CREATE", "FOREIGN", "DATA", "WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
 /* FOREIGN TABLE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
+	else if (TAIL_MATCHES2("FOREIGN", "TABLE") &&
+			 !TAIL_MATCHES3("CREATE", "FOREIGN", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
+	else if (TAIL_MATCHES1("GRANT|REVOKE"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
@@ -3090,27 +2240,13 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev2_wd, "REVOKE") == 0)
+	else if (TAIL_MATCHES2("GRANT|REVOKE", ANY))
 	{
-		if (pg_strcasecmp(prev_wd, "SELECT") == 0
-			|| pg_strcasecmp(prev_wd, "INSERT") == 0
-			|| pg_strcasecmp(prev_wd, "UPDATE") == 0
-			|| pg_strcasecmp(prev_wd, "DELETE") == 0
-			|| pg_strcasecmp(prev_wd, "TRUNCATE") == 0
-			|| pg_strcasecmp(prev_wd, "REFERENCES") == 0
-			|| pg_strcasecmp(prev_wd, "TRIGGER") == 0
-			|| pg_strcasecmp(prev_wd, "CREATE") == 0
-			|| pg_strcasecmp(prev_wd, "CONNECT") == 0
-			|| pg_strcasecmp(prev_wd, "TEMPORARY") == 0
-			|| pg_strcasecmp(prev_wd, "TEMP") == 0
-			|| pg_strcasecmp(prev_wd, "EXECUTE") == 0
-			|| pg_strcasecmp(prev_wd, "USAGE") == 0
-			|| pg_strcasecmp(prev_wd, "ALL") == 0)
+		if (TAIL_MATCHES1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
 		else
 		{
-			if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
+			if (TAIL_MATCHES2("GRANT", ANY))
 				COMPLETE_WITH_CONST("TO");
 			else
 				COMPLETE_WITH_CONST("FROM");
@@ -3128,9 +2264,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege
 	 */
-	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (TAIL_MATCHES3("GRANT|REVOKE", ANY, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'DATABASE'"
 								   " UNION SELECT 'DOMAIN'"
@@ -3142,195 +2276,138 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'SCHEMA'"
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
-	{
-		static const char *const list_privilege_foreign[] =
-		{"DATA WRAPPER", "SERVER", NULL};
-
-		COMPLETE_WITH_LIST(list_privilege_foreign);
-	}
+	else if (TAIL_MATCHES4("GRANT|REVOKE", ANY, "ON", "FOREIGN"))
+		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (TAIL_MATCHES4("GRANT|REVOKE", ANY, "ON", ANY))
 	{
-		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		if (TAIL_MATCHES1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
+		else if (TAIL_MATCHES1("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
+		else if (TAIL_MATCHES1("FUNCTION"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
+		else if (TAIL_MATCHES1("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (TAIL_MATCHES1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+		else if (TAIL_MATCHES1("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
+		else if (TAIL_MATCHES1("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+		else if (TAIL_MATCHES4("GRANT", ANY, ANY, ANY))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
-	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("GRANT", ANY, "ON", ANY, ANY))
 	{
-		if (pg_strcasecmp(prev_wd, "TO") == 0)
+		if (TAIL_MATCHES1("TO"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 		else
 			COMPLETE_WITH_CONST("TO");
 	}
-	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("REVOKE", ANY, "ON", ANY, ANY))
 	{
-		if (pg_strcasecmp(prev_wd, "FROM") == 0)
+		if (TAIL_MATCHES1("FROM"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * TO/FROM" with username, GROUP, or PUBLIC */
-	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
-	{
+	else if (TAIL_MATCHES3("GRANT", ANY, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
-	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
-	{
+	else if (TAIL_MATCHES3("REVOKE", ANY, "FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
 
 /* GROUP BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "GROUP") == 0)
+	else if (TAIL_MATCHES3("FROM", ANY, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+	else if (TAIL_MATCHES1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (TAIL_MATCHES2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (TAIL_MATCHES1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev_wd, "INTO") == 0)
+	else if (TAIL_MATCHES2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (TAIL_MATCHES4("INSERT", "INTO", ANY, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INTO") == 0)
-	{
-		static const char *const list_INSERT[] =
-		{"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
-
-		COMPLETE_WITH_LIST(list_INSERT);
-	}
+	else if (TAIL_MATCHES3("INSERT", "INTO", ANY))
+		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 prev_wd[strlen(prev_wd) - 1] == ')')
-	{
-		static const char *const list_INSERT[] =
-		{"SELECT", "TABLE", "VALUES", NULL};
-
-		COMPLETE_WITH_LIST(list_INSERT);
-	}
+	else if (TAIL_MATCHES4("INSERT", "INTO", ANY, ANY) && ends_with(prev_wd, ')'))
+		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (pg_strcasecmp(prev_wd, "VALUES") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
+	else if (TAIL_MATCHES1("VALUES") && !TAIL_MATCHES2("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+	else if (TAIL_MATCHES1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LOCK") == 0)
+	else if (TAIL_MATCHES2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-			  pg_strcasecmp(prev_wd, "TABLE") != 0) ||
-			 (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "LOCK") == 0))
+	else if ((TAIL_MATCHES2("LOCK", ANY) && !TAIL_MATCHES2("LOCK", "TABLE")) ||
+			 TAIL_MATCHES3("LOCK", "TABLE", ANY))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (pg_strcasecmp(prev_wd, "IN") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "LOCK") == 0 ||
-			  (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev4_wd, "LOCK") == 0)))
-	{
-		static const char *const lock_modes[] =
-		{"ACCESS SHARE MODE",
-			"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
-			"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
-			"SHARE ROW EXCLUSIVE MODE",
-		"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE", NULL};
-
-		COMPLETE_WITH_LIST(lock_modes);
-	}
+	else if (TAIL_MATCHES3("LOCK", ANY, "IN") ||
+			 TAIL_MATCHES4("LOCK", "TABLE", ANY, "IN"))
+		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
+							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
+							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
+							"SHARE ROW EXCLUSIVE MODE",
+							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY */
-	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
+	else if (TAIL_MATCHES1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+	else if (TAIL_MATCHES1("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (TAIL_MATCHES2("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ORDER") == 0)
+	else if (TAIL_MATCHES3("FROM", ANY, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (TAIL_MATCHES4("FROM", ANY, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "PREPARE") == 0)
-	{
-		static const char *const list_PREPARE[] =
-		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
-
-		COMPLETE_WITH_LIST(list_PREPARE);
-	}
+	else if (TAIL_MATCHES3("PREPARE", ANY, "AS"))
+		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3338,116 +2415,62 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+	else if (TAIL_MATCHES1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED");
-	else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+	else if (TAIL_MATCHES2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+	else if (TAIL_MATCHES3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+	else if (TAIL_MATCHES4("REASSIGN", "OWNED", "BY", ANY))
 		COMPLETE_WITH_CONST("TO");
-	else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
-			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+	else if (TAIL_MATCHES5("REASSIGN", "OWNED", "BY", ANY, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+	else if (TAIL_MATCHES1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (TAIL_MATCHES2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (TAIL_MATCHES3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (TAIL_MATCHES4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (TAIL_MATCHES4("REFRESH", "MATERIALIZED", "VIEW", ANY))
 		COMPLETE_WITH_CONST("WITH");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+	else if (TAIL_MATCHES5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", ANY))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
-	{
-		static const char *const list_WITH_DATA[] =
-		{"NO DATA", "DATA", NULL};
-
-		COMPLETE_WITH_LIST(list_WITH_DATA);
-	}
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (TAIL_MATCHES5("REFRESH", "MATERIALIZED", "VIEW", ANY, "WITH"))
+		COMPLETE_WITH_LIST2("NO DATA", "DATA");
+	else if (TAIL_MATCHES6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", ANY, "WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (TAIL_MATCHES6("REFRESH", "MATERIALIZED", "VIEW", ANY, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
-	{
-		static const char *const list_REINDEX[] =
-		{"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
-
-		COMPLETE_WITH_LIST(list_REINDEX);
-	}
-	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
+	else if (TAIL_MATCHES1("REINDEX"))
+		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
+	else if (TAIL_MATCHES2("REINDEX", ANY))
 	{
-		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
+		if (TAIL_MATCHES1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
+		else if (TAIL_MATCHES1("INDEX"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (TAIL_MATCHES1("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
-				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		else if (TAIL_MATCHES1("SYSTEM|DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
 
 /* SECURITY LABEL */
-	else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (TAIL_MATCHES1("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "LABEL") == 0)
-	{
-		static const char *const list_SECURITY_LABEL_preposition[] =
-		{"ON", "FOR"};
-
-		COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
-	}
-	else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (TAIL_MATCHES2("SECURITY", "LABEL"))
+		COMPLETE_WITH_LIST2("ON", "FOR");
+	else if (TAIL_MATCHES4("SECURITY", "LABEL", "FOR", ANY))
 		COMPLETE_WITH_CONST("ON");
-	else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev3_wd, "FOR") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0))
+	else if (TAIL_MATCHES3("SECURITY", "LABEL", "ON") ||
+			 TAIL_MATCHES5("SECURITY", "LABEL", "FOR", ANY, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
@@ -3456,9 +2479,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (TAIL_MATCHES5("SECURITY", "LABEL", "ON", ANY, ANY))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3466,135 +2487,51 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (TAIL_MATCHES1("SET|RESET") && !MATCHES3("UPDATE", ANY, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+	else if (TAIL_MATCHES1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "START") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "WORK") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev4_wd, "SESSION") == 0
-				 && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0
-				 && pg_strcasecmp(prev2_wd, "AS") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
-	{
-		static const char *const my_list[] =
-		{"ISOLATION LEVEL", "READ", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0
-			  || pg_strcasecmp(prev3_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev3_wd, "START") == 0
-			  || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0
-				  && pg_strcasecmp(prev3_wd, "AS") == 0))
-			 && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev2_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev_wd, "ISOLATION") == 0)
+	else if (TAIL_MATCHES2("SET|BEGIN|START", "TRANSACTION") ||
+			 TAIL_MATCHES2("BEGIN", "WORK") ||
+			 TAIL_MATCHES4("SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
+	else if (TAIL_MATCHES3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+			 TAIL_MATCHES4("CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if ((pg_strcasecmp(prev4_wd, "SET") == 0
-			  || pg_strcasecmp(prev4_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev4_wd, "START") == 0
-			  || pg_strcasecmp(prev4_wd, "AS") == 0)
-			 && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev3_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev2_wd, "ISOLATION") == 0
-			 && pg_strcasecmp(prev_wd, "LEVEL") == 0)
-	{
-		static const char *const my_list[] =
-		{"READ", "REPEATABLE", "SERIALIZABLE", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
-	{
-		static const char *const my_list[] =
-		{"UNCOMMITTED", "COMMITTED", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPEATABLE") == 0)
+	else if (TAIL_MATCHES4("SET|BEGIN|START|AS", "TRANSACTION|WORK", "ISOLATION", "LEVEL"))
+		COMPLETE_WITH_LIST3("READ", "REPEATABLE", "SERIALIZABLE");
+	else if (TAIL_MATCHES4("TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ"))
+		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+	else if (TAIL_MATCHES4("TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BEGIN") == 0 ||
-			  pg_strcasecmp(prev3_wd, "START") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AS") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev2_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
-	{
-		static const char *const my_list[] =
-		{"ONLY", "WRITE", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
+	else if (TAIL_MATCHES3("SET|BEGIN|START|AS", "TRANSACTION|WORK", "READ"))
+		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
-	{
+	else if (TAIL_MATCHES2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
-	}
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
-	{
-		static const char *const constraint_list[] =
-		{"DEFERRED", "IMMEDIATE", NULL};
-
-		COMPLETE_WITH_LIST(constraint_list);
-	}
+	else if (TAIL_MATCHES3("SET", "CONSTRAINTS", ANY))
+		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "ROLE") == 0)
+	else if (TAIL_MATCHES2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
-	{
-		static const char *const my_list[] =
-		{"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL};
-
-		COMPLETE_WITH_LIST(my_list);
-	}
+	else if (TAIL_MATCHES2("SET", "SESSION"))
+		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0
-			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
-			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
+	else if (TAIL_MATCHES3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (TAIL_MATCHES2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
-			 pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
-			 prev_wd[strlen(prev_wd) - 1] != ')' &&
-			 prev_wd[strlen(prev_wd) - 1] != '=' &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
+	else if (MATCHES2("SET", ANY))
 		COMPLETE_WITH_CONST("TO");
 	/* Suggest possible variable values */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+	else if (TAIL_MATCHES3("SET", ANY, "TO|="))
 	{
-		if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
+		if (TAIL_MATCHES2("DateStyle", "TO|="))
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3604,66 +2541,48 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0)
-		{
-			static const char *const my_list[] =
-			{"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
-		else if (pg_strcasecmp(prev2_wd, "GEQO") == 0)
-		{
-			static const char *const my_list[] =
-			{"ON", "OFF", "DEFAULT", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
-		else if (pg_strcasecmp(prev2_wd, "search_path") == 0)
-		{
+		else if (TAIL_MATCHES2("IntervalStyle", "TO|="))
+			COMPLETE_WITH_LIST4("postgres", "postgres_verbose", "sql_standard", "iso_8601");
+		else if (TAIL_MATCHES2("GEQO", "TO|="))
+			COMPLETE_WITH_LIST3("ON", "OFF", "DEFAULT");
+		else if (TAIL_MATCHES2("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
 								" UNION SELECT 'DEFAULT' ");
-		}
 		else
-		{
-			static const char *const my_list[] =
-			{"DEFAULT", NULL};
-
-			COMPLETE_WITH_LIST(my_list);
-		}
+			COMPLETE_WITH_CONST("DEFAULT");
 	}
 
 /* START TRANSACTION */
-	else if (pg_strcasecmp(prev_wd, "START") == 0)
+	else if (TAIL_MATCHES1("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (TAIL_MATCHES2("", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
+	else if (TAIL_MATCHES1("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
 
-	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
+	else if (TAIL_MATCHES2("TABLESTAMPLE", ANY))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
+	else if (TAIL_MATCHES1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
+	else if (TAIL_MATCHES1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
+	else if (TAIL_MATCHES1("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
+	else if (TAIL_MATCHES2("UPDATE", ANY))
 		COMPLETE_WITH_CONST("SET");
 
 	/*
@@ -3671,83 +2590,54 @@ psql_completion(const char *text, int start, int end)
 	 * word) the word before it was (hopefully) a table name and we'll now
 	 * make a list of attributes.
 	 */
-	else if (pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (TAIL_MATCHES2(ANY, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* UPDATE xx SET yy = */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+	else if (TAIL_MATCHES4("UPDATE", ANY, "SET", ANY))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+	else if (TAIL_MATCHES3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (TAIL_MATCHES4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (TAIL_MATCHES4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (TAIL_MATCHES5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", ANY))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
+	else if (TAIL_MATCHES1("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
+	else if (TAIL_MATCHES2("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (TAIL_MATCHES3("VACUUM", "FULL|FREEZE", "ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (TAIL_MATCHES3("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+	else if TAIL_MATCHES2("VACUUM", "VERBOSE")
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (TAIL_MATCHES2("VACUUM", "ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+	else if (TAIL_MATCHES2("VERBOSE", "ANALYZE|ANALYSE") ||
+			 TAIL_MATCHES2("ANALYZE|ANALYSE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3756,29 +2646,26 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCHES1("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
-	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (TAIL_MATCHES1("ANALYZE|ANALYSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
+	else if (TAIL_MATCHES1("WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
-			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+	else if (TAIL_MATCHES1("FROM") && !MATCHES3("COPY|\\copy", ANY, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+	else if (TAIL_MATCHES1("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
#4Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Thomas Munro (#3)
Re: Making tab-complete.c easier to maintain

Thomas Munro wrote:

Thanks, good point. Here's a version that uses NULL via a macro ANY.
Aside from a few corrections it also now distinguishes between
TAIL_MATCHESn (common) and MATCHESn (rarely used for now), for example:

This looks pretty neat -- 100x neater than what we have, at any rate. I
would use your new MATCHESn() macros a bit more -- for instance the
completion for "ALTER but not ALTER after ALTER TABLE" could be
rephrased as simply MATCHES1("ALTER"), i.e. have it match at start of
command only. Maybe that's just a matter of going over the new code
after the initial run, so that we can have a first patch that's mostly
mechanical and a second pass in which more semantically relevant changes
are applied. Seems easier to review ...

I would use "ANY" as a keyword here. Sounds way too generic to me.
Maybe "CompleteAny" or something like that.

Stylistically, I find there's too much uppercasing here. Maybe rename the
macros like this instead:

+       else if (TailMatches4("ALL", "IN", "TABLESPACE", ANY))
+               CompleteWithList2("SET TABLESPACE", "OWNED BY");

Not totally sure about this part TBH.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Alvaro Herrera (#4)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Tue, Sep 8, 2015 at 1:56 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thomas Munro wrote:

Thanks, good point. Here's a version that uses NULL via a macro ANY.
Aside from a few corrections it also now distinguishes between
TAIL_MATCHESn (common) and MATCHESn (rarely used for now), for example:

This looks pretty neat -- 100x neater than what we have, at any rate. I
would use your new MATCHESn() macros a bit more -- for instance the
completion for "ALTER but not ALTER after ALTER TABLE" could be
rephrased as simply MATCHES1("ALTER"), i.e. have it match at start of
command only. Maybe that's just a matter of going over the new code
after the initial run, so that we can have a first patch that's mostly
mechanical and a second pass in which more semantically relevant changes
are applied. Seems easier to review ...

+1

I would use "ANY" as a keyword here. Sounds way too generic to me.
Maybe "CompleteAny" or something like that.

MatchAny would make more sense to me.

Stylistically, I find there's too much uppercasing here. Maybe rename the

macros like this instead:

+       else if (TailMatches4("ALL", "IN", "TABLESPACE", ANY))
+               CompleteWithList2("SET TABLESPACE", "OWNED BY");

Not totally sure about this part TBH.

Ok, here's a rebased version that uses the style you suggested. It also
adds HeadMatchesN macros, so we can do this:

         * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
         * CURRENT_USER, or SESSION_USER.
         */
-       else if (((pg_strcasecmp(prev9_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev5_wd, "GRANT") == 0) &&
-                         pg_strcasecmp(prev_wd, "TO") == 0) ||
-                        ((pg_strcasecmp(prev9_wd, "REVOKE") == 0 ||
-                          pg_strcasecmp(prev8_wd, "REVOKE") == 0 ||
-                          pg_strcasecmp(prev7_wd, "REVOKE") == 0 ||
-                          pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
-                          pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
-                         pg_strcasecmp(prev_wd, "FROM") == 0))
+       else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
+                        (HeadMatches1("REVOKE") && TailMatches1("FROM")))
                COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);

So to recap:

MatchesN(...) -- matches the whole expression (up to lookback buffer size)
HeadMatchesN(...) -- matches the start of the expression (ditto)
TailMatchesN(...) -- matches the end of the expression
MatchAny -- placeholder

It would be nice to get rid of those numbers in the macro names, and I
understand that we can't use variadic macros. Should we use varargs
functions instead of macros? Then we could lose the numbers, but we'd need
to introduce global variables to keep the notation short and sweet (or pass
in the previous_words and previous_words_count, which would be ugly
boilerplate worse than the numbers).

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v3.patch.gzapplication/x-gzip; name=tab-complete-macrology-v3.patch.gzDownload
#6Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Thomas Munro (#5)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

Hi

Here is a new version merging recent changes.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v4.patch.gzapplication/x-gzip; name=tab-complete-macrology-v4.patch.gzDownload
#7Jeff Janes
jeff.janes@gmail.com
In reply to: Thomas Munro (#6)
Re: Making tab-complete.c easier to maintain

On Sun, Oct 18, 2015 at 5:31 PM, Thomas Munro <thomas.munro@enterprisedb.com

wrote:

Hi

Here is a new version merging recent changes.

For reasons I do not understand, "SET work_mem " does not complete with
"TO".

But if I change:

else if (Matches2("SET", MatchAny))

to:

else if (TailMatches2("SET", MatchAny))

Then it does.

Cheers,

Jeff

#8Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Jeff Janes (#7)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Mon, Oct 19, 2015 at 4:58 PM, Jeff Janes <jeff.janes@gmail.com> wrote:

On Sun, Oct 18, 2015 at 5:31 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

Hi

Here is a new version merging recent changes.

For reasons I do not understand, "SET work_mem " does not complete with
"TO".

But if I change:

else if (Matches2("SET", MatchAny))

to:

else if (TailMatches2("SET", MatchAny))

Then it does.

Thanks for taking a look at this! The word count returned by
get_previous_words was incorrect. Here is a corrected version.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v5.patch.gzapplication/x-gzip; name=tab-complete-macrology-v5.patch.gzDownload
#9Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Thomas Munro (#8)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

Here is a new version merging the recent CREATE EXTENSION ... VERSION
patch from master.

(Apologies for sending so many versions. tab-complete.c keeps moving
and I want to keep a version that applies on top of master out there,
for anyone interested in looking at this. As long as no one objects
and there is interest in the patch, I'll keep doing that.)

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v6.patch.gzapplication/x-gzip; name=tab-complete-macrology-v6.patch.gzDownload
#10Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#9)
Re: Making tab-complete.c easier to maintain

On Wed, Oct 21, 2015 at 8:54 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

Here is a new version merging the recent CREATE EXTENSION ... VERSION
patch from master.

(Apologies for sending so many versions. tab-complete.c keeps moving
and I want to keep a version that applies on top of master out there,
for anyone interested in looking at this. As long as no one objects
and there is interest in the patch, I'll keep doing that.)

I don't want to rain on the parade since other people seem to like
this, but I'm sort of unimpressed by this. Yes, it removes >1000
lines of code, and that's not nothing. But it's all mechanical code,
so, not to be dismissive, but who really cares? Is it really worth
replacing the existing notation that we all know with a new one that
we have to learn? I'm not violently opposed if someone else wants to
commit this, but I'm unexcited about it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#10)
Re: Making tab-complete.c easier to maintain

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Oct 21, 2015 at 8:54 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

(Apologies for sending so many versions. tab-complete.c keeps moving
and I want to keep a version that applies on top of master out there,
for anyone interested in looking at this. As long as no one objects
and there is interest in the patch, I'll keep doing that.)

I don't want to rain on the parade since other people seem to like
this, but I'm sort of unimpressed by this. Yes, it removes >1000
lines of code, and that's not nothing. But it's all mechanical code,
so, not to be dismissive, but who really cares? Is it really worth
replacing the existing notation that we all know with a new one that
we have to learn? I'm not violently opposed if someone else wants to
commit this, but I'm unexcited about it.

What I would like is to find a way to auto-generate basically this entire
file from gram.y. That would imply going over to something at least
somewhat parser-based, instead of the current way that is more or less
totally ad-hoc. That would be a very good thing though, because the
current way gives wrong answers not-infrequently, even discounting cases
that it's simply not been taught about.

I have no very good idea how to do that, though. Bison does have a
notion of which symbols are possible as the next symbol at any given
parse point, but it doesn't really make that accessible. There's a lack
of cooperation on the readline side too: we'd need to be able to see the
whole query buffer not just the current line.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#11)
Re: Making tab-complete.c easier to maintain

Tom Lane wrote:

What I would like is to find a way to auto-generate basically this entire
file from gram.y. That would imply going over to something at least
somewhat parser-based, instead of the current way that is more or less
totally ad-hoc. That would be a very good thing though, because the
current way gives wrong answers not-infrequently, even discounting cases
that it's simply not been taught about.

I did discuss exactly this topic with Thomas a month ago or so in
private email, and our conclusion was that it would be a really neat
project but a lot more effort than this patch. And after skimming over
the patch back then, I think this is well worth the reduced maintenance
effort.

I have no very good idea how to do that, though. Bison does have a
notion of which symbols are possible as the next symbol at any given
parse point, but it doesn't really make that accessible. There's a lack
of cooperation on the readline side too: we'd need to be able to see the
whole query buffer not just the current line.

At the current pace, a project like this might take years to
turn into a real patch. My own vote is for applying this for the time
being.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13David Fetter
david@fetter.org
In reply to: Tom Lane (#11)
Re: Making tab-complete.c easier to maintain

On Thu, Oct 22, 2015 at 02:36:53PM -0700, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Oct 21, 2015 at 8:54 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

(Apologies for sending so many versions. tab-complete.c keeps moving
and I want to keep a version that applies on top of master out there,
for anyone interested in looking at this. As long as no one objects
and there is interest in the patch, I'll keep doing that.)

I don't want to rain on the parade since other people seem to like
this, but I'm sort of unimpressed by this. Yes, it removes >1000
lines of code, and that's not nothing. But it's all mechanical code,
so, not to be dismissive, but who really cares? Is it really worth
replacing the existing notation that we all know with a new one that
we have to learn? I'm not violently opposed if someone else wants to
commit this, but I'm unexcited about it.

What I would like is to find a way to auto-generate basically this entire
file from gram.y.

I've been hoping we could use a principled approach for years. My
fondest hope along that line would also involve catalog access, so it
could correctly tab-complete user-defined things, but I have the
impression that the catalog access variant is "much later" even if
autogeneration from gram.y is merely "soon." I'd love to be wrong
about that.

That would imply going over to something at least
somewhat parser-based, instead of the current way that is more or less
totally ad-hoc. That would be a very good thing though, because the
current way gives wrong answers not-infrequently, even discounting cases
that it's simply not been taught about.

Indeed.

I have no very good idea how to do that, though. Bison does have a
notion of which symbols are possible as the next symbol at any given
parse point, but it doesn't really make that accessible. There's a lack
of cooperation on the readline side too: we'd need to be able to see the
whole query buffer not just the current line.

This may be on point:

http://stackoverflow.com/questions/161495/is-there-a-nice-way-of-handling-multi-line-input-with-gnu-readline

I suspect we might have to stop pretending to support alternatives to
libreadline if we went that direction, not that that would necessarily
be a bad idea.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#13)
Re: Making tab-complete.c easier to maintain

David Fetter <david@fetter.org> writes:

On Thu, Oct 22, 2015 at 02:36:53PM -0700, Tom Lane wrote:

I have no very good idea how to do that, though. Bison does have a
notion of which symbols are possible as the next symbol at any given
parse point, but it doesn't really make that accessible. There's a lack
of cooperation on the readline side too: we'd need to be able to see the
whole query buffer not just the current line.

This may be on point:

http://stackoverflow.com/questions/161495/is-there-a-nice-way-of-handling-multi-line-input-with-gnu-readline

I suspect we might have to stop pretending to support alternatives to
libreadline if we went that direction, not that that would necessarily
be a bad idea.

Given the license issues around GNU readline, requiring it seems like
probably a non-starter.

It strikes me though that maybe we don't need readline's cooperation.
I think it's already true that the previous lines of the query buffer
are stashed somewhere that psql knows about, so in principle we could
sew them together with the current line. That might be a project worth
tackling on its own, since we could make the existing code smarter about
multiline queries, whether or not we ever get to a grammar-based solution.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15David G. Johnston
david.g.johnston@gmail.com
In reply to: Tom Lane (#14)
Re: Making tab-complete.c easier to maintain

On Thu, Oct 22, 2015 at 7:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think it's already true that the previous lines of the query buffer
are stashed somewhere that psql knows about,

​Just skimming but:​

​"""
​\p or \print
Print the current query buffer to the standard output.
"""
http://www.postgresql.org/docs/9.4/interactive/app-psql.html

David J.

#16David Fetter
david@fetter.org
In reply to: Tom Lane (#14)
Re: Making tab-complete.c easier to maintain

On Thu, Oct 22, 2015 at 04:05:06PM -0700, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Thu, Oct 22, 2015 at 02:36:53PM -0700, Tom Lane wrote:

I have no very good idea how to do that, though. Bison does have a
notion of which symbols are possible as the next symbol at any given
parse point, but it doesn't really make that accessible. There's a lack
of cooperation on the readline side too: we'd need to be able to see the
whole query buffer not just the current line.

This may be on point:

http://stackoverflow.com/questions/161495/is-there-a-nice-way-of-handling-multi-line-input-with-gnu-readline

I suspect we might have to stop pretending to support alternatives to
libreadline if we went that direction, not that that would necessarily
be a bad idea.

Given the license issues around GNU readline, requiring it seems like
probably a non-starter.

I should have made this more clear. I am not an IP attorney and don't
play one on TV, but this is what I've gotten out of conversations with
IP attorneys on this subject:

- If we allow people to disable readline at compile time, the software
is not GPL even if only libreadline would add the command line
editing capability.

- When people do compile with libreadline, the only software affected
by libreadine's license is the binary to which it is linked, namely
the psql client.

To be affective negatively by libreadline's viral license, an entity
would need to fork the psql client in proprietary ways that they did
not wish not to make available to end users, at the same time linking
in libreadline.

Maybe I'm missing something big, but I really don't see people out
there shipping a libreadline-enabled psql client, details of whose
source they'd want to keep a deep, dark secret.

If this gets to matter, we can probably get /pro bono/ work from IP
attorneys specializing in just this kind of thing.

It strikes me though that maybe we don't need readline's
cooperation. I think it's already true that the previous lines of
the query buffer are stashed somewhere that psql knows about, so in
principle we could sew them together with the current line. That
might be a project worth tackling on its own, since we could make
the existing code smarter about multiline queries, whether or not we
ever get to a grammar-based solution.

Great!

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#16)
Re: Making tab-complete.c easier to maintain

David Fetter <david@fetter.org> writes:

On Thu, Oct 22, 2015 at 04:05:06PM -0700, Tom Lane wrote:

Given the license issues around GNU readline, requiring it seems like
probably a non-starter.

I should have made this more clear. I am not an IP attorney and don't
play one on TV, but this is what I've gotten out of conversations with
IP attorneys on this subject:

I'm not an IP attorney either, and the ones I've talked to seem to agree
with you. (Red Hat's corporate attorneys, at least, certainly thought
that when I last asked them.) But the observed facts are that some
distros refuse to ship psql-linked-to-readline, and substitute libedit
instead, because they got different advice from their attorneys.

If we desupport libedit, the end result is going to be these distros
shipping psql with no command-line-editing support at all. Our opinion,
or even our lawyers' opinion, is unlikely to prevent that outcome.

While libedit certainly has got its bugs and deficiencies, it's way better
than nothing at all, so I think we'd better keep supporting it.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Andres Freund
andres@anarazel.de
In reply to: David Fetter (#16)
Re: Making tab-complete.c easier to maintain

On 2015-10-22 16:26:10 -0700, David Fetter wrote:

To be affective negatively by libreadline's viral license, an entity
would need to fork the psql client in proprietary ways that they did
not wish not to make available to end users, at the same time linking
in libreadline.

Maybe I'm missing something big, but I really don't see people out
there shipping a libreadline-enabled psql client, details of whose
source they'd want to keep a deep, dark secret.

Isn't that just about every proprietary fork of postgres? Most have
added backend features and I guess many of those have in turn added
support to psql for those features. Sure it'd probably in reality be
relatively harmless for them to release these psql modifications, but I
rather doubt their management will generally see it that way.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#18)
Re: Making tab-complete.c easier to maintain

On Thu, Oct 22, 2015 at 8:09 PM, Andres Freund <andres@anarazel.de> wrote:

On 2015-10-22 16:26:10 -0700, David Fetter wrote:

To be affective negatively by libreadline's viral license, an entity
would need to fork the psql client in proprietary ways that they did
not wish not to make available to end users, at the same time linking
in libreadline.

Maybe I'm missing something big, but I really don't see people out
there shipping a libreadline-enabled psql client, details of whose
source they'd want to keep a deep, dark secret.

Isn't that just about every proprietary fork of postgres? Most have
added backend features and I guess many of those have in turn added
support to psql for those features. Sure it'd probably in reality be
relatively harmless for them to release these psql modifications, but I
rather doubt their management will generally see it that way.

Yeah, exactly. EnterpriseDB have to keep libedit working even if the
PostgreSQL community dropped support, so I hope we don't decide to do
that.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#17)
Re: Making tab-complete.c easier to maintain

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

David Fetter <david@fetter.org> writes:

On Thu, Oct 22, 2015 at 04:05:06PM -0700, Tom Lane wrote:

Given the license issues around GNU readline, requiring it seems like
probably a non-starter.

I should have made this more clear. I am not an IP attorney and don't
play one on TV, but this is what I've gotten out of conversations with
IP attorneys on this subject:

I'm not an IP attorney either, and the ones I've talked to seem to agree
with you. (Red Hat's corporate attorneys, at least, certainly thought
that when I last asked them.) But the observed facts are that some
distros refuse to ship psql-linked-to-readline, and substitute libedit
instead, because they got different advice from their attorneys.

The issue for Debian, at least, isn't really about libreadline or
libedit, it's about the GPL and the OpenSSL license. From the Debian
perspective, if we were able to build with GNUTLS or another SSL library
other than OpenSSL (which I know we've made some progress on, thanks
Heikki...) then Debian wouldn't have any problem shipping PG linked with
libreadline.

If we desupport libedit, the end result is going to be these distros
shipping psql with no command-line-editing support at all. Our opinion,
or even our lawyers' opinion, is unlikely to prevent that outcome.

While libedit certainly has got its bugs and deficiencies, it's way better
than nothing at all, so I think we'd better keep supporting it.

I agree that it's probably not a good idea to desupport libedit.
However, I don't believe supporting libedit necessairly prevents us
from providing better support when libreadline is available.
Debian-based distributions won't be able to reap the benefits of that
better support until we remove the OpenSSL dependency, which is
unfortunate, but other distributions would be able to.

Also, if the PG project is agreeable to it, there's no reason why we
couldn't provide full libreadline-enabled builds off of
apt.postgresql.org, regardless of what Debian wants. That would add a
bit of extra burden on our package maintainers, as there isn't any
difference between packages built for Debian and packages built for
apt.p.o currently, I don't believe, but it seems like it'd be a pretty
minor change that would be well worth it if we get better libreadline
integration.

The one bit of all of this that worries me is that we would take
libreadline for granted and the libedit support would end up broken.
That seems a relatively minor concern though, as it sounds like there is
a fair bit of exercise of the libedit path due to the commercial forks
of PG which make use of it to avoid the GPL.

Thanks!

Stephen

#21Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Stephen Frost (#20)
Re: Making tab-complete.c easier to maintain

Stephen Frost wrote:

The issue for Debian, at least, isn't really about libreadline or
libedit, it's about the GPL and the OpenSSL license. From the Debian
perspective, if we were able to build with GNUTLS or another SSL library
other than OpenSSL (which I know we've made some progress on, thanks
Heikki...) then Debian wouldn't have any problem shipping PG linked with
libreadline.

What if OpenSSL were to switch to APL2?
/messages/by-id/20150801151410.GA28344@awork2.anarazel.de

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Stephen Frost
sfrost@snowman.net
In reply to: Alvaro Herrera (#21)
Re: Making tab-complete.c easier to maintain

* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:

Stephen Frost wrote:

The issue for Debian, at least, isn't really about libreadline or
libedit, it's about the GPL and the OpenSSL license. From the Debian
perspective, if we were able to build with GNUTLS or another SSL library
other than OpenSSL (which I know we've made some progress on, thanks
Heikki...) then Debian wouldn't have any problem shipping PG linked with
libreadline.

What if OpenSSL were to switch to APL2?
/messages/by-id/20150801151410.GA28344@awork2.anarazel.de

According to that post, it would depend what libreadline is licensed
under. There may be other complications with GPL3 and other libraries
we link against, though I can't think of any offhand and perhaps there
aren't any.

Thanks!

Stephen

#23David Fetter
david@fetter.org
In reply to: Andres Freund (#18)
Re: Making tab-complete.c easier to maintain

On Fri, Oct 23, 2015 at 02:09:43AM +0200, Andres Freund wrote:

On 2015-10-22 16:26:10 -0700, David Fetter wrote:

To be affective negatively by libreadline's viral license, an entity
would need to fork the psql client in proprietary ways that they did
not wish not to make available to end users, at the same time linking
in libreadline.

Maybe I'm missing something big, but I really don't see people out
there shipping a libreadline-enabled psql client, details of whose
source they'd want to keep a deep, dark secret.

Isn't that just about every proprietary fork of postgres?

Proprietary versions of the psql client? I'm not sure I understand.

Proprietary, secret changes to the back end, sure, but the client?
The most recent example I recall of that is Netezza, and I suspect
that they just couldn't be bothered to publish the changes they made.
At that time, the community psql client was not by any means as nice
as it is now, so it's conceivable that they made substantive
improvements, at least for talking to Netezza DBs.

Most have added backend features and I guess many of those have in
turn added support to psql for those features. Sure it'd probably
in reality be relatively harmless for them to release these psql
modifications, but I rather doubt their management will generally
see it that way.

Is it really on us as a community to go long distances out of our way
to assuage the baseless[1]as far as I know, and I'll take this back in its entirety if someone shows me a reasonable basis -- David Fetter <david@fetter.org> http://fetter.org/ Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter Skype: davidfetter XMPP: david.fetter@gmail.com paranoia of people who are by and large not
part of our community?

As to our "support" of libedit, feel free to try working with a psql
client that has libedit instead of libreadline for a few weeks. It
would be a very long stretch, at least for the work flows I've seen so
far, to call that client functional. The strongest praise I can come
up with is that it's usually not quite as awful as working with a psql
client entirely devoid of command line editing support.

Of course, we can continue to pretend that there is a viable
alternative to libreadline if that soothes down some feathers, but I
don't see any reason to make substantive sacrifices in service of
keeping that particular box checked.

Cheers,
David.

[1]: as far as I know, and I'll take this back in its entirety if someone shows me a reasonable basis -- David Fetter <david@fetter.org> http://fetter.org/ Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter Skype: davidfetter XMPP: david.fetter@gmail.com
someone shows me a reasonable basis
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Robert Haas
robertmhaas@gmail.com
In reply to: David Fetter (#23)
Re: Making tab-complete.c easier to maintain

On Fri, Oct 23, 2015 at 11:16 AM, David Fetter <david@fetter.org> wrote:

Proprietary, secret changes to the back end, sure, but the client?
The most recent example I recall of that is Netezza, and I suspect
that they just couldn't be bothered to publish the changes they made.
At that time, the community psql client was not by any means as nice
as it is now, so it's conceivable that they made substantive
improvements, at least for talking to Netezza DBs.

Most have added backend features and I guess many of those have in
turn added support to psql for those features. Sure it'd probably
in reality be relatively harmless for them to release these psql
modifications, but I rather doubt their management will generally
see it that way.

Is it really on us as a community to go long distances out of our way
to assuage the baseless[1] paranoia of people who are by and large not
part of our community?

I was under the impression that I was part of this community, and I
have already said that my employer has added tab completion support,
and other psql features, related to the server features we have added.

Also, your statement that this is a long distance out of our way does
not seem to be justified by the facts.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25David Fetter
david@fetter.org
In reply to: Robert Haas (#24)
Re: Making tab-complete.c easier to maintain

On Fri, Oct 23, 2015 at 12:15:01PM -0400, Robert Haas wrote:

On Fri, Oct 23, 2015 at 11:16 AM, David Fetter <david@fetter.org> wrote:

Proprietary, secret changes to the back end, sure, but the client?
The most recent example I recall of that is Netezza, and I suspect
that they just couldn't be bothered to publish the changes they
made. At that time, the community psql client was not by any
means as nice as it is now, so it's conceivable that they made
substantive improvements, at least for talking to Netezza DBs.

Most have added backend features and I guess many of those have
in turn added support to psql for those features. Sure it'd
probably in reality be relatively harmless for them to release
these psql modifications, but I rather doubt their management
will generally see it that way.

Is it really on us as a community to go long distances out of our
way to assuage the baseless[1] paranoia of people who are by and
large not part of our community?

I was under the impression that I was part of this community, and I
have already said that my employer has added tab completion support,
and other psql features, related to the server features we have
added.

Also, your statement that this is a long distance out of our way
does not seem to be justified by the facts.

As I wrote in the part you cut, we can continue to pretend libedit is
a viable alternative if it keeps some feathers unruffled. If libedit
"support" gets to be a real drag, we can and should reconsider with a
strong bias to dropping it.

Cheers,
David.

P.S. With a little luck, our next license or other perceived legal
conflict will be as easy to sort out as this one. I have the sinking
feeling that anything we do with crypto or hashing more secure than
MD5 will run afoul of someone's idea of what the law is in their
country, and the impacts will be a lot nastier than this. We will
need to deal with that separately, as needs arise.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#23)
Re: Making tab-complete.c easier to maintain

David Fetter <david@fetter.org> writes:

Is it really on us as a community to go long distances out of our way
to assuage the baseless[1] paranoia of people who are by and large not
part of our community?

While I personally don't care that much about the proprietary-psql-variant
scenario, I do care whether people using Debian packages will have a good
experience with psql.

And frankly, sir, you do not have the standing to call their fears
baseless. You can believe that they are, and you might even be right,
but your opinion on such matters does not matter to them.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Jeff Janes
jeff.janes@gmail.com
In reply to: Thomas Munro (#8)
Re: Making tab-complete.c easier to maintain

On Sun, Oct 18, 2015 at 9:12 PM, Thomas Munro <thomas.munro@enterprisedb.com

wrote:

Thanks for taking a look at this! The word count returned by
get_previous_words was incorrect. Here is a corrected version.

I haven't looked at v6 yet, but in v5:

"set work_mem TO" completes to "NULL" not to "DEFAULT"

line 2665 of the patched tab complete file,, should be "DEFAULT", not
"NULL" as the completion string. Looks like a simple copy and paste error.

For the bigger picture, I don't think we should not apply this patch simply
because there is something even better we might theoretically do at some
point in the future. Having used it a little bit, I do agree with Robert
that it is not a gigantic improvement over the current situation, as the
code it replaces is largely mechanical boilerplate. But I think it is
enough of an improvement that we should go ahead with it.

Cheers,

Jeff

#28Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Jeff Janes (#27)
Re: Making tab-complete.c easier to maintain

Jeff Janes wrote:

For the bigger picture, I don't think we should not apply this patch simply
because there is something even better we might theoretically do at some
point in the future.

Agreed.

Having used it a little bit, I do agree with Robert
that it is not a gigantic improvement over the current situation, as the
code it replaces is largely mechanical boilerplate. But I think it is
enough of an improvement that we should go ahead with it.

To me this patch sounds much like 2eafcf68d563df8a1db80a. You could say
that what was replaced was "largely mechanical", but it was so much
easier to make mistakes with the original coding that it's not funny.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Jeff Janes (#27)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Sat, Oct 24, 2015 at 6:19 AM, Jeff Janes <jeff.janes@gmail.com> wrote:

On Sun, Oct 18, 2015 at 9:12 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

Thanks for taking a look at this! The word count returned by
get_previous_words was incorrect. Here is a corrected version.

I haven't looked at v6 yet, but in v5:

"set work_mem TO" completes to "NULL" not to "DEFAULT"

line 2665 of the patched tab complete file,, should be "DEFAULT", not "NULL"
as the completion string. Looks like a simple copy and paste error.

Indeed. Thanks. Fixed in the attached.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v7.patch.gzapplication/x-gzip; name=tab-complete-macrology-v7.patch.gzDownload
���*Vtab-complete-macrology-v7.patch�}kwI��g�W����P�d�����#,s����nN	J2�
����7��U�YY����s���+����?���.�/��������K��x8{�O�e�b���~}6���?�Ip>*_T_������_F����J����q������,3����R��;EG���s����?�o�����~
B��]����YX,=�C0�_��X���I�?��O�I�
'������5
����y�w����8\�
a�Ca��9���K9����
?���<�]>|��'b�<��m���?�~�2I���$]���a0�i8^�?���?	��j2�H��t� �����K�+�������D��w��j!	DX����%2N5���?�B���)g����m1��w)C�3����]���%]X����}D��%GO���/��ex�oo������0x��<�1�q��Ct?[����#����4~I�����w��.�����@E��a��������/���BSP�z1�.�����G	��?�H��C�3Qi~s���d�����k��W���.�g�z���"�<���YfXO�h>���x��
<DZL�g<��|��h(�2�&��<����b�?���E�h��U�zA�1�b��'���?���Q c�}��ItwL�#L���`�;����$���>��=Z~
����8���_#��Y���_.�����C�B�"�>��$y��������0K�y?4���W������t�/��h�/F��������i�R��n6�<;��&v��/>Y��Hk��Iw�����"?�H��A�[�c��MA'����j���r5=��D��|�$�t�U��0�O���F�����|����o�)f���,O%���f������F"��X
"�9�*s�"6���i�� p��O���"X��W�'�����o'x.(��������}����i�����S`��H��C���5�q2�,�t�d>����F��TA���s��)�t�����t���f�,{&�b+��|���M�VA����d2�~E����p����#t��GGC��OJ>W�>�;�%��	W�S��P���xP�,������v�O��L�k�a��LH�>�����3�\�B1Z#��gtB�~���G��!]9��P�S��h�~5�'�*�.�E"��'���R�>Q��	��+�#�#��H��d"����������1��G����t�?bc�E�(�$�G��Cx�qi���#��Gl�MW�w��g|��0Gb�4��:]bj�G����K�Q���XxRY:\N�S���d<�_*4B�)�a,�����?2��D~|aH
��'��J�]�Y7��)�i�'�Eq��S�k�<K��B���c�<uM�r
�l"�T}���'���(�Ej�(��9��8�������k���.�
P�A��8><4LP.�����I�-��E9k��k�_u)������ja�U�op��U����ia�o�-X����i�g�9�V���oq�Z�e���0�7��A����'#/
s|e� r�)�oq���9���A�8���C��"5y.�F)1��2F*%F�]����O��#�#���G�(?F���9�5��7�V�R[�C���&�ZEi�@tu���P�|���>B�B��Y`��02��}�2C28����/�!3t��{��W�n�C��V��������n���W�6�P���r�i�i�z��k��=��U�\_7x���?����v�>�t���Z������u�Gv��V���xz��J�\j��?����u_��_�������]�����5:>Fw��7.���l�/�<4�-�\1��	���;1�.���1��+	p��\�E]�/�d�
3���g���S�e���M��m���e���Y5����f#X���I����
M��:��y���&��Y"���g��$�������
b���b&�5?E���pJ��x#\T�>�> ��!�����fa���	z��i��p�(�C�B0��/��I��h��������d��A���n�>������Vrf6��'D3C��D����GG�n�{L�;�{8��O��G��&��-/��U�JL����J�t�pS�y�+�U*���&���?��Mg,n�Wg6���;?�"�& !�E�k�r���)�)e���8�"����7���7�V��/�GC"�}*�v���}
K#e.�!��l���
��'�G������q.V�d�'K���!�ht��C0
�r��@]v;7�	��}�
���(����"�l���l��L�������'�&2F�`��� ���U(�J�w�����Sb�0�"	�t:���1����^*D:��a�V]�k���6��'������>�f��%fAH��?���m��i�^������VP��������}��c�M�`���
OAvB4��CW��-���^���/^��/!$2y��0��X���o)b���x9�����L�U��������h�$T��|y��D-%M��_���6���X��Z�>&	��������.kx�Z�k��k��>��6�6[��F�~����
��I��+V�DLWO���NV���`!���Hb�x��e�W���m�Z��n\����;�����om�������6-)�A��� ��5�+C��N){L(�k�	�:��	��-��?��E�fbW�w��e+�p��U�d��5k���Vg�����p
W�3]9x�0�4?��JN�G�lC��_���r;��4���v��7�����(�A�qJ��������ba�&]V�C9�Z��q|
mX��
�_H��=zt�eIew$���T�")�.*�TU���K��x�>8<8����"#���t����7�(��n�]�n�?�4b�LBNZ��ez��@����@�������Q�a�(�"��A�{%�Z�*�������{I���$�j��3�����G�<���,�n=����n��R��)�#�mG�6o��a^�;����M�����i!U������j���Ni!/,��]#rt��O�P7{�~�^�H_�ku��S������q��D�&���i)V&�ru�&�Y
(s�����t�FmSP'�����W�n���B��t
�����T��fS�����s0]����C�c�-��[(K=4b���Y��6�
����l���c����J��~�5Q���pg���2�J���+N�.��m�H�MYc��Q��M�Y�1�����������P����I�L/<�����k,r���}#z�.���Z����7��\�����g]`{BV�$��N���j������MxWEv�C���nnT�����Cf�^}��7��~I&���[ ���c�.�����K������"r���/[mok�&^C�����:�r�8��X�	D	(�6�&�`�4�Ty����-��=�/9N8bw��@D��t�������.�H��7�.������y�;���b�T����$TWj�����#;>e�>�O�4�H���$>vy����>����c����>�![g��#9�w"XTBX��.2�p��l������`��Q�F�6�\[Q(���p�{
J��w!�����2�����UH�
����x\�!�������sM"b���dr������.W����_��`����\���K�Q��O�
����a�`A���
����nkW����Ir/�u�o?bH�1#�}��8�Z������N���x�#���w*���E$<
����i�@���-?E$�85^)��#;��4�c������F���>������fnz������_����|
�y��F�^��j�}�:�`��Nt�8��������I�?,h�*���\I��r���M����LOP����U��-��Kg�xB������8��j�]%ei�j�*]��M\FV����9��4I��O�	�+
,�7�0�i�7Ez������S�*"(�0z_�H�KL����Q��$�\����v�����w�����/��w����\�:�d*��"�����94��l}�%�l$�[��6�>�5���������<��R�A�ac��
�Q"��u��8J�g!��������	�~�1���tY��;~���t���
&n�@]��v�XX��F�P��M�N+�z�8����j���',C�$�IN��	�5��[��,K"�\U���������x���67'������f�fE��qz��$���A�a�+8�2����P���w�h��]����:muP�������q���(m���.�y@�����lg����~fy�9T
Z����N�RD�������)%�	Z�K�$E%`����\���������'�����_B�������`���+2
�U]���OL /xJ��xz?�!�b��hw �����>m�4��q1��V�o`o��
�5��-q1�(gJ��I/����W"��b�+Vv�(
������jk@�u�	�F��rD�����uc(�����{�es��.�a��~�#otl��v��J�'V��%Y����)1��%��KN�?�.�v��y{��m|*�6�i�W��\j]�:�'jk���zd�-_�y�	���.����d��#-���kwv����n7�7��&&QTE�8!��M�
�q��u2"�f�r��Mn��[�Se�
G��c�a=#*(96uvdC��� (����7����1���]��o�0���cr9{�q���b��L�\n���Ny\���)�-�q��h�|-^��$K�2=�*��)\��0X>�4I�2}.�%fS/�0~%��]k����n��"�T������H���8m�~
�t�vso���x��J`>����`���*�o:�f�� u���k[�Yth�t�#!H?f��7�BjG���z�*�Rv�Ha��;�+>C�1
?�����$�����R
y����P#�����{�#�1��@4���~g3��9
Tlx����w��H���8u�m����m����:uA6�A�yX���LV�4%�����3c�����	�b7�4�w8b����Q%��"�-�C��6�B�I�����UEl�V�Ny��vv���N~�����yX����9H��A�����������`�D��H���
��UHR���P	�(W�W��D�8	���s�^����-�c~<��n�N21�}�hSL�6����J��u���06��r����IlT�\T��F��"�lJ:I.��Mv��*�JmWJi�[l��b���A`���,v�e�X^��r��%0Kq�)��Q��pu'3fC8.�P��D�&��%s�;/��x��(��5��Z9�JgXBT�XB���{C+r�����>���B��%�If���x�47��;�j�m�P�Q��tx������'�Q��@PDFI�
�u_@
����?���la�;��8,A��O�n�F����J��,I��@w��(��<_~4/���h�����w���	� \H�n�5�\9���E��-(@^+���N��U]�����������9��K��-~�7����a����n(2��Z���y��F%4N{0������s�kb�"��;`�}�!=���a��`�v;�������MqU����z��q[V����Z��W�K��&3�����%���=oEEQ����(Gt���w`TS����s�v�Q|����]����K��n4���G�*����-���F,+e����_Z�C��j|��Z���*��x��u��V��d�o�ANNt�q�`P
q�2
��;z�3}��fg���z��eH���	�}��-���/�7Y=NA��,a��v�(��R?=+M`�F;�����Y���OOO�q�>���U�r�J|�I�X���I�����
�B�����^*�w
����)`�1��t���D��
��`���3Q��|����
��f���\�.�(v:<����Wa�4r4���Nc5��
�E	'���y��#C���QB[(x�S�vC����	��B��^[rx�D1�YD�9� �u�v��;�0����YZ��c��<��5�_��B����BI6G��<~�R��lM�g$���o�-2%0����Rv�����A�,��=6My_=��#��3B�,��*p3�9e�	����@�~��9�Y���:]���h.��Sl&!�'�p�����Z��'&]�u��u��8���� f���t��-�`g+O%a�F���tH��+���S�/����f]71�O/������c
��q������.�!�X���nZ,Y	���G����d��<����cSl��"@]9O�4�0��,�I���|������y�|r�h�W�<��)�`���1��&'w�!�����R!!�Ml�ZG�V�3�"��W9Z�J{�3X�h��}�_.���2�?����N�i�z2�Y�R�[��+&#e��x����v�F���;�N�7����g��{V�3]��J�Ik_R�����a`7���U��ZQ*�����:�CL��N9�#+4e�-P�yiO��kTJ����������p9�k�,3%F�ube:��iC�d���E��b����
�K-�|��R��+��@Ht$�ZK��X�y��+UN�Q����+�\lB�D�.���'�b}hl^6�����.L��WM���pJ��\��d�P�vZu��:�.�q��(��#M
�'�V�d�=C�pz�*�4-�����y�T���B|y�9O��n�dr�]]��(�x�)o�2@��x]���`2�.M�1E7eg-$�h=$2jyJ��F���O�_u�wS7$8Rs����H�?
�.n��k�(#*�"�dw�z*i����i)����;*���o�^l���{E�c0�����������c����>.����xF^����"����6��`���k2)����W\��>f�Z��,��-y�'���:��]<	�+��yu����"���X�R�����U��
Lb�^��r��E��W�`��%�������*��U�p6�����+��m�!#jEr�&<��rUe���q)�	�+��S�sE�z����+"I6�u*p���ZA�It���X���J��2[W-�����d������y��y�ZX��|��p�S�(�J�����?�����E��w�o[�o�|u��)����*�;���
�\�-����o]����gW0#vH��Q�.����dr'��w5���wy�9+�X���+f�L�Q���E3�
#����"������������DI��Q�dk��$�����o�l7�&6�si�t#��(
�%^/�40F�
.1��v�
�|�R�qJ�cZ��8�vE�B@��]=r]9����;$q,^u;�7ZP��&�	r�Q�/�(���T)�J"X����Z��i�NT2�Q(s3��E���l�(v�����Z'��[�>o�
�K�������L�C�0X ��P��\0���v�y����������IV5x�0�_5��m5�����t�,l��_/��xthS�G{}�����u��Pe�|]��/��Nzd��b�����RM��n��x��dZ��D���>6���t7�.����!�Qm�5IW�Z8���V��k�^�m�����g�W�B��,���rQ�V�U��p)�����fr�����M�<��	�����	%�dF'�V!�2[��8�����t�T}#G�a�p5�(����J������XP��_b�	�X��������r�D�����mt_uz
]n�VC�/"�R�sc|�qr����w��s��Y<��r���?%U?�Z�������������'�/5[F2QF*�j�]v�5��I����r���6��K�A�{Wz�A�uX!�D��C�|������C�}Gv��s*������m�G�u�����'�H2��]�x��-���2���Rq$J�Q,��X�p�W�'��5�XG���:�l�Q�VI7���W�h}^"�����^Q��z���p�gQ�R:������Q"�7�TG�SY 'k��X��;%�����W���M��9��]v$A����Z�;�F��������A2z�����s��5jP���#.��}%��$�v�����K��'[��I�A���Z������_���5�I�r���	;Q�TA���������>u��4����!w�vY6@��yY��'y���$\�lQS;�5�r�\�LVK�s��"y�[����<�D����mK$pPp"c��T�0���s0]�l����S��������'�K�����W���_V�=���B�H��S��l9��j��m��0%>m�2��z������yO�j��pq�U���e�r��J��K\��<^5�b��|?����`�~_���i��a0�?����>X���fHT�M��H?z���d�@,�w���p���De�C0^"�m������S�u���&�i�a���J��7��r)�5J^Io�'���g��G�g���|�/��,��!�
�_w;�p�)��,b��������;J�'�spcd>��b�x��*+��	��k�(�E)u?�V���b��d>t�T2������@��g�wl��l��Q�O��B3���:�#���_���/f�B�0^��
�g��:P��D��#+�,9����!�)��}��%EE����F�y���T���{K��v�s	�&S�\=�����	K��g-<�<���]k�N�/�%}�mD�7��%C��o;�]����X����n����O�$m��6W�4q�L�J7XuD�z�Aw2���v���	��+z%��Bu'&rW5{��!]�~����#zo�A�Z=ni�"�I�w������/��J�������c2n�������C/���@z ����b���K������%}W�	���z�c�.Lm0�C[�1]����I�k���`�����/3�F�O�E
o��$��4_��f ��qD>���$"�N8�"������T�#��v��Y����������b�����H�%�������\]x;�#��0H�����
a�H��7kv��s��v��	���v��s=�r�}��X�zA�$�������EyE�s���H��n@P���@��\k_��{	+��Vi����$Mi�X�qD����d��~=^P{#5�H#	oz�n�-��[,��n�(���h=Y�RV":��G���{X(R����1�V��\�y\�^���L�����U�}/�%��2��'u���~�*��i�g6�T��v��v��� :w��x��s�v�~D�3vI���V�-��ul�B�~��e����a]��o��-�����;�w
���S'7�W���T�PN�O�A��Tk���I�����Tpc6T��u2��3����t�=cVI�v:��p��EJP�[���G!H����.�zAE��Z'+���C6R�i����"�7�'tGy��_`�Z��g��@����,���Iw��RJ��w�BG��`���Xq�y%�DXK@�h�d)�V�8��I!_V�����"F��y2������!y*�}���|�$���Y9��9%����ZHA.v�2��c(`�m�������.�N�����r���p�c��c�k��D��~7us1~�A�C`A�4r��|q�i5�i��t]"s��J��=CJ�v��e	���}��HRPw�����sc6���A?���g��nn�8�!��na
�B���!�g
�A��"I8�?�8I�QG���~gM������l�_�(�g���4���O%K�yY�7Y�A����F�
�$,���t����u��ZkfZ7��F����!u���
��}����+T�X���W��__*,S�UX)j�
�`��F��8r�2K&��:������F.�������S�5RJ2��E�6
��;,zME�
d��]j���aU�������X1�A�|U^�(�@w�Tl='o�L�s�������"��f?i_��yX�!��g,&���q�1�'�$G����v�v�"�x�&'�l�����w��H�3�5�S�"7�m�*�[h�xX��e�&�
���U����U�!�Z���d1�vo[
����J����/B��+�����@���
��yCG�Z������5(0�s��\,
	l�������]��#���.���X�8�F'Wt[��H��	z�S���bhI$
Bz�h�iBR
R������C��}1����1�v9[����PXM���4Q�"N����Y�����Ue��+��He�qs�fQb�t8&��D,{Hv�A��O�
3�#��h�<�)�%�O�K�z
<H�����e�2f��G����L�U0C�R�3���+`0��E�H6���P�}��n�e������s����1�Y�
��}�%+l\���~��j�;y�����%,O���� |a�0�V$�r�J�&��K�,��v'�Q�����.;%�� �p�Tr�v��v�XhxW��1q�`��a�?��3l��l���3���aO��yK2Z�2$������uU����z����w��b���>
 ���M�����l�
M�;�1��H����`��;qg���^Z	��8&?���p?�� 7�Ba���Y��h���f4��*������\]5.9�$�we�gr�}����`j6)��{1$����n���;��F����[��M���G�:��Cq���p��P��T��/��i���+	G?2��aGn�2���4�N%%��T�Y�
`2S�ye������+�8�(g��Xk99�C���_T�I�~�OR����(��s�IU'�'~��)��D��j���P���@�)�������.�����	[���%��v�a6��<�����j�*S5����QF�AS����q:�uV%Tke��c�����=���2s+��~��]w�l�����4sH�9�&���5�9�LN]@�3�]ty����H
7~�	�T�<"��5I����'�E���R�)�
c���q3������T�Lk�a��l���z)������@@�!_�d��*}������=�j���L�����@�"M�|c����cY��[I�BXo��&*�}y@�hbl��>Q�����	c�Fg<)���_uR��b����7.o�v��wN�C%������H����G�����������9����7ZL��>�z�lnu�����D�Q��jx����+�n�V��0�V)��������
�����-�2�$�1d���^#���5~���f���:��|h% :�:*T��n�C����Z��m�����������y�+�Q����mR(in2pu���u}����Y8&���^e{�n�0��\s��$�`D����	�
��N2���>�[-�h6=XF���E��?��`�0�Q9C�����I����rq�U6��zc��F��}�y��m���`��
����t��}��b,(_92���g-�n��3�����,m��*-�]���8�8u����)�������o@`s�S��?Yy9�E	�1�\�Rt �
(�K����+Lj,�.�z����sk�Yqc5s��������k���;��*pn��9����,����z���[���(�^��:���FG�G{�ZR97��`{h��o������k�j���.V7tG����[���o-�]������@��kw��a�����L���b��xd!I��x2R�i7������-���n�5
����{4��RGj@�6�����C�"���b6w�*L��ng0����E�3DRp\6���'J@��n��t5Z�cQ���7�2�:�+if�vE�o�=�����o& �Y�������3�Z^�w��`�5���M�EO�G��/�~��P6�����^��LT��*�7i�r�����W*F�|�O.6�aH!�g����pA��t�D�#��/�����E@�8,�8D���nI6Pax������4W)P����k"A�_�����],����.^'c�r@�
�?��#�L��S3��/C�QF���
%ONwJr}�e�\���DI�*R�
=����/4�9-ebNc��/���wr(��``�F&��Y�5�M�����`F��9P%����I���_�+�N����W��0��������e_�W�u/����vD0����t���B���'���fg��lt�h���}=�Q���Kg:�?���G���]5^8H\���K�������"�]2�G�>,��P��������V4LC6&�T��v(-u�e��������~��<�\�n7?�qc]�3;R�X�=���^�n����z2��
��
���V�������@:]v�kM��<�>�o�uG`��i��#�w�U�:�#a]��5H^���V^7WMw�j-��z�S�����oL�l<D����(���9lx���]���~]���%~��|]4�k8C��K4�X�����Y�� 9���q�z�A%���^+�8���Sc�k&��Rn��$k��]H�5��k����ZS��&�f���r���d�R�u��f�u:�S6R��P0%#gY@
���+�r�eK`�:b�5c���:����I5&C���C�)�0V"�gQ>�|ui�!�UQ	@��$�Q�6J���M��b�xv,TG���.T���B��h�����o�	��]��*��_S]��}K5��(@���Z�*�=���O�*j�l5�]�b�y��q'Z�	y��a%#�TR����$O�m�ZI�*k�p�L�8D�A�{%i.l-t��/��tl��������������y6��L+L����N�&E���C�i����Sk�m�5C�Z��zUf����	u,�ENM��r��sh8l��h[ee�[��������*�g������Nqm�t��W|� %���s����rq���������|���I�U]@�|#^~evI�x��]P5QZDV����l2�	�B��V6YN�x|d}D�s�l�l�
_������OuG��[��X�k��s^�P�pIb�B�t~��@u�&�+��)����Rh���>���Le1g�}X���w�h�Hm���S�@��Z��+�����k�k]��E@*S��%u�P�e�I�EV>)�y'��T��{��|�R�b��v����������N�/����7<�E����	���N�����]"_�}�z1��<�'����F�>_�WG������A�/��Xv���C����u�I������g��E9�5�����-%�����kVw�cu�K���/���j���
�y�n��o�S�u��&�.��J��4 ��u��������Z���� �o��*��Ic���/�Ml�������^�u��k�O���'��-A[����^|kK9�2��H1�B�V�������n�\_r�Ki#��R.�mn��ku�@vn�R�U�rL%�"����8
�{,un�������_(��j���`��c���K��'����dB�`�e�V1������?Q8{�*!Q��������/�>V������j� O�����	�Q��T��x�	�����������DbQ��\JG>��������Kz1}�aE�|�#���t�58^�mb�+��Mx]e���mb�Q�te�v������)����'c�-�/9�>����:�`C��f�I�qh'�;����<�@'���!(Q�!���M������4-C4�n�m���C���&"`��-������x<�a2��L����l��h-�T���������M-������}f (�K^�����W,o+�,���I�����mhyK%YN���r����e6�B(QJP"a���;A��K��
'VC��8Ve��8�dk��5�6^7�,R:��*ek��6fi��I����6�����E1��Q%�C�+fQ�yq�D�5A�uD�5'��!z�Q�����5]��."��S��3�N$|:������M���oivY�h�pI&�N�"������"�������/���"�VJDv�A��x����m�]@6�\�
�>�������?���b����4��x��C���'6R�[���HJ.6������&��b�p���2D���U7��������v�%&��A����8!bt��|?�'����/SS�:t�;2
���_&�����w��_$����FG��
6S�0�q6$9�,Bsxm��l�8;����4M�T�}�'3�.�� #���/Y���/^�'�i�aW#� ����������rk�02-4��$*{su~��}a?��@�����2\,�v�d����V:��t���	xF����Vb��L�W�P��A?�=MH	�@$#��+���X��j:$G���Q�Y�����q�OV����������.�	q�G��D������9(�X�a�F�����4�V�G�u^S��ea����p��<��xg.�6�#��"I�����uU�7K��C�e��on&��GD���}i;>�Y��Br
}��� ��
C,�S���5���H�Gf��\�~��<����s7u��Q]8�+�����X��%�0)-�0=�:9��h���i�]Y�[cP���+���q��#�V�\�����z��g�00���y��s��:N=�����S��r��g�:rf��9�A���N��+���b$�X�4?�m���=�tQ������R.y�m=�9�$�����S�D�x���U�Y�>7��n����%��L��#�C�:���Y�lV��3e�y���W5Ld�R��|o���9�}����������){@�+�&D�M�a�O�Q(8��G����$�~Y�c<aO�@8MG�6fr���b\�I�c�t8{�1��Y����q���P:�y�Y"(��&�G	���wx��1J��%�����:x��"<}H�@��r
h+���9Y�r���QG[A�<��#��aA�F����H������������
��g���?�KB(o;���!����g�	�6�\(�U_�,$�v���YS��|a�G�:jqCD���0��(���%3d�k��<��*�"�� ������n�������r
�H��@ �O�����4���p��������Z���,���ln���]6^�n[}��;x�Cq[�����M=3X`Kl��/��<��?B^��46�s#-��:��u�u�m���S@ni��m������+�;{�J�ge~48-���S��.�?E�y0E$�w������_����YbhcY�%�q��q�E
�������V��K����~����Dq���n�*wim�G��U�J?3������6#g�r6��<"�~6�������OG��~K�JH���D��:
�}$��/Q�z��g\���}�6��`��n��%���VI���H1A�<���p�x3Pe�F3�?��g#{q-�C�Y�P)(�9��z��M�1��$��@*��z��C�7���u������w;�b�"�M��z��Gi���s�N5A����7���C�e�~���b����[Ag}dkz�W�Z\�r �e{�':eE�<���~���r�X�5��d�������9��v6�=���j���9
����`r���(��GrS,��UX8$�,�_�V�A��C�[,SxE�?F�?�Gx!�s�<aX���h�
A:	I��EX������eb�3��e,Lx?��0�t6 o�#-)��{���������z�;n�B����Rz������A���d�-��`J�t7D����.�"\D�56��e�w�]�������v��ukJ���2��(�����n���Q�QR�q���h�Z�ga�5�$r�H�H�����?
}�(��L+�'^�x��J���wZ���4*��m�z=b������|������W�vI���	��1�n<���%�u��uFsl$R�~k)��N��u���F��Kp��N W�@��A]n�K&��
0��8�8�����*'�j���n��&G�)�����5@�����W"C���S!e  ����o�
D����m�D��c������mhG����-]���d
���Fvj��T�������le��#��<��]X���SUw�#1�������������O`��=����_�
Gq�9u�_5�*.�+��
�p����!MQ�I�:�n�Q�e�6h�|{�.��f�q�f��:�Z>��B�����Z�(x�H�7^6�(������(F&+�,I���d�����������b.���T��V�.�b.�h5{�q�:/h<8F�~Ka��;��wfA��i7�����<�I��^K�f�=j�^5Z@A!:�7W��,�����lbj����H41�����g��&#!'�]�m��<�C�8��4�������\C4�I���c�����S�
@(�2�Jt����pvXL�m1�$1]V�a����cx�����%
��lH�	I�(AD� ��<�����K���DT��iv��Eq��fi5�~��'���%����Dwsd��������%
rT���b��]\x�M����}�����{�y�����;4���1�{U	�8TS�))�^Uf�;=�8%���)q��6�|�S�m��Dv9���>�pt`VL[�,����XE�W��>B���`����2����I����k�l�{�>���U�Jz���x�������Z
8�"l+oj]<����z�QT)��5�b��(�F�`��i�����x�$e�Q���n�H��0�$7sV�fH-���<��(������$�!g�8��4E%>���id��� D��)^�]��i�"���"Z��CPl
r�����pKU��
�5]xWz(@K���qA���j���Qq����al�x����x���Kz�n
�y��9%����4jR�qC'�{��b�$ ���I@�O��H�V���y�~��x�n�������?��![r���5! �)�����zLR�}�eHN/��tb�f[�Gx6{qF9�B�����4We=�y$���;��L�uf����{�����YFC����������{"�R������E�|7�pb��P��(�'����P-��t��.�s12$���*U��s5���xX�%)~�m\������&		���W��\SF`'�S,�J�j;�&�0k�v�r[���L� ����;��aAW�}�8�d
~oa\U����t��E��J�HJ����/�{�ekn`$� ~���`��!�o�.��e*N�X� w`�{h�?Y7�8�V�����x��Gw��J
�Y��t����.i�2�^	����o����2%�=�eW4�sX:��C�
�[/2r7<���m:��)���2$����A���K��,���C>v|�'+@���|���������1����Iy!����`8�'4�~���(�F��h�y<Z�_�n����J��2�-�&A�K
9�e��z��`�
iH��t����nf��a���U�x���E��tF���N��X��M2�@�
����pl���~���'���O��
���%�;7���3~���h���4�����t0����r��������<�S���1����27(����4X��"��q���l���|>[,�S�Y��+������w�a�$�Apx���e��r#�<��t��������s7�Mb6DV��7�M	�g�����bE�����'��Og��E���2'��j�U�����^ZC�(l��BZN<l���SY]�BQ��� �_���g��^�JX����7JN+����=
tSl)Y���Y�1�Pn�������d������]0"A��)�M����A�����p�����s��� C��<Pr������WQ����H.��v�;(�,C�O8k�,?�Fa�2��^`I,��Z�t#%�����\��g�d����bd�m!��n0�������� #q<��d���L��O��������D���C����+2��`�n�"9 ��PDs�^�@0�4�DY�G�dz�;y�O*�9�c���^u������4�%�]@j���}�CT�4������6JJ�W	&�j�
��c0��L�jr�������`r^F��������~�����er+q�O
T�F]�nn��+p��Z��#�����#�_v;2����M �g8�v��PT��3��d&-����c}s��[��I�l�<�^
>��&=y�K�X+������f,�zF�������~+�c�mb�$$����?�c�!����(�n4�:���B��#M�!U��� ��~�Z+O��V���F�k���5���n�����U��>���#�K�]k�����V"&��)���)�!n���e�%���9�.�b�����@k�q�0a���0ci�1i�!Q���n&�voL����K�|����}Zl��,���(/9������	������N��[���]����7��0����6����A�� ����7���zY���&N��H�����Glf��:�����6�kqsM��K"�H���������+�w����W:���!��	P���`���,�x��!&�!���"��<����Ola���\���x�J���H�E���)�=,�s<[�����y���h5���`{��f�\<t�d"j����Cd"��������/HN����h$'��-o�d�".^�@�}����s��+�'���p���}��7p��d�2/��{�Go��o���I���+���$3��1C/����:�:���l#���4������"_�����~�=�9X9��{�:*�VN�"5	�}���@�
���y�_�����> ; $��!X2���[�6�|�e���/Z��h����v��"x��������$x�x@��%Z�P�8_>!�L��>���D���'�`$"���d6<��/����`�ZL�z���;,{�b�_�>����#TX���S���~��o��g���#���� ��92(<���
���'x��/�����=�	-&���`���?��O�~���<C����6``~������4��N�u|4�M�)��|�8���[<���(�����cT����d
4>>>d�u^.y��\g�S�@���/{������MhF:b�/f�����21q�?l�{{!Y����C�49�=�P�;������g�����G�[Q���HA3���	@����!�5p������]������}�)
#30Greg Stark
stark@mit.edu
In reply to: Tom Lane (#11)
Re: Making tab-complete.c easier to maintain

On Thu, Oct 22, 2015 at 10:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

What I would like is to find a way to auto-generate basically this entire
file from gram.y. That would imply going over to something at least
somewhat parser-based, instead of the current way that is more or less
totally ad-hoc. That would be a very good thing though, because the
current way gives wrong answers not-infrequently, even discounting cases
that it's simply not been taught about.

I always assumed the reason we didn't use the bison grammar table to
generate completions was because the grammar is way too general and
there would be way too many spurious completions that in practice
nobody would ever be interested in. I assumed it was an intentional
choice that it was more helpful to complete things we know people
usually want rather than every theoretically possible next token.

If that's not true then maybe I'll poke at this sometime. But I agree
with the other part of this thread that that would be totally
experimental and even if we had a working patch it would be a long
time before the user experience was up to the same level as the
current behaviour. I suspect it would involve sending the partial
query to the server for parsing and asking for feedback on completions
using the grammar parser table and the search_path object resolution
rules in effect.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Thomas Munro (#29)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

New version attached, merging recent changes.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-v8.patch.gzapplication/x-gzip; name=tab-complete-v8.patch.gzDownload
#32Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Alvaro Herrera (#28)
2 attachment(s)
Re: Making tab-complete.c easier to maintain

Hello. How about regular expressions?

I've been thinking of better mechanism for tab-compltion for
these days since I found some bugs in it.

At Fri, 23 Oct 2015 14:50:58 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20151023175058.GA3391@alvherre.pgsql>

Jeff Janes wrote:

For the bigger picture, I don't think we should not apply this patch simply
because there is something even better we might theoretically do at some
point in the future.

Agreed.

Auto-generating from grammer should be the ultimate solution but
I don't think it will be available. But still I found that the
word-splitting-then-match-word-by-word-for-each-matching is
terriblly unmaintainable and poorly capable. So, how about
regular expressions?

I tried to use pg_regex in frontend and found that it is easily
doable. As a proof of the concept, the two patches attached to
this message does that changes.

1. 0001-Allow-regex-module-to-be-used-outside-server.patch

This small change makes pg_regex possible to be used in
frontend.

2. 0002-Replace-previous-matching-rule-with-regexps.patch

Simply replaces existing matching rules almost one-by-one with
regular expression matches.

I made these patches not to change the behavior except inevitable
ones.

We would have far powerful matching capability using regular
expressions and it makes tab-complete.c look simpler. On the
other hand, regular expressions - which are stashed away into new
file by this patch - is a chunk of complexity and (also) error
prone. For all that I think this is better than the current
situation in terms of maintainability and capability.

This should look stupid because it really be replaced stupidly
and of course this can be more sane/effective/maintainable by
refactoring. But before that issue, I'm not confident at all that
this is really a alternative with *gigantic* improvement.

Any opinions?

Having used it a little bit, I do agree with Robert
that it is not a gigantic improvement over the current situation, as the
code it replaces is largely mechanical boilerplate. But I think it is
enough of an improvement that we should go ahead with it.

To me this patch sounds much like 2eafcf68d563df8a1db80a. You could say
that what was replaced was "largely mechanical", but it was so much
easier to make mistakes with the original coding that it's not funny.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Allow-regex-module-to-be-used-outside-server.patchtext/x-patch; charset=us-asciiDownload
>From a594ea41a88f30a0ada67b3c94f2326bb7eb83df Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:03:18 +0900
Subject: [PATCH 1/2] Allow regex module to be used outside server.

To make regular expression available on frontend, enable pg_regex
module and the files included from it to be detached from the features
not available when used outside backend.
---
 src/backend/regex/regc_pg_locale.c |  7 +++++++
 src/backend/regex/regcomp.c        | 12 ++++++++++++
 src/include/regex/regcustom.h      |  6 ++++++
 src/include/utils/pg_locale.h      |  7 +++++--
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index b707b06..a631ac2 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -220,6 +220,13 @@ static const unsigned char pg_char_properties[128] = {
 };
 
 
+/* Get rid of using server-side feature elsewhere of server. */
+#ifdef FRONTEND
+#define lc_ctype_is_c(col) (true)
+#define GetDatabaseEncoding() PG_UTF8
+#define ereport(x,...) exit(1)
+#endif
+
 /*
  * pg_set_regex_collation: set collation for these functions to obey
  *
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index a165b3b..b35ccb4 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -2040,11 +2040,17 @@ rfree(regex_t *re)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rcancelrequested(void)
 {
+#ifndef FRONTEND
 	return InterruptPending && (QueryCancelPending || ProcDiePending);
+#else
+	return 0;
+#endif
 }
 
 /*
@@ -2056,11 +2062,17 @@ rcancelrequested(void)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rstacktoodeep(void)
 {
+#ifndef FRONTEND
 	return stack_is_too_deep();
+#else
+	return 0;
+#endif
 }
 
 #ifdef REG_DEBUG
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index dbb461a..ffc4031 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -29,7 +29,11 @@
  */
 
 /* headers if any */
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
 #include "postgres.h"
+#endif
 
 #include <ctype.h>
 #include <limits.h>
@@ -53,7 +57,9 @@
 #define MALLOC(n)		malloc(n)
 #define FREE(p)			free(VS(p))
 #define REALLOC(p,n)	realloc(VS(p),n)
+#ifndef assert
 #define assert(x)		Assert(x)
+#endif
 
 /* internal character type and related */
 typedef pg_wchar chr;			/* the type itself */
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 8e91033..d71ec07 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,14 +17,16 @@
 #include <xlocale.h>
 #endif
 
+/* Don't look GUCs elsewhere of server */
+#ifndef FRONTEND
 #include "utils/guc.h"
 
-
 /* GUC settings */
 extern char *locale_messages;
 extern char *locale_monetary;
 extern char *locale_numeric;
 extern char *locale_time;
+#endif
 
 /* lc_time localization cache */
 extern char *localized_abbrev_days[];
@@ -32,7 +34,7 @@ extern char *localized_full_days[];
 extern char *localized_abbrev_months[];
 extern char *localized_full_months[];
 
-
+#ifndef FRONTEND
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
@@ -41,6 +43,7 @@ extern bool check_locale_numeric(char **newval, void **extra, GucSource source);
 extern void assign_locale_numeric(const char *newval, void *extra);
 extern bool check_locale_time(char **newval, void **extra, GucSource source);
 extern void assign_locale_time(const char *newval, void *extra);
+#endif
 
 extern bool check_locale(int category, const char *locale, char **canonname);
 extern char *pg_perm_setlocale(int category, const char *locale);
-- 
1.8.3.1

0002-Replace-previous-matching-rule-with-regexps.patchtext/x-patch; charset=us-asciiDownload
>From 76d78dc33452294f9e536ffcf49babb58f9c7956 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:18:18 +0900
Subject: [PATCH 2/2] Replace previous matching rule with regexps.

This patch simply replaces previous matching rule with regular
expressions.
---
 src/bin/psql/Makefile          |   15 +-
 src/bin/psql/tab-complete-re.h | 1160 +++++++++++++++++++++++
 src/bin/psql/tab-complete.c    | 2017 ++++++++++++----------------------------
 3 files changed, 1775 insertions(+), 1417 deletions(-)
 create mode 100644 src/bin/psql/tab-complete-re.h

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..290e7b8 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -23,12 +23,23 @@ override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/p
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
+	sql_help.o regcomp.o regexec.o wstrncmp.o \
 	$(WIN32RES)
 
-
+CFLAGS+= -DFRONTEND
 all: psql
 
+
+regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c: % :$(top_srcdir)/src/backend/regex/%
+	rm -f $@ && $(LN_S) $< .
+
+wstrncmp.c: % : $(top_srcdir)/src/backend/utils/mb/%
+	rm -f $@ && $(LN_S) $< .
+
+regcomp.o regexec.o:regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c wstrncmp.c
+
+tab-complete.o: tab-complete.c tab-complete-re.h
+
 psql: $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
diff --git a/src/bin/psql/tab-complete-re.h b/src/bin/psql/tab-complete-re.h
new file mode 100644
index 0000000..e0ba4c6
--- /dev/null
+++ b/src/bin/psql/tab-complete-re.h
@@ -0,0 +1,1160 @@
+typedef enum
+{
+	RE_none,
+	RE_CREATE,
+	RE_DROP,
+	RE_ALTER_TABLE,
+	RE_ALTER,
+	RE_ALL_IN_TABLESPACE_id,
+	RE_ALL_IN_TABLESPACE_id_OWNED_BY,
+	RE_ALTER_AGGREGATEoFUNCTION,
+	RE_ALTER_AGGREGATEoFUNCTION__,
+	RE_ALTER_AGGREGATEoFUNCTION_,
+	RE_ALTER_SCHEMA_id,
+	RE_ALTER_COLLATION_id,
+	RE_ALTER_CONVERSION_id,
+	RE_ALTER_DATABASE_id,
+	RE_ALTER_EVENT_TRIGGER,
+	RE_ALTER_EVENT_TRIGGER_id,
+	RE_ALTER_EVENT_TRIGGER_id_ENABLE,
+	RE_ALTER_EXTENSION_id,
+	RE_ALTER_FOREIGN,
+	RE_ALTER_FOREIGN_DATA_WRAPPER_id,
+	RE_ALTER_FOREIGN_TABLE_id,
+	RE_ALTER_INDEX,
+	RE_ALTER_INDEX_id,
+	RE_ALTER_INDEX_id_SET,
+	RE_ALTER_INDEX_id_RESET,
+	RE_ALTER_INDEX_id_SET_,
+	RE_ALTER_INDEX_id_RESET_,
+	RE_ALTER_LANGUAGE_id,
+	RE_ALTER_LARGE_OBJECT_oid,
+	RE_ALTER_MATERIALIZED_VIEW,
+	RE_ALTER_USERoROLE_id,
+	RE_ALTER_USERoROLE_id_WITH,
+	RE_ALTER_USERoROLE_id_ENCoUNENC,
+	RE_ALTER_DEFAULT_PRIVILEGES,
+	RE_ALTER_DEFAULT_PRIVILEGES_FOR,
+	RE_ALTER_DEFAULT_PRIVILEGES_FORROLEoINSCHEMA_id,
+	RE_ALTER_DOMAIN_id,
+	RE_ALTER_DOMAIN_id_DROP,
+	RE_ALTER_DOMAIN_sth_DROPoRENAMEoVALIDATE_CONSTRAINT,
+	RE_ALTER_DOMAIN_id_RENAME,
+	RE_ALTER_DOMAIN_id_RENAME_CONSTRAINT,
+	RE_ALTER_DOMAIN_id_SET,
+	RE_ALTER_SEQUENCE_id,
+	RE_ALTER_SEQUENCE_id_NO,
+	RE_ALTER_SERVER_id,
+	RE_ALTER_SYSTEM,
+	RE_ALTER_SYSTEM_SEToRESET,
+	RE_ALTER_VIEW_id,
+	RE_ALTER_MVIEW_id,
+	RE_ALTER_POLICY_id,
+	RE_ALTER_POLICY_id_ON,
+	RE_ALTER_POLICY_id_ON_table,
+	RE_ALTER_POLICY_id_ON_table_TO,
+	RE_ALTER_POLICY_id_ON_table_USING,
+	RE_ALTER_POLICY_id_ON_table_WITH_CHECK,
+	RE_ALTER_RULE_id,
+	RE_ALTER_RULE_id_ON,
+	RE_ALTER_RULE_id_ON_id,
+	RE_ALTER_TRIGGER_id,
+	RE_ALTER_TRIGGER_id_ON,
+	RE_ALTER_TRIGGER_id_ON_id,
+	RE_ALTER_TABLE_id,
+	RE_ALTER_TABLE_id_ENABLE,
+	RE_ALTER_TABLE_id_ENABLE_REPLICAoALWAYS,
+	RE_ALTER_TABLE_id_ENABLE_RULE,
+	RE_ALTER_TABLE_id_ENABLE_kwd_RULE,
+	RE_ALTER_TABLE_id_ENABLE_TRIGGER,
+	RE_ALTER_TABLE_id_ENABLE_kwd_TRIGGER,
+	RE_ALTER_TABLE_id_INHERIT,
+	RE_ALTER_TABLE_id_NOINHERIT,
+	RE_ALTER_TABLE_id_DISABLE,
+	RE_ALTER_TABLE_id_DISABLE_RULE,
+	RE_ALTER_TABLE_id_DISABLE_TRIGGER,
+	RE_ALTER_TABLE_id_DISABLE_ROW_LEVEL_SECURITY,
+	RE_ALTER_TABLE_id_ALTER,
+	RE_ALTER_TABLE_id_RENAME,
+	RE_ALTER_TABLE_id_ALTERoRENAME_COLUMN,
+	RE_ALTER_TABLE_id_RENAME_negCONSTRAINToTO,
+	RE_ALTER_TABLE_id_RENAME_COLUMNoCONSTRAINT_negTO,
+	RE_ALTER_TABLE_id_ALToDROPoRENoVALIDATE_CONSTR,
+	RE_ALTER_TABLE_id_RENAMEoRENCOLoRENCONST_id,
+	RE_ALTER_TABLE_id_DROP,
+	RE_ALTER_TABLE_id_DROP_COLUMN,
+	RE_ALTER_TABLE_id_ALTER_oCOLUMN_id,
+	RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET,
+	RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_,
+	RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_STORAGE,
+	RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_DROP,
+	RE_ALTER_TABLE_id_CLUSTER,
+	RE_ALTER_TABLE_id_CLUSTER_ON,
+	RE_ALTER_TABLE_id_SET,
+	RE_ALTER_TABLE_id_SET_TABLESPACE,
+	RE_ALTER_TABLE_id_SET_WITHOUT,
+	RE_ALTER_TABLE_id_RESET,
+	RE_ALTER_TABLE_id_RESET_,
+	RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING_INDEX,
+	RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING,
+	RE_ALTER_TABLE_id_REPLICA_IDENTITY,
+	RE_ALTER_TABLE_id_REPLICA,
+	RE_ALTER_TABLESPACE_id,
+	RE_ALTER_TABLESPACE_id_SEToRESET,
+	RE_ALTER_TABLESPACE_id_SEToRESET_,
+	RE_ALTER_TEXT_SEARCH,
+	RE_ALTER_TEXT_SEARCH_TEMPLATEoPARSER_id,
+	RE_ALTER_TEXT_SEARCH_DICTIONARY_id,
+	RE_ALTER_TEXT_SEARCH_CONFIGURATION_id,
+	RE_ALTER_TYPE_id,
+	RE_ALTER_TYPE_id_ADD,
+	RE_ALTER_TYPE_id_RENAME,
+	RE_ALTER_TYPE_id_RENAME_ATTRIBUTE_id,
+	RE_ALTER_TYPE_id_ALToDROPoREN_ATTRIBUTE,
+	RE_ALTER_TYPE_id_ALTER_ATTRIBUTE_id,
+	RE_ALTER_GROUP_id,
+	RE_ALTER_GROUP_id_ADDoDROP,
+	RE_ALTER_GROUP_id_ADDoDROP_USER,
+	RE_BEGINoENDoABORT,
+	RE_COMMIT,
+	RE_RELEASE,
+	RE_ROLLBACK,
+	RE_CLUSTER,
+	RE_CLUSTER_VERBOSE,
+	RE_CLUSTER_negONoVERBOSE,
+	RE_CLUSTER_VERBOSE_id,
+	RE_CLUSTER_id_USING,
+	RE_CLUSTER_VERBOSE_id_USING,
+	RE_COMMENT,
+	RE_COMMENT_ON,
+	RE_COMMENT_ON_FOREIGN,
+	RE_COMMENT_ON_TEXT_SEARCH,
+	RE_COMMENT_ON_CONSTRAINT,
+	RE_COMMENT_ON_CONSTRAINT_id,
+	RE_COMMENT_ON_CONSTRAINT_id_ON,
+	RE_COMMENT_ON_MATERIALIZED_VIEW,
+	RE_COMMENT_ON_EVENT_TRIGGER,
+	RE_COMMENT_ON_any_negIS,
+	RE_COPY_oBINARY,
+	RE_COPY_oBINARY_id,
+	RE_COPY_oBINARY_id_FROMoTO,
+	RE_COPY_oBINARY_id_FROMoTO_any,
+	RE_COPY_any_FROMoTO_any_CSV,
+	RE_CREATE_DATABASE_id,
+	RE_CREATE_DATABASE_id_TEMPLATE,
+	RE_CREATE_EXTENSION,
+	RE_CREATE_EXTENSION_id,
+	RE_CREATE_EXTENSION_id_VERSION,
+	RE_CREATE_FOREIGN,
+	RE_CREATE_FOREIGN_DATA_WRAPPER_id,
+	RE_CREATE_UNIQUE,
+	RE_CREATE_oUNIQUE_INDEX,
+	RE_INDEX_oname_CONCURRENTLY_ON,
+	RE_INDEX_oname_CONCURRENTLY,
+	RE_CREATE_oUNIQUE_INDEX_name,
+	RE_INDEX_oname_CONCURRENTLY_ON_id,
+	RE_INDEX_oname_CONCURRENTLY_ON_id_,
+	RE_ON_id_USING_kwd_,
+	RE_ON_id_USING,
+	RE_CREATE_INDEX_any_ON_id_USING_kwd,
+	RE_CREATE_POLICY_id,
+	RE_CREATE_POLICY_id_ON,
+	RE_CREATE_POLICY_id_ON_id,
+	RE_CREATE_POLICY_id_ON_id_FOR,
+	RE_CREATE_POLICY_id_ON_id_FOR_INSERT,
+	RE_CREATE_POLICY_id_ON_id_FOR_UPDATE,
+	RE_CREATE_POLICY_id_ON_id_FOR_SELoDEL,
+	RE_CREATE_POLICY_id_ON_id_TO,
+	RE_CREATE_POLICY_id_ON_id_USING,
+	RE_CREATE_RULE_id,
+	RE_CREATE_RULE_id_AS,
+	RE_CREATE_RULE_id_AS_ON,
+	RE_CREATE_RULE_id_AS_ON_event,
+	RE_CREATE_RULE_id_AS_ON_event_TO,
+	RE_CREATE_oTEMP_SEQUENCE_oINE_id,
+	RE_CREATE_oTEMP_SEQUENCE_oINE_id_any_NO,
+	RE_CREATE_SERVER_id,
+	RE_CREATE_TEMP,
+	RE_CREATE_UNLOGGED,
+	RE_CREATE_TABLESPACE_id,
+	RE_CREATE_TABLESPACE_id_OWNER_id,
+	RE_CREATE_TEXT_SEARCH,
+	RE_CREATE_TEXT_SEARCH_CONFIGURATION_id,
+	RE_CREATE_TRIGGER_id,
+	RE_CREATE_TRIGGER_id_BEFoAFT,
+	RE_CREATE_TRIGGER_id_INSTEAD_OF,
+	RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events,
+	RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events_ON,
+	RE_CREATE_TRIGGER_id_any_EXECUTE,
+	RE_CREATE_USERoROLEoGROUP_id,
+	RE_CREATE_USERoROLEoGROUP_id_WITH,
+	RE_CREATE_USERoROLEoGROUP_id_ENCoUNENC,
+	RE_CREATE_USERoROLEoGROUP_id_IN,
+	RE_CREATE_VIEW_id,
+	RE_CREATE_VIEW_id_AS,
+	RE_CREATE_MATERIALIZED,
+	RE_CREATE_MATVIEW_id,
+	RE_CREATE_MATVIEW_id_any_AS,
+	RE_CREATE_EVENT,
+	RE_CREATE_EVENT_TRIGGER_id,
+	RE_CREATE_EVENT_TRIGGER_id_ON,
+	RE_DECLARE_id,
+	RE_DECLARE_id_oBIN_oINSEN_oNOSCR_CURSOR,
+	RE_DELETE,
+	RE_DELETE_FROM,
+	RE_DELETE_FROM_id,
+	RE_DISCARD,
+	RE_DO,
+	RE_DROP_AGGREGATE_id,
+	RE_DROP_obj_1,
+	RE_DROP_obj_2,
+	RE_DROP_obj_3,
+	RE_DROP_FUNCTION_id,
+	RE_DROP_FOREIGN,
+	RE_DROP_MATERIALIZED,
+	RE_DROP_MATERIALIZED_VIEW,
+	RE_DROP_AGGoFUNC_id_,
+	RE_DROP_OWNED,
+	RE_DROP_OWNED_BY,
+	RE_DROP_TEXT_SEARARCH,
+	RE_DROP_TRIGGER_id,
+	RE_DROP_TRIGGER_id_ON,
+	RE_DROP_TRIGGER_id_ON_id,
+	RE_DROP_EVENT,
+	RE_DROP_EVENT_TRIGGER,
+	RE_DROP_POLICY,
+	RE_DROP_POLICY_id,
+	RE_DROP_POLICY_id_ON,
+	RE_DROP_RULE_id,
+	RE_DROP_RULE_id_ON,
+	RE_DROP_RULE_id_ON_id,
+	RE_EXECUTE,
+	RE_EXPLAIN,
+	RE_EXPLAIN_ANALYZE,
+	RE_EXPLAIN_oANALYZE_VERBOSE,
+	RE_FETCHoMOVE,
+	RE_FETCHoMOVE_id,
+	RE_FETCHoMOVE_any_any,
+	RE_negCREATE_FOREIGN_DATA_WRAPPER,
+	RE_negCREATE_FOREIGN_TABLE,
+	RE_FOREIGN_SERVER,
+	RE_GRANToREVOKE,
+	RE_GRANToREVOKE_kwd,
+	RE_GRANToREVOKE_id,
+	RE_GRANToREVOKE_kwd_ON,
+	RE_GRANToREVOKE_kwd_ON_ALL,
+	RE_GRANToREVOKE_kwd_ON_FOREIGN,
+	RE_GRANToREVOKE_kwd_ON_any,
+	RE_GRANToREVOKE_kwd_ON_ALL_any_IN_SCHEMA_id,
+	RE_GRANToREVOKE_kwd_ON_FDW_id,
+	RE_GRANToREVOKE_kwd_ON_FOREIGN_SERVER_id,
+	RE_GRANToREVOKE_any_TO,
+	RE_GRANToREVOKE_kwd_ON_kwd_id,
+	RE_GRANToREVOKE_kwd_TO,
+	RE_FROM_any_GROUP,
+	RE_IMPORT,
+	RE_IMPORT_FOREIGN,
+	RE_INSERT,
+	RE_INSERT_INTO,
+	RE_INSERT_INTO_id_,
+	RE_INSERT_INTO_id,
+	RE_INSERT_INTO_id__,
+	RE_negDEFAULT_VALUES,
+	RE_LOCK,
+	RE_LOCK_TABLE,
+	RE_LOCK_oTABLE_id,
+	RE_LOCK_oTABLE_id_IN,
+	RE_NOTIFY,
+	RE_OPTIONS,
+	RE_OWNER_TO,
+	RE_FROM_any_ORDER,
+	RE_FROM_any_ORDER_BY,
+	RE_PREPARE_any_AS,
+	RE_REASSIGN,
+	RE_REASSIGN_OWNED,
+	RE_REASSIGN_OWNED_BY,
+	RE_REASSIGN_OWNED_BY_id,
+	RE_REASSIGN_OWNED_BY_id_TO,
+	RE_REFRESH,
+	RE_REFRESH_MATERIALIZED,
+	RE_REFRESH_MATERIALIZED_VIEW,
+	RE_REFRESH_MATERIALIZED_VIEW_CONC,
+	RE_REFRESH_MATERIALIZED_VIEW_id,
+	RE_REFRESH_MATERIALIZED_VIEW_CONC_id,
+	RE_REFRESH_MATERIALIZED_VIEW_id_WITH,
+	RE_REFRESH_MATERIALIZED_VIEW_CONC_id_WITH,
+	RE_REFRESH_MATERIALIZED_VIEW_id_WITH_NO,
+	RE_REINDEX,
+	RE_REINDEX_kwd,
+	RE_SECURITY,
+	RE_SECURITY_LABEL,
+	RE_SECURITY_LABEL_FOR_id,
+	RE_SECURITY_LABEL_oFORname_ON,
+	RE_SECURITY_LABEL_ON_kwd_id,
+	RE_SEToRESET,
+	RE_SHOW,
+	RE_SET_TRANSACTIONs,
+	RE_SET_TRANSACTIONs_ISOLATION,
+	RE_SET_TRANSACTIONs_ISOLATION_LEVEL,
+	RE_SET_TRANSACTIONs_ISOLATION_LEVEL_READ,
+	RE_SET_TRANSACTIONs_ISOLATION_LEVEL_REPEATABLE,
+	RE_SET_TRANSACTIONs_READ,
+	RE_SET_CONSTRAINTS,
+	RE_SET_CONSTRAINTS_id,
+	RE_SET_ROLE,
+	RE_SET_SESSION,
+	RE_SET_SESSION_AUTH,
+	RE_RESET_SESSION,
+	RE_SET_var,
+	RE_SET_var_TOoEQ,
+	RE_START,
+	RE_TABLE,
+	RE_any_TABLESAMPLE,
+	RE_any_TABLESAMPLE_id,
+	RE_TRUNCATE,
+	RE_UNLISTEN,
+	RE_any_UPDATE,
+	RE_any_UPDATE_id,
+	RE_any_id_SET,
+	RE_any_UPDATE_id_SET_id,
+	RE_CRToALToDRP_USER_MAPPING,
+	RE_CREATE_USER_MAPPING_FOR,
+	RE_ALToDRP_USER_MAPPING_FOR,
+	RE_CRToALToDRP_USER_MAPPING_FOR_id,
+	RE_VACUUM,
+	RE_VACUUM_FULLoFREEZE,
+	RE_VACUUM_FULLoFREEZE_ANALYZE,
+	RE_VACUUM_FULLoFREEZE_VERBOSE,
+	RE_VACUUM_VERBOSE,
+	RE_VACUUM_ANALYZE,
+	RE_any_ANLVRBoVRBANL,
+	RE_WITH,
+	RE_any_ANALYZE,
+	RE_any_id_WHERE,
+	RE_COPY_id_FROM,
+	RE_any_JOIN,
+
+/* metacommands */
+	RE_meta_question,
+	RE_meta_CONNECT,
+	RE_meta_CONNECT_word,
+	RE_meta_da,
+	RE_meta_db,
+	RE_meta_dD,
+	RE_meta_des,
+	RE_meta_deu,
+	RE_meta_dew,
+	RE_meta_df,
+	RE_meta_dFd,
+	RE_meta_dFp,
+	RE_meta_dFt,
+	RE_meta_dF,
+	RE_meta_di,
+	RE_meta_dL,
+	RE_meta_dn,
+	RE_meta_dpOz,
+	RE_meta_ds,
+	RE_meta_dt,
+	RE_meta_dT,
+	RE_meta_duOdg,
+	RE_meta_dv,
+	RE_meta_dx,
+	RE_meta_dm,
+	RE_meta_dE,
+	RE_meta_dy,
+	RE_meta_d,
+	RE_meta_ef,
+	RE_meta_ev,
+	RE_meta_encoding,
+	RE_meta_h,
+	RE_meta_password,
+	RE_meta_pset,
+	RE_meta_pset_id,
+	RE_meta_unset,
+	RE_meta_set,
+	RE_meta_set_id,
+	RE_meta_sf,
+	RE_meta_sv,
+	RE_meta_FILECMDS,
+	RE_AnySingleWord,
+	RE_NUM
+} patterns;
+
+
+#define WB  "[\\t\\n@$><=;|&{\\(\\) ]"				/* WORD BREAKS */
+#define NWB "[^\\t\\n@$><=;|&{\\(\\) ]"				/* ^WORD BREAKS */
+#define PB  "^(?:.*;)?\\s*"			/* bind to the beginning of the line */
+#define PM  "^(?:.*"NWB WB")?\\s*"					/* floating head */
+#define ID  NWB"+"									/* identifier */
+#define KWD "\\w+"									/* any keywords */
+typedef struct _comp_def
+{
+	patterns pat_id;		/* for sanity check */
+	char *pat;				/* pattern */
+	int cflags;
+} comp_def;
+
+static comp_def redef[] =
+{
+	{RE_none,
+	 PB"\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE,
+	 PM"CREATE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP,
+	 PB"DROP\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_TABLE,
+	 PM"ALTER\\s+TABLE\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER,
+	 PB"ALTER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALL_IN_TABLESPACE_id,
+	 PM"ALL\\s+IN\\s+TABLESPACE\\s+"ID"\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALL_IN_TABLESPACE_id_OWNED_BY,
+	 PM"ALL\\s+IN\\s+TABLESPACE\\s+"ID"\\s+OWNED\\s+BY\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_AGGREGATEoFUNCTION,
+	 PM"ALTER\\s+(AGGREGATE|FUNCTION)\\s+"ID"\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_AGGREGATEoFUNCTION__,
+	 PM"ALTER\\s+(?:AGGREGATE|FUNCTION)\\s\\w+\\s*\\(.*\\)\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_AGGREGATEoFUNCTION_,
+	 PM"ALTER\\s+(?:AGGREGATE|FUNCTION)\\s+("ID")\\s*\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_SCHEMA_id,
+	 PM"ALTER\\s+SCHEMA\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_COLLATION_id,
+	 PM"ALTER\\s+COLLATION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_CONVERSION_id,
+	 PM"ALTER\\s+CONVERSION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DATABASE_id,
+	 PM"ALTER\\s+DATABASE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_EVENT_TRIGGER,
+	 PM"ALTER\\s+EVENT\\s+TRIGGER\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_EVENT_TRIGGER_id,
+	 PM"ALTER\\s+EVENT\\s+TRIGGER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_EVENT_TRIGGER_id_ENABLE,
+	 PM"ALTER\\s+EVENT\\s+TRIGGER\\s+"ID"\\s+ENABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_EXTENSION_id,
+	 PM"ALTER\\s+EXTENSION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_FOREIGN,
+	 PM"ALTER\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_FOREIGN_DATA_WRAPPER_id,
+	 PM"ALTER\\s+FOREIGN\\s+DATA\\s+WRAPPER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_FOREIGN_TABLE_id,
+	 PM"ALTER\\s+FOREIGN\\s+TABLE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX,
+	 PM"ALTER\\s+INDEX\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX_id,
+	 PM"ALTER\\s+INDEX\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX_id_SET,
+	 PM"ALTER\\s+INDEX\\s+"ID"\\s+SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX_id_RESET,
+	 PM"ALTER\\s+INDEX\\s+"ID"\\s+RESET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX_id_SET_,
+	 PM"ALTER\\s+INDEX\\s+"ID"\\s+SET\\s*\\(\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_INDEX_id_RESET_,
+	 PM"ALTER\\s+INDEX\\s+"ID"\\s+RESET\\s*\\(\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_LANGUAGE_id,
+	 PM"ALTER\\s+LANGUAGE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_LARGE_OBJECT_oid,
+	 PM"ALTER\\s+LARGE\\s+OBJECT\\s+\\w+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_MATERIALIZED_VIEW,
+	 PM"ALTER\\s+MATERIALIZED\\s+VIEW\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_USERoROLE_id,
+	 PM"ALTER\\s+(?!USER\\s+MAPPING)(ROLE|USER)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_USERoROLE_id_WITH,
+	 PM"ALTER\\s+(?!USER\\s+MAPPING)(ROLE|USER)\\s+"ID"\\s+WITH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_USERoROLE_id_ENCoUNENC,
+	 PM"ALTER\\s+(?!USER\\s+MAPPING)(ROLE|USER)\\s+"ID"\\s+(UN)?ENCRYPTED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DEFAULT_PRIVILEGES,
+	 PM"ALTER\\s+DEFAULT\\s+PRIVILEGES\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DEFAULT_PRIVILEGES_FOR,
+	 PM"ALTER\\s+DEFAULT\\s+PRIVILEGES\\s+FOR\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DEFAULT_PRIVILEGES_FORROLEoINSCHEMA_id,
+	 PM"ALTER\\s+DEFAULT\\s+PRIVILEGES\\s+(?:FOR\\s+ROLE|IN\\s+SCHEMA)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_id,
+	 PM"ALTER\\s+DOMAIN\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_id_DROP,
+	 PM"ALTER\\s+DOMAIN\\s+"ID"\\s+DROP\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_sth_DROPoRENAMEoVALIDATE_CONSTRAINT,
+	 PM"ALTER\\s+DOMAIN\\s+("ID")\\s+(?:DROP|RENAME|VALIDATE)\\s+CONSTRAINT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_id_RENAME,
+	 PM"ALTER\\s+DOMAIN\\s+"ID"\\s+RENAME\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_id_RENAME_CONSTRAINT,
+	 PM"ALTER\\s+DOMAIN\\s+"ID"\\s+RENAME\\s+CONSTRAINT\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_DOMAIN_id_SET,
+	 PM"ALTER\\s+DOMAIN\\s+"ID"\\s+SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_SEQUENCE_id,
+	 PM"ALTER\\s+SEQUENCE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_SEQUENCE_id_NO,
+	 PM"ALTER\\s+SEQUENCE\\s+"ID"\\s+NO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALTER_SERVER_id,
+	 PM"ALTER\\s+SERVER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_SYSTEM,
+	 PM"ALTER\\s+SYSTEM\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_SYSTEM_SEToRESET,
+	 PM"ALTER\\s+SYSTEM\\s+(?:RE)?SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_VIEW_id,
+	 PM"ALTER\\s+VIEW\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_MVIEW_id,
+	 PM"ALTER\\s+MATERIALIZED\\s+VIEW\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id_ON,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id_ON_table,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id_ON_table_TO,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+TO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id_ON_table_USING,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+USING\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_POLICY_id_ON_table_WITH_CHECK,
+	 PM"ALTER\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+WITH\\s+CHECK\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_RULE_id,
+	 PM"ALTER\\s+RULE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_RULE_id_ON,
+	 PM"ALTER\\s+RULE\\s+("ID")\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_RULE_id_ON_id,
+	 PM"ALTER\\s+RULE\\s+"ID"\\s+ON\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TRIGGER_id,
+	 PM"ALTER\\s+TRIGGER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TRIGGER_id_ON,
+	 PM"ALTER\\s+TRIGGER\\s+("ID")\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TRIGGER_id_ON_id,
+	 PM"ALTER\\s+TRIGGER\\s+("ID")\\s+ON\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE_REPLICAoALWAYS,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+(?:REPLICA|ALWAYS)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE_RULE,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+RULE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE_kwd_RULE,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+\\w+\\s+RULE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE_TRIGGER,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+TRIGGER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ENABLE_kwd_TRIGGER,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ENABLE\\s+\\w+\\s+TRIGGER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_INHERIT,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+INHERIT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_NOINHERIT,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+NO\\s+INHERIT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DISABLE,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+DISABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DISABLE_RULE,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+DISABLE\\s+RULE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DISABLE_TRIGGER,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+DISABLE\\s+TRIGGER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DISABLE_ROW_LEVEL_SECURITY,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+DISABLE\\s+ROW\\s+LEVEL\\s+SECURITY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+ALTER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_RENAME,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+RENAME\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTERoRENAME_COLUMN,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+(?:ALTER|RENAME)\\s+COLUMN\\s+\\w*$", REG_ADVANCED|REG_ICASE},	
+ 	{RE_ALTER_TABLE_id_RENAME_negCONSTRAINToTO,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+RENAME\\s+(?!CONSTRAINT|TO)"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},	
+ 	{RE_ALTER_TABLE_id_RENAME_COLUMNoCONSTRAINT_negTO,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+RENAME\\s+(?:COLUMN|CONSTRAINT)\\s+(?!TO)"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},	
+ 	{RE_ALTER_TABLE_id_ALToDROPoRENoVALIDATE_CONSTR,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+(?:ALTER|DROP|RENAME|VALIDATE)\\s+CONSTRAINT\\s+\\w*$", REG_ADVANCED|REG_ICASE},	
+ 	{RE_ALTER_TABLE_id_RENAMEoRENCOLoRENCONST_id,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+RENAME\\s+(?:COLUMN\\s+|CONSTRAINT\\s+)?"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DROP,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+DROP\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_DROP_COLUMN,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+DROP\\s+COLUMN\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER_oCOLUMN_id,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+ALTER\\s+(?:(?:COLUMN\\s+"ID")|(?!COLUMN\\s+)"ID")\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+ALTER\\s+(?:(?:COLUMN\\s+"ID")|(?!COLUMN\\s+)"ID")\\s+SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+ALTER\\s+(?:(?:COLUMN\\s+"ID")|(?!COLUMN\\s+)"ID")\\s+SET\\s+\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_STORAGE,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+ALTER\\s+(?:(?:COLUMN\\s+"ID")|(?!COLUMN\\s+)"ID")\\s+SET\\s+STORAGE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_DROP,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+ALTER\\s+(?:(?:COLUMN\\s+"ID")|(?!COLUMN\\s+)"ID")\\s+DROP\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_CLUSTER,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+CLUSTER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_CLUSTER_ON,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+CLUSTER\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_SET,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_SET_TABLESPACE,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+SET\\s+TABLESPACE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_SET_WITHOUT,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+SET\\s+WITHOUT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_RESET,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+RESET\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_RESET_,
+	 PM"ALTER\\s+TABLE\\s+"ID"\\s+RESET\\s*\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING_INDEX,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+REPLICA\\s+IDENTITY\\s+USING\\s+INDEX\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+REPLICA\\s+IDENTITY\\s+USING\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_REPLICA_IDENTITY,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+REPLICA\\s+IDENTITY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLE_id_REPLICA,
+	 PM"ALTER\\s+TABLE\\s+("ID")\\s+REPLICA\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLESPACE_id,
+	 PM"ALTER\\s+TABLESPACE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLESPACE_id_SEToRESET,
+	 PM"ALTER\\s+TABLESPACE\\s+"ID"\\s+(?:RE)?SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TABLESPACE_id_SEToRESET_,
+	 PM"ALTER\\s+TABLESPACE\\s+"ID"\\s+(?:RE)?SET\\s*\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TEXT_SEARCH,
+	 PM"ALTER\\s+TEXT\\s+SEARCH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TEXT_SEARCH_TEMPLATEoPARSER_id,
+	 PM"ALTER\\s+TEXT\\s+SEARCH\\s+(?:TEMPLATE|PARSER)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TEXT_SEARCH_DICTIONARY_id,
+	 PM"ALTER\\s+TEXT\\s+SEARCH\\s+DICTIONARY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TEXT_SEARCH_CONFIGURATION_id,
+	 PM"ALTER\\s+TEXT\\s+SEARCH\\s+CONFIGURATION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id,
+	 PM"ALTER\\s+TYPE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id_ADD,
+	 PM"ALTER\\s+TYPE\\s+"ID"\\s+ADD\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id_RENAME,
+	 PM"ALTER\\s+TYPE\\s+"ID"\\s+RENAME\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id_RENAME_ATTRIBUTE_id,
+	 PM"ALTER\\s+TYPE\\s+"ID"\\s+RENAME\\s+ATTRIBUTE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id_ALToDROPoREN_ATTRIBUTE,
+	 PM"ALTER\\s+TYPE\\s+("ID")\\s+(?:ALTER|DROP|RENAME)\\s+ATTRIBUTE\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_TYPE_id_ALTER_ATTRIBUTE_id,
+	 PM"ALTER\\s+TYPE\\s+("ID")\\s+ALTER\\s+ATTRIBUTE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_GROUP_id,
+	 PM"ALTER\\s+GROUP\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_GROUP_id_ADDoDROP,
+	 PM"ALTER\\s+GROUP\\s+"ID"\\s+(?:ADD|DROP)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ALTER_GROUP_id_ADDoDROP_USER,
+	 PM"ALTER\\s+GROUP\\s+"ID"\\s+(?:ADD|DROP)\\s+USER\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_BEGINoENDoABORT,
+	 PM"(?:BEGIN|END|ABORT)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMIT,
+	 PM"COMMIT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_RELEASE,
+	 PM"RELEASE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ROLLBACK,
+	 PM"ROLLBACK\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER,
+	 PM"CLUSTER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER_VERBOSE,
+	 PM"CLUSTER\\s+VERBOSE\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER_negONoVERBOSE,
+	 PM"CLUSTER\\s+(?!ON|VERBOSE)"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER_VERBOSE_id,
+	 PM"CLUSTER\\s+VERBOSE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER_id_USING,
+	 PM"CLUSTER\\s+("ID")\\s+USING\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CLUSTER_VERBOSE_id_USING,
+	 PM"CLUSTER\\s+VERBOSE\\s+("ID")\\s+USING\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT,
+	 PM"COMMENT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON,
+	 PM"COMMENT\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_FOREIGN,
+	 PM"COMMENT\\s+ON\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_TEXT_SEARCH,
+	 PM"COMMENT\\s+ON\\s+TEXT\\s+SEARCH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_CONSTRAINT,
+	 PM"COMMENT\\s+ON\\s+CONSTRAINT\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_CONSTRAINT_id,
+	 PM"COMMENT\\s+ON\\s+CONSTRAINT\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_CONSTRAINT_id_ON,
+	 PM"COMMENT\\s+ON\\s+CONSTRAINT\\s+("ID")\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_MATERIALIZED_VIEW,
+	 PM"COMMENT\\s+ON\\s+MATERIALIZED\\s+VIEW\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_EVENT_TRIGGER,
+	 PM"COMMENT\\s+ON\\s+EVENT\\s+TRIGGER\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COMMENT_ON_any_negIS,
+	 PM"COMMENT\\s+ON\\s+.*\\s+(?!IS)"ID"\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COPY_oBINARY,
+	 PM"(?:COPY\\s+(?:BINARY\\s+)?|\\\\COPY\\s+)[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COPY_oBINARY_id,
+	 PM"(?:COPY\\s+(?:BINARY\\s+)?|\\\\COPY\\s+)"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COPY_oBINARY_id_FROMoTO,
+	 PM"(?:COPY\\s+(?:BINARY\\s+)?|\\\\COPY\\s+)"ID"\\s+(?:FROM|TO)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COPY_oBINARY_id_FROMoTO_any,
+	 PM"(?:COPY\\s+(?:BINARY\\s+)?|\\\\COPY\\s+)"ID"\\s+(?:FROM|TO)\\s+.*\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_COPY_any_FROMoTO_any_CSV,
+	 PM"COPY\\s+.*\\s+(?:FROM|TO).*(?:WITH\\s+)?CSV\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_DATABASE_id,
+	 PM"CREATE\\s+DATABASE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_DATABASE_id_TEMPLATE,
+	 PM"CREATE\\s+DATABASE\\s+"ID"\\s+TEMPLATE\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_EXTENSION,
+	 PM"CREATE\\s+EXTENSION\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_EXTENSION_id,
+	 PM"CREATE\\s+EXTENSION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_EXTENSION_id_VERSION,
+	 PM"CREATE\\s+EXTENSION\\s+("ID")\\s+VERSION\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_FOREIGN,
+	 PM"CREATE\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_FOREIGN_DATA_WRAPPER_id,
+	 PM"CREATE\\s+FOREIGN\\s+DATA\\s+WRAPPER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_UNIQUE,
+	 PM"CREATE\\s+UNIQUE"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_oUNIQUE_INDEX,
+	 PM"CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_INDEX_oname_CONCURRENTLY_ON,
+	 PM"INDEX\\s+(?:"ID"\\s+)?CONCURRENTLY\\s+ON\\s+"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_INDEX_oname_CONCURRENTLY,
+	 PM"INDEX\\s+(?:"ID"\\s+)?CONCURRENTLY\\s+"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_oUNIQUE_INDEX_name,
+	 PM"CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+"ID"\\s+"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_INDEX_oname_CONCURRENTLY_ON_id,
+	 PM"INDEX\\s+(?:"ID"\\s+)?CONCURRENTLY\\s+ON\\s+"ID"\\s+"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_INDEX_oname_CONCURRENTLY_ON_id_,
+	 PM"INDEX\\s+(?:"ID"\\s+)?CONCURRENTLY\\s+ON\\s+"ID"\\s*\\(\\s*"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_ON_id_USING_kwd_,
+	 PM"ON\\s+"ID"\\s+USING\\s+"KWD"\\s*\\(\\s*"NWB"*$", REG_ADVANCED|REG_ICASE},
+	{RE_ON_id_USING,
+	 PM"ON\\s+"ID"\\s+USING\\s+"NWB"*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_INDEX_any_ON_id_USING_kwd,
+	 PB"CREATE\\s+INDEX\\s+(?:.+\\s+)?ON\\s+"ID"\\s+USING\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	
+ 	{RE_CREATE_POLICY_id,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_FOR,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+FOR\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_FOR_INSERT,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+FOR\\s+INSERT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_FOR_UPDATE,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+FOR\\s+UPDATE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_FOR_SELoDEL,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+FOR\\s+(?:SELECT|DELETE)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_TO,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+TO\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_POLICY_id_ON_id_USING,
+	 PM"CREATE\\s+POLICY\\s+"ID"\\s+ON\\s+"ID"\\s+USING\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_RULE_id,
+	 PM"CREATE\\s+RULE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_RULE_id_AS,
+	 PM"CREATE\\s+RULE\\s+"ID"\\s+AS\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_RULE_id_AS_ON,
+	 PM"CREATE\\s+RULE\\s+"ID"\\s+AS\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_RULE_id_AS_ON_event,
+	 PM"CREATE\\s+RULE\\s+"ID"\\s+AS\\s+ON\\s+(?:SELECT|INSERT|UPDATE|DELETE)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_RULE_id_AS_ON_event_TO,
+	 PM"CREATE\\s+RULE\\s+"ID"\\s+AS\\s+ON\\s+(?:SELECT|INSERT|UPDATE|DELETE)\\s+TO\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_oTEMP_SEQUENCE_oINE_id,
+	 PM"CREATE\\s+(?:TEMP(?:ORARY)?\\s+)?SEQUENCE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_oTEMP_SEQUENCE_oINE_id_any_NO,
+	 PM"CREATE\\s+(?:TEMP(?:ORARY)?\\s+)?SEQUENCE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?"ID"\\s+(?:.*\\s+)?NO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+ 	{RE_CREATE_SERVER_id,
+	 PM"CREATE\\s+SERVER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TEMP,
+	 PM"CREATE\\s+TEMP(ORARY)?\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_UNLOGGED,
+	 PM"CREATE\\s+UNLOGGED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TABLESPACE_id,
+	 PM"CREATE\\s+TABLESPACE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TABLESPACE_id_OWNER_id,
+	 PM"CREATE\\s+TABLESPACE\\s+"ID"\\s+OWNER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TEXT_SEARCH,
+	 PM"CREATE\\s+TEXT\\s+SEARCH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TEXT_SEARCH_CONFIGURATION_id,
+	 PM"CREATE\\s+TEXT\\s+SEARCH\\s+CONFIGURATION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id_BEFoAFT,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+(?:BEFORE|AFTER)\\s+(?:"ID"\\s+OR\\s+)*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id_INSTEAD_OF,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+INSTEAD\\s+OF\\s+(?:"ID"\\s+OR\\s+)*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+(?:BEFORE|AFTER|INSTEAD\\s+OF)\\s+"ID"(?:\\s+OR\\s+"ID")*\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events_ON,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+(?:BEFORE|AFTER|INSTEAD\\s+OF)\\s+"ID"(?:\\s+OR\\s+"ID")*\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_TRIGGER_id_any_EXECUTE,
+	 PM"CREATE\\s+(?:CONSTRAINT\\s+)?TRIGGER\\s+"ID"\\s+.*\\s+EXECUTE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_USERoROLEoGROUP_id,
+	 PM"CREATE\\s+(?:USER(?!\\s+MAPPING)|ROLE|GROUP)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_USERoROLEoGROUP_id_WITH,
+	 PM"CREATE\\s+(?:USER(?!\\s+MAPPING)|ROLE|GROUP)\\s+"ID"\\s+WITH\\s+(?:"ID"\\s+)*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_USERoROLEoGROUP_id_ENCoUNENC,
+	 PM"CREATE\\s+(?:USER(?!\\s+MAPPING)|ROLE|GROUP)\\s+"ID"\\s+(?:UN)?ENCRYPTED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_USERoROLEoGROUP_id_IN,
+	 PM"CREATE\\s+(?:USER(?!\\s+MAPPING)|ROLE|GROUP)\\s+"ID"\\s+IN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_VIEW_id,
+	 PM"CREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:TEMP(?:ORARY)?\\s+)?(?:RECURSIVE\\s+)?VIEW\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_VIEW_id_AS,
+	 PM"CREATE\\s+(?:OR\\s+REPLACE\\s+)?(?:TEMP(?:ORARY)?\\s+)?(?:RECURSIVE\\s+)?VIEW\\s+"ID"\\s+AS\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_MATERIALIZED,
+	 PM"CREATE\\s+MATERIALIZED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_MATVIEW_id,
+	 PM"CREATE\\s+MATERIALIZED\\s+VIEW\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_MATVIEW_id_any_AS,
+	 PM"CREATE\\s+MATERIALIZED\\s+VIEW\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?"ID"\\s+(?:.*\\s+)*AS\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_EVENT,
+	 PM"CREATE\\s+EVENT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_EVENT_TRIGGER_id,
+	 PM"CREATE\\s+EVENT\\s+TRIGGER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_EVENT_TRIGGER_id_ON,
+	 PM"CREATE\\s+EVENT\\s+TRIGGER\\s+"ID"\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DECLARE_id,
+	 PM"DECLARE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DECLARE_id_oBIN_oINSEN_oNOSCR_CURSOR,
+	 PM"DECLARE\\s+"ID"\\s+(?:BINARY\\s+)?(?:INSENSITIVE\\s+)?(?:(?:NO\\s+)?SCROLL\\s+)?CURSOR\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DELETE,
+	 PM"DELETE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DELETE_FROM,
+	 PM"DELETE\\s+FROM\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DELETE_FROM_id,
+	 PM"DELETE\\s+FROM\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DISCARD,
+	 PM"DISCARD\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DO,
+	 PM"DO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_AGGREGATE_id,
+	 PB"DROP\\s+AGGREGATE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_obj_1,
+	 PB"DROP\\s+(?:COLLATION|CONVERSION|DOMAIN|EXTENSION|FUNCTION|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_obj_2,
+	 PB"DROP\\s+AGGREGATE\\s*\\([^\\)]*\\)\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_obj_3,
+	 PB"DROP\\s+(?:(?:EVENT\\s+TRIGGER)|(?:FOREIGN\\s+DATA\\s+WRAPPER)|(?:TEXT\\s+SEARCH\\s+(?:COFIGURATION|PARSER|TEMPLATE)))\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_FUNCTION_id,
+	 PB"DROP\\s+FUNCTION\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+
+	{RE_DROP_FOREIGN,
+	 PM"DROP\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_MATERIALIZED,
+	 PM"DROP\\s+MATERIALIZED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_MATERIALIZED_VIEW,
+	 PM"DROP\\s+MATERIALIZED\\s+VIEW\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_AGGoFUNC_id_,
+	 PM"DROP\\s+(?:AGGREATE|FUNCTION)\\s+(?:IF\\s+EXISTS\\s+)?("ID")\\s*\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_OWNED,
+	 PM"DROP\\s+OWNED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_OWNED_BY,
+	 PM"DROP\\s+OWNED\\s+BY\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_TEXT_SEARARCH,
+	 PM"DROP\\s+TEXT\\s+SEARCH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_TRIGGER_id,
+	 PM"DROP\\s+TRIGGER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_TRIGGER_id_ON,
+	 PM"DROP\\s+TRIGGER\\s+("ID")\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_TRIGGER_id_ON_id,
+	 PM"DROP\\s+TRIGGER\\s+"ID"\\s+ON\\s+"ID"\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_EVENT,
+	 PM"DROP\\s+EVENT\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_EVENT_TRIGGER,
+	 PM"DROP\\s+EVENT\\s+TRIGGER\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_POLICY,
+	 PM"DROP\\s+POLICY\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_POLICY_id,
+	 PM"DROP\\s+POLICY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_POLICY_id_ON,
+	 PM"DROP\\s+POLICY\\s+("ID")\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_RULE_id,
+	 PM"DROP\\s+RULE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_RULE_id_ON,
+	 PM"DROP\\s+RULE\\s+("ID")\\s+ON\\s+[\\w\"]*$", REG_ADVANCED|REG_ICASE},
+	{RE_DROP_RULE_id_ON_id,
+	 PM"DROP\\s+RULE\\s+"ID"\\s+ON\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_EXECUTE,
+	 PB"EXECUTE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_EXPLAIN,
+	 PM"EXPLAIN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_EXPLAIN_ANALYZE,
+	 PM"EXPLAIN\\s+ANALYZE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_EXPLAIN_oANALYZE_VERBOSE,
+	 PM"EXPLAIN\\s+(?:ANALYZE\\s+)?VERBOSE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FETCHoMOVE,
+	 PM"(?:FETCH|MOVE)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FETCHoMOVE_id,
+	 PM"(?:FETCH|MOVE)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FETCHoMOVE_any_any,
+	 PM"(?:FETCH|MOVE)\\s+\\w+\\s+\\w+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_negCREATE_FOREIGN_DATA_WRAPPER,
+	 PM"(?!CREATE)\\w+\\s+FOREIGN\\s+DATA\\s+WRAPPER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_negCREATE_FOREIGN_TABLE,
+	 PM"(?!CREATE)\\w+\\s+FOREIGN\\s+TABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FOREIGN_SERVER,
+	 "\\s+FOREIGN\\s+SERVER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE,
+	 PM"(?:GRANT|REVOKE)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd,
+	 PM"(?:GRANT|REVOKE)\\s+(?:SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|(?:TEMP(?:ORARY)?)|EXECUTE|USAGE|ALL)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_id,
+	 PM"(GRANT|REVOKE)\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON,
+	 PM"(?:GRANT|REVOKE)\\s+\\w+\\s+ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_ALL,
+	 PM"(?:GRANT|REVOKE)\\s+\\w+\\s+ON\\s+ALL\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_FOREIGN,
+	 PM"(?:GRANT|REVOKE)\\s+\\w+\\s+ON\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_any,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+ON\\s+(\\w+)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_ALL_any_IN_SCHEMA_id,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+ON\\s+ALL\\w+\\s+\\w+\\s+IN\\s+SCHEMA\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_FDW_id,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+ON\\s+FOREIGN\\s+DATA\\s+WRAPPER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_FOREIGN_SERVER_id,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+ON\\s+FOREIGN\\s+SERVER\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_any_TO,
+	 PM"(GRANT|REVOKE)\\s+.*\\s+(?:TO|FROM)+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_ON_kwd_id,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+ON\\s+\\w+\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_GRANToREVOKE_kwd_TO,
+	 PM"(GRANT|REVOKE)\\s+\\w+\\s+(?:TO|FROM)+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FROM_any_GROUP,
+	 "\\s+FROM\\s+"ID"\\s+GROUP\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_IMPORT,
+	 PM"IMPORT+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_IMPORT_FOREIGN,
+	 PM"IMPORT+\\s+FOREIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_INSERT,
+	 PM"INSERT+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_INSERT_INTO,
+	 PM"INSERT+\\s+INTO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_INSERT_INTO_id_,
+	 PM"INSERT+\\s+INTO\\s+("ID")\\s*\\(\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_INSERT_INTO_id,
+	 PM"INSERT+\\s+INTO\\s+("ID")\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_INSERT_INTO_id__,
+	 PM"INSERT+\\s+INTO\\s+("ID")\\s*\\([^\\)]*\\)\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_negDEFAULT_VALUES,
+	 "(?!\\s+DEFAULT\\s+)VALUES\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_LOCK,
+	 PM"LOCK+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_LOCK_TABLE,
+	 PM"LOCK+\\s+TABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_LOCK_oTABLE_id,
+	 PM"LOCK+\\s+(?:TABLE\\s+)?\\w+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_LOCK_oTABLE_id_IN,
+	 PM"LOCK+\\s+(?:TABLE\\s+)?\\w+\\s+IN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_NOTIFY,
+	 PM"NOTIFY+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_OPTIONS,
+	 "\\s+OPTIONS\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_OWNER_TO,
+	 "\\s+OWNER\\s+TO\\s*\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FROM_any_ORDER,
+	 "\\s+FROM\\s+"ID"\\s+ORDER\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_FROM_any_ORDER_BY,
+	 "\\s+FROM\\s+("ID")\\s+ORDER\\s+BY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_PREPARE_any_AS,
+	 PM"PREPARE\\s+"ID"\\s+AS\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REASSIGN,
+	 PM"REASSIGN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REASSIGN_OWNED,
+	 PM"REASSIGN\\s+OWNED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REASSIGN_OWNED_BY,
+	 PM"REASSIGN\\s+OWNED\\s+BY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REASSIGN_OWNED_BY_id,
+	 PM"REASSIGN\\s+OWNED\\s+BY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REASSIGN_OWNED_BY_id_TO,
+	 PM"REASSIGN\\s+OWNED\\s+BY\\s+"ID"\\s+TO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH,
+	 PM"REFRESH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED,
+	 PM"REFRESH\\s+MATERIALIZED\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_CONC,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+CONCURRENTLY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_id,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_CONC_id,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+CONCURRENTLY\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_id_WITH,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+"ID"\\s+WITH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_CONC_id_WITH,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+CONCURRENTLY\\s+"ID"\\s+WITH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REFRESH_MATERIALIZED_VIEW_id_WITH_NO,
+	 PM"REFRESH\\s+MATERIALIZED\\s+VIEW\\s+"ID"\\s+WITH\\s+NO\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REINDEX,
+	 PM"REINDEX\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_REINDEX_kwd,
+	 PM"REINDEX\\s+(\\w+)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SECURITY,
+	 PM"SECURITY\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SECURITY_LABEL,
+	 PM"SECURITY\\s+LABEL\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SECURITY_LABEL_FOR_id,
+	 PM"SECURITY\\s+LABEL\\s+FOR\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SECURITY_LABEL_oFORname_ON,
+	 PM"SECURITY\\s+LABEL\\s+(?:FOR\\s+"ID"\\s)?ON\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SECURITY_LABEL_ON_kwd_id,
+	 PM"SECURITY\\s+LABEL\\s+ON\\s+\\w+\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SEToRESET,
+	 PM"(?:RE)?SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SHOW,
+	 PM"SHOW\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs_ISOLATION,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+ISOLATION\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs_ISOLATION_LEVEL,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+ISOLATION\\s+LEVEL\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs_ISOLATION_LEVEL_READ,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+ISOLATION\\s+LEVEL\\s+READ\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs_ISOLATION_LEVEL_REPEATABLE,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+ISOLATION\\s+LEVEL\\s+REPEATABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_TRANSACTIONs_READ,
+	 PM"(?:(?:(?:SET|START|BEGIN)\\s+TRANSACTION)|(?:BEGIN\\s+WORK)|(?:SET\\s+SESSION\\s+CHARACTERISTICS\\s+AS\\s+TRANSACTION))\\s+READ\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_CONSTRAINTS,
+	 PM"SET\\s+CONSTRAINTS\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_CONSTRAINTS_id,
+	 PM"SET\\s+CONSTRAINTS\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_ROLE,
+	 PM"SET\\s+ROLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_SESSION,
+	 PM"SET\\s+SESSION\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_SESSION_AUTH,
+	 PM"SET\\s+SESSION\\s+AUTHORIZATION\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_RESET_SESSION,
+	 PM"RESET\\s+SESSION\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_var,
+	 PM"SET\\s+(?!TABLESPACE|SCHEMA|(?:.*\\)[)=]))\\w+\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_SET_var_TOoEQ,
+	 PM"SET\\s+(?!TABLESPACE|SCHEMA|(?:.*\\)[)=]))(\\w+)(?:(?:\\s+TO\\s+)|(?:\\s*=\\s*))\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_START,
+	 PM"START\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_TABLE,
+	 PB"TABLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_TABLESAMPLE,
+	 "\\s+TABLESAMPLE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_TABLESAMPLE_id,
+	 "\\s+TABLESAMPLE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_TRUNCATE,
+	 PM"TRUNCATE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_UNLISTEN,
+	 PM"UNLISTEN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_UPDATE,
+	 "\\s+UPDATE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_UPDATE_id,
+	 "\\s+UPDATE\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_id_SET,
+	 "\\s+([\\w\"])+\\s+SET\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_UPDATE_id_SET_id,
+	 "\\s+UPDATE\\s+"ID"\\s+SET\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CRToALToDRP_USER_MAPPING,
+	 PM"(?:CREATE|ALTER|DROP)\\s+USER\\s+MAPPING\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CREATE_USER_MAPPING_FOR,
+	 PM"CREATE\\s+USER\\s+MAPPING\\s+FOR\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_ALToDRP_USER_MAPPING_FOR,
+	 PM"(?:ALTER|DROP)\\s+USER\\s+MAPPING\\s+FOR\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_CRToALToDRP_USER_MAPPING_FOR_id,
+	 PM"(?:CREATE|ALTER|DROP)\\s+USER\\s+MAPPING\\s+FOR\\s+"ID"\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM,
+	 PM"VACUUM\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM_FULLoFREEZE,
+	 PM"VACUUM\\s+(?:FULL|FREEZE)\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM_FULLoFREEZE_ANALYZE,
+	 PM"VACUUM\\s+(?:FULL|FREEZE)\\s+ANALYZE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM_FULLoFREEZE_VERBOSE,
+	 PM"VACUUM\\s+(?:FULL|FREEZE)\\s+VERBOSE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM_VERBOSE,
+	 PM"VACUUM\\s+VERBOSE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_VACUUM_ANALYZE,
+	 PM"VACUUM\\s+ANALYZE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_ANLVRBoVRBANL,
+	 "\\s+(?:(?:ANALYZE\\s+VERBOSE)|(?:VERBOSE\\s+ANALYZE))\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_WITH,
+	 PB"WITH\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_ANALYZE,
+	 PM"ANALYZE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_id_WHERE,
+	 PM"("ID")\\s+WHERE\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_COPY_id_FROM,
+	 PM"\\\\?COPY\\s+"ID"\\s+FROM\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+	{RE_any_JOIN,
+	 PM"JOIN\\s+\\w*$", REG_ADVANCED|REG_ICASE},
+
+	{RE_meta_question,
+	 PM"\\\\\\?\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_CONNECT,
+	 PM"\\\\c(?:onnect)?\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_CONNECT_word,
+	 PM"\\\\c(?:onnect)?\\s+((?:\"[^\"]+\")|\\w+)\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_da,
+	 PM"\\\\da\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_db,
+	 PM"\\\\db\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dD,
+	 PM"\\\\dD\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_des,
+	 PM"\\\\des\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_deu,
+	 PM"\\\\deu\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dew,
+	 PM"\\\\dew\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_df,
+	 PM"\\\\df\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dFd,
+	 PM"\\\\dFd\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dFp,
+	 PM"\\\\dFp\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dFt,
+	 PM"\\\\dFt\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dF,
+	 PM"\\\\dF\\w*\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_di,
+	 PM"\\\\di\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dL,
+	 PM"\\\\dL\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dn,
+	 PM"\\\\dn\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dpOz,
+	 PM"\\\\(?:dp|z)\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_ds,
+	 PM"\\\\ds\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dt,
+	 PM"\\\\dt\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dT,
+	 PM"\\\\dT\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_duOdg,
+	 PM"\\\\(?:du|dg)\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dv,
+	 PM"\\\\dv\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dx,
+	 PM"\\\\dx\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dm,
+	 PM"\\\\dm\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dE,
+	 PM"\\\\dE\\w*\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_dy,
+	 PM"\\\\dy\\w*\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_d,
+	 PM"\\\\d\\w*\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_ef,
+	 PM"\\\\ef\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_ev,
+	 PM"\\\\ev\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_encoding,
+	 PM"\\\\encoding\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_h,
+	 PM"\\\\h(?:elp)?\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_password,
+	 PM"\\\\password\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_pset,
+	 PM"\\\\pset\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_pset_id,
+	 PM"\\\\pset\\s+(\\w+)\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_unset,
+	 PM"\\\\unset\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_set,
+	 PM"\\\\set\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_set_id,
+	 PM"\\\\set\\s+(\\w+)\\s+\\w*$", REG_ADVANCED},
+
+	{RE_meta_sf,
+	 PM"\\\\sf\\+?\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_sv,
+	 PM"\\\\sv\\+?\\s+\\w*$", REG_ADVANCED},
+	{RE_meta_FILECMDS,
+	 PM"\\\\(?:cd|e|edit|g|i|include|ir|include_relative|o|out|s|w|write|lo_import)\\s+\\w*$", REG_ADVANCED},
+
+	{RE_AnySingleWord,
+	 "(?:[^\\s]\\s+)?\\s*(\\w+)\\s+"NWB"*$", REG_ADVANCED},
+	
+};
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..1eea2dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -54,6 +54,8 @@
 #include "common.h"
 #include "settings.h"
 #include "stringutils.h"
+#include "regex/regex.h"
+#include "catalog/pg_collation.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -792,6 +794,18 @@ typedef struct
 #define THING_NO_DROP		(1 << 1)	/* should not show up after DROP */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP)
 
+#define CAPBUFLEN 128
+#define MATCHNUM 5
+
+#define REGMATCH(patid) (regmatchret=pg_regexec(&re[patid], linebufw, wstrlen, 0, NULL, MATCHNUM, rmatches, 0), regmatchret == 0)
+#define MATCHBEG(n) (rmatches[n].rm_so)
+#define MATCHEND(n) (rmatches[n].rm_eo)
+#define MATCHLEN(n) (MATCHEND(n) - MATCHBEG(n))
+#define CAPCPYLEN(n)(MATCHLEN(n)<CAPBUFLEN ? MATCHLEN(n):CAPBUFLEN - 1)
+#define CAPTURE0(n) (strncpy(capbuf[n], linebuf + MATCHBEG(n), CAPCPYLEN(n)),capbuf[n][CAPCPYLEN(n)] = 0)
+#define CAPTURE(n)  (CAPTURE0(n), capbuf[n])
+#define CAPBUF(n)   (capbuf[n])
+
 static const pgsql_thing_t words_after_create[] = {
 	{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL},		/* Casts have complex structures for names, so
@@ -842,6 +856,9 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL}						/* end of list */
 };
 
+#include "tab-complete-re.h"
+
+static regex_t re[RE_NUM];
 
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
@@ -864,15 +881,68 @@ static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
-static void get_previous_words(int point, char **previous_words, int nwords);
-
 static char *get_guctype(const char *varname);
 
+static void regcomp_e(comp_def *redef, regex_t *re);
+static void tab_complete_init(void);
+
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
+static int
+pg_ascii2wchar_with_len(const char *from, pg_wchar *to, int len)
+{
+	int			cnt = 0;
+
+	while (len > 0 && *from)
+	{
+		*to++ = *from++;
+		len--;
+		cnt++;
+	}
+	*to = 0;
+	return cnt;
+}
+
+static void
+regcomp_e(comp_def *redef, regex_t *re)
+{
+	pg_wchar	*pattern;
+	int patlen;
+	int ret;
+
+	pattern = pg_malloc(sizeof(pg_wchar) * (strlen(redef->pat) + 1));
+	patlen = pg_ascii2wchar_with_len(redef->pat, pattern, strlen(redef->pat));
+	
+	if ((ret = pg_regcomp(re, pattern, patlen,
+						  redef->cflags, C_COLLATION_OID)) != 0)
+	{
+		fprintf(stderr, "failed to compile regexp (%d):\"%s\"\n",
+				ret, redef->pat);
+		exit(1);
+	}
+	pg_free(pattern);
+}
+
+void
+tab_complete_init(void)
+{
+	static int initialized = 0;
+	int i;
+
+	if (initialized)
+		return;
+	initialized = 1;
+
+	for (i = 0 ; i < RE_NUM ; i++)
+	{
+		Assert(redef[i].pat_id == i);
+		regcomp_e(&redef[i], &re[i]);
+	}
+}
+
 
 /*
  * Initialize the readline library for our purposes.
@@ -887,12 +957,39 @@ initialize_readline(void)
 
 	completion_max_records = 1000;
 
+	tab_complete_init();
+
 	/*
 	 * There is a variable rl_completion_query_items for this but apparently
 	 * it's not defined everywhere.
 	 */
 }
 
+static pg_wchar *
+expand_wchar_buffer(pg_wchar *p, int *buflen, int newlen, int limit)
+{
+	pg_wchar *ret = p;
+	int       len1 = newlen - 1;
+
+	Assert(newlen > 0);
+	if (newlen >= *buflen)
+	{
+		int mask;
+
+		/* Allocate in size of 2^n. Minimum 256 wchars */
+		for (mask = 255 ; mask < limit && (len1 & mask) != len1 ;
+			 mask = (mask << 1) | 1);
+		
+		/* mask is (<new buffer length> - 1) here */
+		if (mask >= limit) return NULL; /* Exceeds limit */
+		
+		*buflen = mask + 1;
+		
+		ret = pg_realloc(p, (*buflen) * sizeof(pg_wchar));
+	}		
+
+	return ret;
+}
 
 /*
  * The completion function.
@@ -905,23 +1002,17 @@ initialize_readline(void)
 static char **
 psql_completion(const char *text, int start, int end)
 {
+	const char *linebuf = rl_line_buffer;
+	static pg_wchar	 *linebufw = NULL;
+	static int	     linebufwlen = 0;
+	int len, wstrlen;
+	regmatch_t rmatches[MATCHNUM];
+	char capbuf[MATCHNUM][CAPBUFLEN];
+	int regmatchret = 0;
+
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
-	/* This array will contain some scannage of the input line. */
-	char	   *previous_words[9];
-
-	/* For compactness, we use these macros to reference previous_words[]. */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
@@ -961,12 +1052,10 @@ psql_completion(const char *text, int start, int end)
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
 
-	/*
-	 * Scan the input line before our current position for the last few words.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	get_previous_words(start, previous_words, lengthof(previous_words));
+	len = strlen(linebuf);
+	/* Expand string buffer if needed */
+	linebufw = expand_wchar_buffer(linebufw, &linebufwlen, len + 1, 2048); 
+	wstrlen = pg_ascii2wchar_with_len(linebuf, linebufw, strlen(linebuf));
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -984,36 +1073,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (REGMATCH(RE_none))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (REGMATCH(RE_CREATE))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (REGMATCH(RE_DROP))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_TABLE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (REGMATCH(RE_ALTER))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1026,9 +1110,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (REGMATCH(RE_ALL_IN_TABLESPACE_id))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1036,38 +1118,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (REGMATCH(RE_ALL_IN_TABLESPACE_id_OWNED_BY))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (REGMATCH(RE_ALTER_AGGREGATEoFUNCTION))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (REGMATCH(RE_ALTER_AGGREGATEoFUNCTION_))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+	else if (REGMATCH(RE_ALTER_AGGREGATEoFUNCTION__))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
+		static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
-		else
-			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+	else if (REGMATCH(RE_ALTER_SCHEMA_id))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1076,18 +1144,15 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+	else if (REGMATCH(RE_ALTER_COLLATION_id))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
-
-	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
+	/* ALTER COLLATION/CONVERSION <name> */
+	else if (REGMATCH(RE_ALTER_CONVERSION_id))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1096,8 +1161,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (REGMATCH(RE_ALTER_DATABASE_id))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1107,17 +1171,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_EVENT_TRIGGER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_ALTER_EVENT_TRIGGER_id))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
 		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
@@ -1126,10 +1184,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (REGMATCH(RE_ALTER_EVENT_TRIGGER_id_ENABLE))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
 		{"REPLICA", "ALWAYS", NULL};
@@ -1138,8 +1193,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (REGMATCH(RE_ALTER_EXTENSION_id))
 	{
 		static const char *const list_ALTEREXTENSION[] =
 		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
@@ -1148,8 +1202,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_ALTER_FOREIGN))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -1158,10 +1211,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (REGMATCH(RE_ALTER_FOREIGN_DATA_WRAPPER_id))
 	{
 		static const char *const list_ALTER_FDW[] =
 		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
@@ -1170,9 +1220,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (REGMATCH(RE_ALTER_FOREIGN_TABLE_id))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1183,15 +1231,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_INDEX))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 	/* ALTER INDEX <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (REGMATCH(RE_ALTER_INDEX_id))
 	{
 		static const char *const list_ALTERINDEX[] =
 		{"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
@@ -1199,9 +1243,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEX);
 	}
 	/* ALTER INDEX <name> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (REGMATCH(RE_ALTER_INDEX_id_SET))
 	{
 		static const char *const list_ALTERINDEXSET[] =
 		{"(", "TABLESPACE", NULL};
@@ -1209,25 +1251,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
 	}
 	/* ALTER INDEX <name> RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (REGMATCH(RE_ALTER_INDEX_id_RESET))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (REGMATCH(RE_ALTER_INDEX_id_RESET_))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor", "fastupdate", "gin_pending_list_limit", NULL};
 
 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (REGMATCH(RE_ALTER_INDEX_id_SET_))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor =", "fastupdate =", "gin_pending_list_limit =", NULL};
@@ -1236,8 +1270,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LANGUAGE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
+	else if (REGMATCH(RE_ALTER_LANGUAGE_id))
 	{
 		static const char *const list_ALTERLANGUAGE[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1246,9 +1279,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OBJECT") == 0)
+	else if (REGMATCH(RE_ALTER_LARGE_OBJECT_oid))
 	{
 		static const char *const list_ALTERLARGEOBJECT[] =
 		{"OWNER TO", NULL};
@@ -1257,19 +1288,12 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_MATERIALIZED_VIEW))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/* ALTER USER,ROLE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
+	else if (REGMATCH(RE_ALTER_USERoROLE_id))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1283,10 +1307,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "USER") == 0 ||
-			   pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (REGMATCH(RE_ALTER_USERoROLE_id_WITH))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1301,16 +1322,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (REGMATCH(RE_ALTER_USERoROLE_id_ENCoUNENC))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
+
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
+	else if (REGMATCH(RE_ALTER_DEFAULT_PRIVILEGES))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
 		{"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
@@ -1318,10 +1334,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
 	}
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (REGMATCH(RE_ALTER_DEFAULT_PRIVILEGES_FOR))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
 		{"ROLE", "USER", NULL};
@@ -1329,10 +1342,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
 	}
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-			  pg_strcasecmp(prev3_wd, "IN") == 0))
+	else if (REGMATCH(RE_ALTER_DEFAULT_PRIVILEGES_FORROLEoINSCHEMA_id))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
 		{"GRANT", "REVOKE", NULL};
@@ -1340,8 +1350,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
 	}
 	/* ALTER DOMAIN <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
+	else if (REGMATCH(RE_ALTER_DOMAIN_id))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
@@ -1349,9 +1358,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> DROP */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (REGMATCH(RE_ALTER_DOMAIN_id_DROP))
 	{
 		static const char *const list_ALTERDOMAIN2[] =
 		{"CONSTRAINT", "DEFAULT", "NOT NULL", NULL};
@@ -1359,20 +1366,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
 	}
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		completion_info_charp = prev3_wd;
+	else if (REGMATCH(RE_ALTER_DOMAIN_sth_DROPoRENAMEoVALIDATE_CONSTRAINT))
+	{
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (REGMATCH(RE_ALTER_DOMAIN_id_RENAME))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"CONSTRAINT", "TO", NULL};
@@ -1380,15 +1380,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+	else if (REGMATCH(RE_ALTER_DOMAIN_id_RENAME_CONSTRAINT))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (REGMATCH(RE_ALTER_DOMAIN_id_SET))
 	{
 		static const char *const list_ALTERDOMAIN3[] =
 		{"DEFAULT", "NOT NULL", "SCHEMA", NULL};
@@ -1396,8 +1392,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
 	}
 	/* ALTER SEQUENCE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
+	else if (REGMATCH(RE_ALTER_SEQUENCE_id))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1406,9 +1401,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (REGMATCH(RE_ALTER_SEQUENCE_id_NO))
 	{
 		static const char *const list_ALTERSEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -1416,32 +1409,26 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
 	/* ALTER SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (REGMATCH(RE_ALTER_SERVER_id))
 	{
 		static const char *const list_ALTER_SERVER[] =
 		{"VERSION", "OPTIONS", "OWNER TO", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER_SERVER);
 	}
-	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "SYSTEM") == 0)
+	/* ALTER SYSTEM  */
+	else if (REGMATCH(RE_ALTER_SYSTEM))
 	{
 		static const char *const list_ALTERSYSTEM[] =
 		{"SET", "RESET", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
 	}
-	/* ALTER SYSTEM SET|RESET <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	/* ALTER SYSTEM SET|RESET */
+	else if (REGMATCH(RE_ALTER_SYSTEM_SEToRESET))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_ALTER_VIEW_id))
 	{
 		static const char *const list_ALTERVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1449,9 +1436,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_ALTER_MVIEW_id))
 	{
 		static const char *const list_ALTERMATVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1459,94 +1444,65 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
 	}
 
-	/* ALTER POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	/* ALTER POLICY <name> */
+	else if (REGMATCH(RE_ALTER_POLICY_id))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_ALTER_POLICY_id_ON))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_ALTER_POLICY_id_ON_table))
 	{
 		static const char *const list_ALTERPOLICY[] =
 		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERPOLICY);
 	}
-	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	/* ALTER POLICY <name> ON <table> TO */
+	else if (REGMATCH(RE_ALTER_POLICY_id_ON_table_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	/* ALTER POLICY <name> ON <table> USING */
+	else if (REGMATCH(RE_ALTER_POLICY_id_ON_table_USING))
 		COMPLETE_WITH_CONST("(");
-	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+	/* ALTER POLICY <name> ON <table> WITH CHECK */
+	else if (REGMATCH(RE_ALTER_POLICY_id_ON_table_WITH_CHECK))
 		COMPLETE_WITH_CONST("(");
-
-	/* ALTER RULE <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	/* ALTER RULE <name> */
+	else if (REGMATCH(RE_ALTER_RULE_id))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_ALTER_RULE_id_ON))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0)
+	else if (REGMATCH(RE_ALTER_RULE_id_ON_id))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_ALTER_TRIGGER_id))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
-	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
-	}
-
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+	else if (REGMATCH(RE_ALTER_TRIGGER_id_ON))
+	{
+		completion_info_charp = CAPTURE(1);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+	}
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_ALTER_TRIGGER_id_ON_id))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1556,102 +1512,66 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE))
 	{
 		static const char *const list_ALTERENABLE[] =
 		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
-			  pg_strcasecmp(prev_wd, "ALWAYS") == 0))
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE_REPLICAoALWAYS))
 	{
 		static const char *const list_ALTERENABLE2[] =
 		{"RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE2);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE_RULE))
 	{
-		completion_info_charp = prev3_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE_kwd_RULE))
 	{
-		completion_info_charp = prev4_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE_TRIGGER))
 	{
-		completion_info_charp = prev3_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ENABLE_kwd_TRIGGER))
 	{
-		completion_info_charp = prev4_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_TABLE_id_INHERIT))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "NO") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_TABLE_id_NOINHERIT))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
 	/* ALTER TABLE xxx DISABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_DISABLE))
 	{
 		static const char *const list_ALTERDISABLE[] =
 		{"ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERDISABLE);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_DISABLE_RULE))
 	{
-		completion_info_charp = prev3_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_DISABLE_TRIGGER))
 	{
-		completion_info_charp = prev3_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
+
+	else if (REGMATCH(RE_ALTER_TABLE_id_DISABLE_ROW_LEVEL_SECURITY))
 	{
 		static const char *const list_DISABLERLS[] =
 		{"CASCADE", NULL};
@@ -1660,45 +1580,28 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALTER") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER))
+		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+	else if (REGMATCH(RE_ALTER_TABLE_id_RENAME))
+		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
-
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTERoRENAME_COLUMN))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_RENAME_negCONSTRAINToTO))
 		COMPLETE_WITH_CONST("TO");
-
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_RENAME_COLUMNoCONSTRAINT_negTO))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_DROP))
 	{
 		static const char *const list_TABLEDROP[] =
 		{"COLUMN", "CONSTRAINT", NULL};
@@ -1706,32 +1609,20 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLEDROP);
 	}
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (REGMATCH(RE_ALTER_TABLE_id_DROP_COLUMN))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		completion_info_charp = prev3_wd;
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALToDROPoRENoVALIDATE_CONSTR))
+	{
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER_oCOLUMN_id))
 	{
 		static const char *const list_COLUMNALTER[] =
 		{"TYPE", "SET", "RESET", "DROP", NULL};
@@ -1739,11 +1630,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNALTER);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET))
 	{
 		static const char *const list_COLUMNSET[] =
 		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
@@ -1751,11 +1638,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSET);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_))
 	{
 		static const char *const list_COLUMNOPTIONS[] =
 		{"n_distinct", "n_distinct_inherited", NULL};
@@ -1763,11 +1646,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_SET_STORAGE))
 	{
 		static const char *const list_COLUMNSTORAGE[] =
 		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
@@ -1775,30 +1654,22 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_ALTER_oCOLUMN_id_DROP))
 	{
 		static const char *const list_COLUMNDROP[] =
 		{"DEFAULT", "NOT NULL", NULL};
 
 		COMPLETE_WITH_LIST(list_COLUMNDROP);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_CLUSTER))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_CLUSTER_ON))
 	{
-		completion_info_charp = prev3_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_SET))
 	{
 		static const char *const list_TABLESET[] =
 		{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
@@ -1806,14 +1677,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET);
 	}
 	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_SET_TABLESPACE))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITHOUT") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_SET_WITHOUT))
 	{
 		static const char *const list_TABLESET2[] =
 		{"CLUSTER", "OIDS", NULL};
@@ -1821,14 +1688,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET2);
 	}
 	/* ALTER TABLE <foo> RESET */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_RESET))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_RESET_))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1865,39 +1728,25 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING_INDEX))
 	{
-		completion_info_charp = prev5_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_TABLE_id_REPLICA_IDENTITY_USING))
 		COMPLETE_WITH_CONST("INDEX");
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
+	else if (REGMATCH(RE_ALTER_TABLE_id_REPLICA_IDENTITY))
 	{
 		static const char *const list_REPLICAID[] =
 		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_REPLICAID);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
-	{
+	else if (REGMATCH(RE_ALTER_TABLE_id_REPLICA))
 		COMPLETE_WITH_CONST("IDENTITY");
-	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (REGMATCH(RE_ALTER_TABLESPACE_id))
 	{
 		static const char *const list_ALTERTSPC[] =
 		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
@@ -1905,17 +1754,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTSPC);
 	}
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (REGMATCH(RE_ALTER_TABLESPACE_id_SEToRESET))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (REGMATCH(RE_ALTER_TABLESPACE_id_SEToRESET_))
 	{
 		static const char *const list_TABLESPACEOPTIONS[] =
 		{"seq_page_cost", "random_page_cost", "effective_io_concurrency", NULL};
@@ -1924,20 +1766,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (REGMATCH(RE_ALTER_TEXT_SEARCH))
 	{
 		static const char *const list_ALTERTEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "PARSER") == 0))
+	else if (REGMATCH(RE_ALTER_TEXT_SEARCH_TEMPLATEoPARSER_id))
 	{
 		static const char *const list_ALTERTEXTSEARCH2[] =
 		{"RENAME TO", "SET SCHEMA", NULL};
@@ -1945,10 +1781,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
+	else if (REGMATCH(RE_ALTER_TEXT_SEARCH_DICTIONARY_id))
 	{
 		static const char *const list_ALTERTEXTSEARCH3[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1956,10 +1789,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (REGMATCH(RE_ALTER_TEXT_SEARCH_CONFIGURATION_id))
 	{
 		static const char *const list_ALTERTEXTSEARCH4[] =
 		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1968,8 +1798,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TYPE") == 0)
+	else if (REGMATCH(RE_ALTER_TYPE_id))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
@@ -1978,9 +1807,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ADD") == 0)
+	else if (REGMATCH(RE_ALTER_TYPE_id_ADD))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "VALUE", NULL};
@@ -1988,9 +1815,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (REGMATCH(RE_ALTER_TYPE_id_RENAME))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "TO", NULL};
@@ -1998,30 +1823,20 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+	else if (REGMATCH(RE_ALTER_TYPE_id_RENAME_ATTRIBUTE_id))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
 	 * attributes
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (REGMATCH(RE_ALTER_TYPE_id_ALToDROPoREN_ATTRIBUTE))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
-	{
+	else if (REGMATCH(RE_ALTER_TYPE_id_ALTER_ATTRIBUTE_id))
 		COMPLETE_WITH_CONST("TYPE");
-	}
 	/* complete ALTER GROUP <foo> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "GROUP") == 0)
+	else if (REGMATCH(RE_ALTER_GROUP_id))
 	{
 		static const char *const list_ALTERGROUP[] =
 		{"ADD USER", "DROP USER", "RENAME TO", NULL};
@@ -2029,22 +1844,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGROUP);
 	}
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev_wd, "DROP") == 0))
+	else if (REGMATCH(RE_ALTER_GROUP_id_ADDoDROP))
 		COMPLETE_WITH_CONST("USER");
 	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
-	else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev_wd, "USER") == 0)
+	else if (REGMATCH(RE_ALTER_GROUP_id_ADDoDROP_USER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
-			 pg_strcasecmp(prev_wd, "END") == 0 ||
-			 pg_strcasecmp(prev_wd, "ABORT") == 0)
+	else if (REGMATCH(RE_BEGINoENDoABORT))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", NULL};
@@ -2052,7 +1859,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* COMMIT */
-	else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
+	else if (REGMATCH(RE_COMMIT))
 	{
 		static const char *const list_COMMIT[] =
 		{"WORK", "TRANSACTION", "PREPARED", NULL};
@@ -2060,10 +1867,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COMMIT);
 	}
 /* RELEASE SAVEPOINT */
-	else if (pg_strcasecmp(prev_wd, "RELEASE") == 0)
+	else if (REGMATCH(RE_RELEASE))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0)
+	else if (REGMATCH(RE_ROLLBACK))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL};
@@ -2075,57 +1882,38 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
+	else if (REGMATCH(RE_CLUSTER))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
 
 	/*
 	 * If the previous words are CLUSTER VERBOSE produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+	else if (REGMATCH(RE_CLUSTER_VERBOSE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") != 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-	{
+	else if (REGMATCH(RE_CLUSTER_negONoVERBOSE))
 		COMPLETE_WITH_CONST("USING");
-	}
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
-	{
+	else if (REGMATCH(RE_CLUSTER_VERBOSE_id))
 		COMPLETE_WITH_CONST("USING");
-	}
-
 	/*
 	 * If we have CLUSTER <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (REGMATCH(RE_CLUSTER_id_USING))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-
-	/*
-	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
-	 */
-	else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (REGMATCH(RE_CLUSTER_VERBOSE_id_USING))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 
 /* COMMENT */
-	else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
+	else if (REGMATCH(RE_COMMENT))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_COMMENT_ON))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -2137,66 +1925,34 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_COMMENT_ON_FOREIGN))
 	{
 		static const char *const list_TRANS2[] =
 		{"DATA WRAPPER", "TABLE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (REGMATCH(RE_COMMENT_ON_TEXT_SEARCH))
 	{
 		static const char *const list_TRANS2[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
+	else if (REGMATCH(RE_COMMENT_ON_CONSTRAINT))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
-	{
+	else if (REGMATCH(RE_COMMENT_ON_CONSTRAINT_id))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_COMMENT_ON_CONSTRAINT_id_ON))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (REGMATCH(RE_COMMENT_ON_MATERIALIZED_VIEW))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (REGMATCH(RE_COMMENT_ON_EVENT_TRIGGER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
-	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev4_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev5_wd, "ON") == 0)) &&
-			 pg_strcasecmp(prev_wd, "IS") != 0)
+	else if (REGMATCH(RE_COMMENT_ON_any_negIS))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2205,15 +1961,10 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (pg_strcasecmp(prev_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev_wd, "\\copy") == 0 ||
-			 (pg_strcasecmp(prev2_wd, "COPY") == 0 &&
-			  pg_strcasecmp(prev_wd, "BINARY") == 0))
+	else if (REGMATCH(RE_COPY_oBINARY))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
-	else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
-			 pg_strcasecmp(prev2_wd, "BINARY") == 0)
+	else if (REGMATCH(RE_COPY_oBINARY_id))
 	{
 		static const char *const list_FROMTO[] =
 		{"FROM", "TO", NULL};
@@ -2221,22 +1972,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FROMTO);
 	}
 	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
-	else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev_wd, "TO") == 0))
+	else if (REGMATCH(RE_COPY_oBINARY_id_FROMoTO))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename */
-	else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev4_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev2_wd, "TO") == 0))
+	else if (REGMATCH(RE_COPY_oBINARY_id_FROMoTO_any))
 	{
 		static const char *const list_COPY[] =
 		{"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
@@ -2245,9 +1988,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-	else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev3_wd, "TO") == 0))
+	else if (REGMATCH(RE_COPY_any_FROMoTO_any_CSV))
 	{
 		static const char *const list_CSV[] =
 		{"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
@@ -2255,9 +1996,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CSV);
 	}
 
-	/* CREATE DATABASE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	/* CREATE DATABASE <name> */
+	else if (REGMATCH(RE_CREATE_DATABASE_id))
 	{
 		static const char *const list_DATABASE[] =
 		{"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS_TEMPLATE",
@@ -2267,19 +2007,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DATABASE);
 	}
 
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+	else if (REGMATCH(RE_CREATE_DATABASE_id_TEMPLATE))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+	else if (REGMATCH(RE_CREATE_EXTENSION))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (REGMATCH(RE_CREATE_EXTENSION_id))
 	{
 		static const char *const list_CREATE_EXTENSION[] =
 		{"WITH SCHEMA", "CASCADE", "VERSION", NULL};
@@ -2287,17 +2023,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
 	}
 	/* CREATE EXTENSION <name> VERSION */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EXTENSION") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERSION") == 0)
+	else if (REGMATCH(RE_CREATE_EXTENSION_id_VERSION))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_CREATE_FOREIGN))
 	{
 		static const char *const list_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2306,10 +2039,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (REGMATCH(RE_CREATE_FOREIGN_DATA_WRAPPER_id))
 	{
 		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
 		{"HANDLER", "VALIDATOR", NULL};
@@ -2319,31 +2049,21 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
+	else if (REGMATCH(RE_CREATE_UNIQUE))
 		COMPLETE_WITH_CONST("INDEX");
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
-	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+	else if (REGMATCH(RE_CREATE_oUNIQUE_INDEX))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX [<name>] ON with a list of tables  */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_INDEX_oname_CONCURRENTLY_ON))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (REGMATCH(RE_INDEX_oname_CONCURRENTLY))
 		COMPLETE_WITH_CONST("ON");
 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (REGMATCH(RE_CREATE_oUNIQUE_INDEX_name))
 	{
 		static const char *const list_CREATE_INDEX[] =
 		{"CONCURRENTLY", "ON", NULL};
@@ -2355,54 +2075,33 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_INDEX_oname_CONCURRENTLY_ON_id))
 	{
 		static const char *const list_CREATE_INDEX2[] =
 		{"(", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
 	}
-	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (REGMATCH(RE_INDEX_oname_CONCURRENTLY_ON_id_))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 	/* same if you put in USING */
-	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+	else if (REGMATCH(RE_ON_id_USING_kwd_))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 	/* Complete USING with an index method */
-	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (REGMATCH(RE_ON_id_USING))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
-			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0)
+	else if (REGMATCH(RE_CREATE_INDEX_any_ON_id_USING_kwd))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"FOR", "TO", "USING", "WITH CHECK", NULL};
@@ -2414,10 +2113,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_FOR))
 	{
 		static const char *const list_POLICYCMDS[] =
 		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
@@ -2425,10 +2121,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYCMDS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_FOR_INSERT))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "WITH CHECK", NULL};
@@ -2440,11 +2133,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
 	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
-			  pg_strcasecmp(prev_wd, "DELETE") == 0))
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_FOR_SELoDEL))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", NULL};
@@ -2457,11 +2146,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
 	 * CHECK"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
-			  pg_strcasecmp(prev_wd, "UPDATE") == 0))
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_FOR_UPDATE))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", "WITH CHECK", NULL};
@@ -2469,32 +2154,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (REGMATCH(RE_CREATE_POLICY_id_ON_id_USING))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (REGMATCH(RE_CREATE_RULE_id))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS with "ON" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (REGMATCH(RE_CREATE_RULE_id_AS))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
-	else if (pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_CREATE_RULE_id_AS_ON))
 	{
 		static const char *const rule_events[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -2502,24 +2176,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(rule_events);
 	}
 	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
-	else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
-			  pg_toupper((unsigned char) prev_wd[5]) == 'T'))
+	else if (REGMATCH(RE_CREATE_RULE_id_AS_ON_event))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (REGMATCH(RE_CREATE_RULE_id_AS_ON_event_TO))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0))
+	else if (REGMATCH(RE_CREATE_oTEMP_SEQUENCE_oINE_id))
 	{
 		static const char *const list_CREATESEQUENCE[] =
 		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
@@ -2528,13 +2192,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev4_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev4_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0)) &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (REGMATCH(RE_CREATE_oTEMP_SEQUENCE_oINE_id_any_NO))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -2543,8 +2201,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (REGMATCH(RE_CREATE_SERVER_id))
 	{
 		static const char *const list_CREATE_SERVER[] =
 		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
@@ -2554,9 +2211,7 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
-			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
+	else if (REGMATCH(RE_CREATE_TEMP))
 	{
 		static const char *const list_TEMP[] =
 		{"SEQUENCE", "TABLE", "VIEW", NULL};
@@ -2564,8 +2219,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TEMP);
 	}
 	/* Complete "CREATE UNLOGGED" with TABLE */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
+	else if (REGMATCH(RE_CREATE_UNLOGGED))
 	{
 		static const char *const list_UNLOGGED[] =
 		{"TABLE", "MATERIALIZED VIEW", NULL};
@@ -2574,8 +2228,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE TABLESPACE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (REGMATCH(RE_CREATE_TABLESPACE_id))
 	{
 		static const char *const list_CREATETABLESPACE[] =
 		{"OWNER", "LOCATION", NULL};
@@ -2583,32 +2236,25 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
 	}
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNER") == 0)
+	else if (REGMATCH(RE_CREATE_TABLESPACE_id_OWNER_id))
 	{
 		COMPLETE_WITH_CONST("LOCATION");
 	}
 
 /* CREATE TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (REGMATCH(RE_CREATE_TEXT_SEARCH))
 	{
 		static const char *const list_CREATETEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (REGMATCH(RE_CREATE_TEXT_SEARCH_CONFIGURATION_id))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_CREATE_TRIGGER_id))
 	{
 		static const char *const list_CREATETRIGGER[] =
 		{"BEFORE", "AFTER", "INSTEAD OF", NULL};
@@ -2616,10 +2262,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev_wd, "AFTER") == 0))
+	else if (REGMATCH(RE_CREATE_TRIGGER_id_BEFoAFT))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
@@ -2627,10 +2270,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev_wd, "OF") == 0)
+	else if (REGMATCH(RE_CREATE_TRIGGER_id_INSTEAD_OF))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", NULL};
@@ -2638,13 +2278,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0)) ||
-			 (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			  pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
-			  pg_strcasecmp(prev2_wd, "OF") == 0))
+	else if (REGMATCH(RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events))
 	{
 		static const char *const list_CREATETRIGGER2[] =
 		{"ON", "OR", NULL};
@@ -2656,27 +2290,14 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_CREATE_TRIGGER_id_BEFoAFToINSOF_events_ON))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OF") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "GRANT") == 0 && prev3_wd[0] == '\0') &&
-			 prev2_wd[0] != '\0')
+	else if (REGMATCH(RE_CREATE_TRIGGER_id_any_EXECUTE))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+	else if (REGMATCH(RE_CREATE_USERoROLEoGROUP_id))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2688,13 +2309,8 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATEROLE);
 	}
-
-/* CREATE ROLE,USER,GROUP <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			   pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	/* Successive options */
+	else if (REGMATCH(RE_CREATE_USERoROLEoGROUP_id_WITH))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2707,23 +2323,14 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
 	}
-
 	/*
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (REGMATCH(RE_CREATE_USERoROLEoGROUP_id_ENCoUNENC))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 pg_strcasecmp(prev_wd, "IN") == 0)
+	else if (REGMATCH(RE_CREATE_USERoROLEoGROUP_id_IN))
 	{
 		static const char *const list_CREATEROLE3[] =
 		{"GROUP", "ROLE", NULL};
@@ -2731,47 +2338,33 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATEROLE3);
 	}
 
+
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_CREATE_VIEW_id))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (REGMATCH(RE_CREATE_VIEW_id_AS))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (REGMATCH(RE_CREATE_MATERIALIZED))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_CREATE_MATVIEW_id))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (REGMATCH(RE_CREATE_MATVIEW_id_any_AS))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
+	else if (REGMATCH(RE_CREATE_EVENT))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (REGMATCH(RE_CREATE_EVENT_TRIGGER_id))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_CREATE_EVENT_TRIGGER_id_ON))
 	{
 		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
 		{"ddl_command_start", "ddl_command_end", "sql_drop", NULL};
@@ -2780,7 +2373,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DECLARE */
-	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
+	else if (REGMATCH(RE_DECLARE_id))
 	{
 		static const char *const list_DECLARE[] =
 		{"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
@@ -2789,7 +2382,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CURSOR */
-	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
+	else if (REGMATCH(RE_DECLARE_id_oBIN_oINSEN_oNOSCR_CURSOR))
 	{
 		static const char *const list_DECLARECURSOR[] =
 		{"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
@@ -2804,19 +2397,13 @@ psql_completion(const char *text, int start, int end)
 	 * Complete DELETE with FROM (only if the word before that is not "ON"
 	 * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
 	 */
-	else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-			   pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0))
+	else if (REGMATCH(RE_DELETE))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
+	else if (REGMATCH(RE_DELETE_FROM))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FROM") == 0)
+	else if (REGMATCH(RE_DELETE_FROM_id))
 	{
 		static const char *const list_DELETE[] =
 		{"USING", "WHERE", "SET", NULL};
@@ -2826,7 +2413,7 @@ psql_completion(const char *text, int start, int end)
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
+	else if (REGMATCH(RE_DISCARD))
 	{
 		static const char *const list_DISCARD[] =
 		{"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
@@ -2839,7 +2426,7 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (pg_strcasecmp(prev_wd, "DO") == 0)
+	else if (REGMATCH(RE_DO))
 	{
 		static const char *const list_DO[] =
 		{"LANGUAGE", NULL};
@@ -2849,49 +2436,16 @@ psql_completion(const char *text, int start, int end)
 
 /* DROP (when not the previous word) */
 	/* DROP AGGREGATE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+	else if (REGMATCH(RE_DROP_AGGREGATE_id))
 		COMPLETE_WITH_CONST("(");
 
 	/* DROP object with CASCADE / RESTRICT */
-	else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
-			   pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-			  prev_wd[strlen(prev_wd) - 1] == ')') ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			  pg_strcasecmp(prev2_wd, "TRIGGER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
-			   pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
-		)
-	{
-		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
-		{
+	else if (REGMATCH(RE_DROP_obj_1) ||
+			 REGMATCH(RE_DROP_obj_2) ||
+			 REGMATCH(RE_DROP_obj_3))
+	{
+		if (REGMATCH(RE_DROP_FUNCTION_id))
 			COMPLETE_WITH_CONST("(");
-		}
 		else
 		{
 			static const char *const list_DROPCR[] =
@@ -2900,8 +2454,7 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(list_DROPCR);
 		}
 	}
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_DROP_FOREIGN))
 	{
 		static const char *const drop_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2910,34 +2463,19 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
-	{
+	else if (REGMATCH(RE_DROP_MATERIALIZED))
 		COMPLETE_WITH_CONST("VIEW");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+	else if (REGMATCH(RE_DROP_MATERIALIZED_VIEW))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
 
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+	else if (REGMATCH(RE_DROP_AGGoFUNC_id_))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
 	/* DROP OWNED BY */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "OWNED") == 0)
+	else if (REGMATCH(RE_DROP_OWNED))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (REGMATCH(RE_DROP_OWNED_BY))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (REGMATCH(RE_DROP_TEXT_SEARARCH))
 	{
 
 		static const char *const list_ALTERTEXTSEARCH[] =
@@ -2947,21 +2485,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
+	else if (REGMATCH(RE_DROP_TRIGGER_id))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_DROP_TRIGGER_id_ON))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_DROP_TRIGGER_id_ON_id))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -2970,55 +2501,34 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
-	{
+	else if (REGMATCH(RE_DROP_EVENT))
 		COMPLETE_WITH_CONST("TRIGGER");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+	else if (REGMATCH(RE_DROP_EVENT_TRIGGER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	}
+
 
 	/* DROP POLICY <name>  */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "POLICY") == 0)
-	{
+	else if (REGMATCH(RE_DROP_POLICY))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
-	}
 	/* DROP POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
-	{
+	else if (REGMATCH(RE_DROP_POLICY_id))
 		COMPLETE_WITH_CONST("ON");
-	}
 	/* DROP POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_DROP_POLICY_id_ON))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
-	{
+	else if (REGMATCH(RE_DROP_RULE_id))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_DROP_RULE_id_ON))
 	{
-		completion_info_charp = prev2_wd;
+		completion_info_charp = CAPTURE(1);
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_DROP_RULE_id_ON_id))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -3027,8 +2537,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* EXECUTE, but not EXECUTE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (REGMATCH(RE_EXECUTE))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -3036,26 +2545,21 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
+	else if (REGMATCH(RE_EXPLAIN))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (REGMATCH(RE_EXPLAIN_ANALYZE))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0))
+	else if (REGMATCH(RE_EXPLAIN_oANALYZE_VERBOSE))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
@@ -3065,8 +2569,7 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (pg_strcasecmp(prev_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev_wd, "MOVE") == 0)
+	else if (REGMATCH(RE_FETCHoMOVE))
 	{
 		static const char *const list_FETCH1[] =
 		{"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL};
@@ -3074,8 +2577,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FETCH1);
 	}
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev2_wd, "MOVE") == 0)
+	else if (REGMATCH(RE_FETCHoMOVE_id))
 	{
 		static const char *const list_FETCH2[] =
 		{"ALL", "NEXT", "PRIOR", NULL};
@@ -3088,8 +2590,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev3_wd, "MOVE") == 0)
+	else if (REGMATCH(RE_FETCHoMOVE_any_any))
 	{
 		static const char *const list_FROMIN[] =
 		{"FROM", "IN", NULL};
@@ -3099,27 +2600,20 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+	else if (REGMATCH(RE_negCREATE_FOREIGN_DATA_WRAPPER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
 /* FOREIGN TABLE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
+	else if (REGMATCH(RE_negCREATE_FOREIGN_TABLE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SERVER") == 0)
+	else if (REGMATCH(RE_FOREIGN_SERVER))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
@@ -3141,31 +2635,14 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev2_wd, "REVOKE") == 0)
-	{
-		if (pg_strcasecmp(prev_wd, "SELECT") == 0
-			|| pg_strcasecmp(prev_wd, "INSERT") == 0
-			|| pg_strcasecmp(prev_wd, "UPDATE") == 0
-			|| pg_strcasecmp(prev_wd, "DELETE") == 0
-			|| pg_strcasecmp(prev_wd, "TRUNCATE") == 0
-			|| pg_strcasecmp(prev_wd, "REFERENCES") == 0
-			|| pg_strcasecmp(prev_wd, "TRIGGER") == 0
-			|| pg_strcasecmp(prev_wd, "CREATE") == 0
-			|| pg_strcasecmp(prev_wd, "CONNECT") == 0
-			|| pg_strcasecmp(prev_wd, "TEMPORARY") == 0
-			|| pg_strcasecmp(prev_wd, "TEMP") == 0
-			|| pg_strcasecmp(prev_wd, "EXECUTE") == 0
-			|| pg_strcasecmp(prev_wd, "USAGE") == 0
-			|| pg_strcasecmp(prev_wd, "ALL") == 0)
-			COMPLETE_WITH_CONST("ON");
+	else if (REGMATCH(RE_GRANToREVOKE_kwd))
+		COMPLETE_WITH_CONST("ON");
+	else if (REGMATCH(RE_GRANToREVOKE_id))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
+			COMPLETE_WITH_CONST("TO");
 		else
-		{
-			if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
-				COMPLETE_WITH_CONST("TO");
-			else
-				COMPLETE_WITH_CONST("FROM");
-		}
+			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/*
@@ -3179,9 +2656,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -3199,10 +2674,7 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALL") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_ALL))
 	{
 		static const char *const list_privilege_all[] =
 		{"FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA", "TABLES IN SCHEMA",
@@ -3211,10 +2683,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_privilege_all);
 	}
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_FOREIGN))
 	{
 		static const char *const list_privilege_foreign[] =
 		{"DATA WRAPPER", "SERVER", NULL};
@@ -3228,70 +2697,56 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON * " with "TO/FROM".
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_any))
 	{
-		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		bool is_grant = (pg_strcasecmp(CAPTURE(1), "GRANT") == 0);
+		char *any = CAPTURE(2);
+		if (pg_strcasecmp(any, "DATABASE") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
+		else if (pg_strcasecmp(any, "DOMAIN") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
+		else if (pg_strcasecmp(any, "FUNCTION") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
+		else if (pg_strcasecmp(any, "LANGUAGE") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (pg_strcasecmp(any, "SCHEMA") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "SEQUENCE") == 0)
+		else if (pg_strcasecmp(any, "SEQUENCE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLE") == 0)
+		else if (pg_strcasecmp(any, "TABLE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+		else if (pg_strcasecmp(any, "TABLESPACE") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
+		else if (pg_strcasecmp(any, "TYPE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+		else if (is_grant)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if ((pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev8_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev6_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev5_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
-	{
-		if (pg_strcasecmp(prev8_wd, "GRANT") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_ALL_any_IN_SCHEMA_id))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev7_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		if (pg_strcasecmp(prev7_wd, "GRANT") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_FDW_id))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev6_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_FOREIGN_SERVER_id))
 	{
-		if (pg_strcasecmp(prev6_wd, "GRANT") == 0)
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -3301,76 +2756,51 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (((pg_strcasecmp(prev9_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev5_wd, "GRANT") == 0) &&
-			  pg_strcasecmp(prev_wd, "TO") == 0) ||
-			 ((pg_strcasecmp(prev9_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev8_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev7_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
-			  pg_strcasecmp(prev_wd, "FROM") == 0))
+	else if (REGMATCH(RE_GRANToREVOKE_any_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
-	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
-		COMPLETE_WITH_CONST("TO");
-
-	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
-		COMPLETE_WITH_CONST("FROM");
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_ON_kwd_id))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
+			COMPLETE_WITH_CONST("TO");
+		else
+			COMPLETE_WITH_CONST("FROM");
+	}
 
 	/*
 	 * Complete "GRANT/REVOKE * TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
-	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
-	{
+	else if (REGMATCH(RE_GRANToREVOKE_kwd_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
-	}
 
 /* GROUP BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "GROUP") == 0)
+	else if (REGMATCH(RE_FROM_any_GROUP))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+	else if (REGMATCH(RE_IMPORT))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (REGMATCH(RE_IMPORT_FOREIGN))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (REGMATCH(RE_INSERT))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev_wd, "INTO") == 0)
+	else if (REGMATCH(RE_INSERT_INTO))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (REGMATCH(RE_INSERT_INTO_id_))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INTO") == 0)
+	else if (REGMATCH(RE_INSERT_INTO_id))
 	{
 		static const char *const list_INSERT[] =
 		{"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
@@ -3382,9 +2812,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 prev_wd[strlen(prev_wd) - 1] == ')')
+	else if (REGMATCH(RE_INSERT_INTO_id__))
 	{
 		static const char *const list_INSERT[] =
 		{"SELECT", "TABLE", "VALUES", NULL};
@@ -3393,33 +2821,25 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (pg_strcasecmp(prev_wd, "VALUES") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
+	else if (REGMATCH(RE_negDEFAULT_VALUES))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+	else if (REGMATCH(RE_LOCK))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LOCK") == 0)
+	else if (REGMATCH(RE_LOCK_TABLE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-			  pg_strcasecmp(prev_wd, "TABLE") != 0) ||
-			 (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "LOCK") == 0))
+	else if (REGMATCH(RE_LOCK_oTABLE_id))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (pg_strcasecmp(prev_wd, "IN") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "LOCK") == 0 ||
-			  (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev4_wd, "LOCK") == 0)))
+	else if (REGMATCH(RE_LOCK_oTABLE_id_IN))
 	{
 		static const char *const lock_modes[] =
 		{"ACCESS SHARE MODE",
@@ -3432,30 +2852,25 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* NOTIFY */
-	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
+	else if (REGMATCH(RE_NOTIFY))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
-	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+	else if (REGMATCH(RE_OPTIONS))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (REGMATCH(RE_OWNER_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ORDER") == 0)
+	else if (REGMATCH(RE_FROM_any_ORDER))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (REGMATCH(RE_FROM_any_ORDER_BY))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* PREPARE xx AS */
-	else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "PREPARE") == 0)
+	else if (REGMATCH(RE_PREPARE_any_AS))
 	{
 		static const char *const list_PREPARE[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -3469,116 +2884,78 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+	else if (REGMATCH(RE_REASSIGN))
 		COMPLETE_WITH_CONST("OWNED");
-	else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+	else if (REGMATCH(RE_REASSIGN_OWNED))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+	else if (REGMATCH(RE_REASSIGN_OWNED_BY))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+	else if (REGMATCH(RE_REASSIGN_OWNED_BY_id))
 		COMPLETE_WITH_CONST("TO");
-	else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
-			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+	else if (REGMATCH(RE_REASSIGN_OWNED_BY_id_TO))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+	else if (REGMATCH(RE_REFRESH))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_CONC))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_id))
 		COMPLETE_WITH_CONST("WITH");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_CONC_id))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_id_WITH))
 	{
 		static const char *const list_WITH_DATA[] =
 		{"NO DATA", "DATA", NULL};
 
 		COMPLETE_WITH_LIST(list_WITH_DATA);
 	}
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_CONC_id_WITH))
 		COMPLETE_WITH_CONST("DATA");
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (REGMATCH(RE_REFRESH_MATERIALIZED_VIEW_id_WITH_NO))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
+	else if (REGMATCH(RE_REINDEX))
 	{
 		static const char *const list_REINDEX[] =
 		{"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
 
 		COMPLETE_WITH_LIST(list_REINDEX);
 	}
-	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
+	else if (REGMATCH(RE_REINDEX_kwd))
 	{
-		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
+		char *kwd = CAPTURE(1);
+		if (pg_strcasecmp(kwd, "TABLE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
+		else if (pg_strcasecmp(kwd, "INDEX") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
+		else if (pg_strcasecmp(kwd, "SCHEMA") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
-				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
+		else if (pg_strcasecmp(kwd, "SYSTEM") == 0 ||
+				 pg_strcasecmp(kwd, "DATABASE") == 0)
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
 
 /* SECURITY LABEL */
-	else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (REGMATCH(RE_SECURITY))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "LABEL") == 0)
+	else if (REGMATCH(RE_SECURITY_LABEL))
 	{
 		static const char *const list_SECURITY_LABEL_preposition[] =
-		{"ON", "FOR"};
+		{"ON", "FOR", NULL};
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
 	}
-	else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (REGMATCH(RE_SECURITY_LABEL_FOR_id))
 		COMPLETE_WITH_CONST("ON");
-	else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev3_wd, "FOR") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0))
+	else if (REGMATCH(RE_SECURITY_LABEL_oFORname_ON))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
@@ -3587,9 +2964,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (REGMATCH(RE_SECURITY_LABEL_ON_kwd_id))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3597,78 +2972,37 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (REGMATCH(RE_SEToRESET))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+	else if (REGMATCH(RE_SHOW))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "START") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "WORK") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev4_wd, "SESSION") == 0
-				 && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0
-				 && pg_strcasecmp(prev2_wd, "AS") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
+	else if (REGMATCH(RE_SET_TRANSACTIONs))
 	{
 		static const char *const my_list[] =
 		{"ISOLATION LEVEL", "READ", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0
-			  || pg_strcasecmp(prev3_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev3_wd, "START") == 0
-			  || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0
-				  && pg_strcasecmp(prev3_wd, "AS") == 0))
-			 && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev2_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev_wd, "ISOLATION") == 0)
+	else if (REGMATCH(RE_SET_TRANSACTIONs_ISOLATION))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if ((pg_strcasecmp(prev4_wd, "SET") == 0
-			  || pg_strcasecmp(prev4_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev4_wd, "START") == 0
-			  || pg_strcasecmp(prev4_wd, "AS") == 0)
-			 && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev3_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev2_wd, "ISOLATION") == 0
-			 && pg_strcasecmp(prev_wd, "LEVEL") == 0)
+	else if (REGMATCH(RE_SET_TRANSACTIONs_ISOLATION_LEVEL))
 	{
 		static const char *const my_list[] =
 		{"READ", "REPEATABLE", "SERIALIZABLE", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (REGMATCH(RE_SET_TRANSACTIONs_ISOLATION_LEVEL_READ))
 	{
 		static const char *const my_list[] =
 		{"UNCOMMITTED", "COMMITTED", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPEATABLE") == 0)
+	else if (REGMATCH(RE_SET_TRANSACTIONs_ISOLATION_LEVEL_REPEATABLE))
 		COMPLETE_WITH_CONST("READ");
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BEGIN") == 0 ||
-			  pg_strcasecmp(prev3_wd, "START") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AS") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev2_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (REGMATCH(RE_SET_TRANSACTIONs_READ))
 	{
 		static const char *const my_list[] =
 		{"ONLY", "WRITE", NULL};
@@ -3676,14 +3010,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* SET CONSTRAINTS */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
+	else if (REGMATCH(RE_SET_CONSTRAINTS))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	}
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
+	else if (REGMATCH(RE_SET_CONSTRAINTS_id))
 	{
 		static const char *const constraint_list[] =
 		{"DEFERRED", "IMMEDIATE", NULL};
@@ -3691,12 +3023,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(constraint_list);
 	}
 	/* Complete SET ROLE */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "ROLE") == 0)
+	else if (REGMATCH(RE_SET_ROLE))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (REGMATCH(RE_SET_SESSION))
 	{
 		static const char *const my_list[] =
 		{"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL};
@@ -3704,29 +3034,20 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0
-			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
-			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
+	else if (REGMATCH(RE_SET_SESSION_AUTH))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (REGMATCH(RE_RESET_SESSION))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
-			 pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
-			 prev_wd[strlen(prev_wd) - 1] != ')' &&
-			 prev_wd[strlen(prev_wd) - 1] != '=' &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
+	else if (REGMATCH(RE_SET_var))
 		COMPLETE_WITH_CONST("TO");
 	/* Suggest possible variable values */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+	else if (REGMATCH(RE_SET_var_TOoEQ))
 	{
+		char *varname = CAPTURE(1);
 		/* special cased code for individual GUCs */
-		if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
+		if (pg_strcasecmp(varname, "DateStyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3736,7 +3057,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (pg_strcasecmp(prev2_wd, "search_path") == 0)
+		else if (pg_strcasecmp(varname, "search_path") == 0)
 		{
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
@@ -3747,13 +3068,13 @@ psql_completion(const char *text, int start, int end)
 		{
 			/* generic, type based, GUC support */
 
-			char	   *guctype = get_guctype(prev2_wd);
+			char	   *guctype = get_guctype(varname);
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
-				snprintf(querybuf, 1024, Query_for_enum, prev2_wd);
+				snprintf(querybuf, 1024, Query_for_enum, varname);
 				COMPLETE_WITH_QUERY(querybuf);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
@@ -3777,35 +3098,34 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* START TRANSACTION */
-	else if (pg_strcasecmp(prev_wd, "START") == 0)
+	else if (REGMATCH(RE_START))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (REGMATCH(RE_TABLE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
+	else if (REGMATCH(RE_any_TABLESAMPLE))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
 
-	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
+	else if (REGMATCH(RE_any_TABLESAMPLE_id))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
+	else if (REGMATCH(RE_TRUNCATE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
+	else if (REGMATCH(RE_UNLISTEN))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
+	else if (REGMATCH(RE_any_UPDATE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
+	else if (REGMATCH(RE_any_UPDATE_id))
 		COMPLETE_WITH_CONST("SET");
 
 	/*
@@ -3813,83 +3133,53 @@ psql_completion(const char *text, int start, int end)
 	 * word) the word before it was (hopefully) a table name and we'll now
 	 * make a list of attributes.
 	 */
-	else if (pg_strcasecmp(prev_wd, "SET") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (REGMATCH(RE_any_id_SET))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* UPDATE xx SET yy = */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+	else if (REGMATCH(RE_any_UPDATE_id_SET_id))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+	else if (REGMATCH(RE_CRToALToDRP_USER_MAPPING))
 		COMPLETE_WITH_CONST("FOR");
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (REGMATCH(RE_CREATE_USER_MAPPING_FOR))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (REGMATCH(RE_ALToDRP_USER_MAPPING_FOR))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (REGMATCH(RE_CRToALToDRP_USER_MAPPING_FOR_id))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
+	else if (REGMATCH(RE_VACUUM))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
+	else if (REGMATCH(RE_VACUUM_FULLoFREEZE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (REGMATCH(RE_VACUUM_FULLoFREEZE_ANALYZE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (REGMATCH(RE_VACUUM_FULLoFREEZE_VERBOSE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+	else if (REGMATCH(RE_VACUUM_VERBOSE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (REGMATCH(RE_VACUUM_ANALYZE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+	else if (REGMATCH(RE_any_ANLVRBoVRBANL))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3898,120 +3188,116 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (REGMATCH(RE_WITH))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
-	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (REGMATCH(RE_any_ANALYZE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (REGMATCH(RE_any_id_WHERE))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
-			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+	else if (REGMATCH(RE_COPY_id_FROM))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+	else if (REGMATCH(RE_any_JOIN))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (REGMATCH(RE_meta_question)) /* \? */
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (REGMATCH(RE_meta_CONNECT)) /* \connect */
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
+	else if (REGMATCH(RE_meta_CONNECT_word)) /* \connect []*/
 	{
-		if (!recognized_connection_string(prev_wd))
+		if (!recognized_connection_string(CAPTURE(1)))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (REGMATCH(RE_meta_da))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (REGMATCH(RE_meta_db))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (REGMATCH(RE_meta_dD))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (REGMATCH(RE_meta_des))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (REGMATCH(RE_meta_deu))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (REGMATCH(RE_meta_dew))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (REGMATCH(RE_meta_df))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (REGMATCH(RE_meta_dFd))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (REGMATCH(RE_meta_dFp))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (REGMATCH(RE_meta_dFt))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (REGMATCH(RE_meta_dF))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (REGMATCH(RE_meta_di))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (REGMATCH(RE_meta_dL))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (REGMATCH(RE_meta_dn))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (REGMATCH(RE_meta_dpOz))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (REGMATCH(RE_meta_ds))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (REGMATCH(RE_meta_dt))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (REGMATCH(RE_meta_dT))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (REGMATCH(RE_meta_duOdg))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (REGMATCH(RE_meta_dv))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (REGMATCH(RE_meta_dx))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (REGMATCH(RE_meta_dm))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (REGMATCH(RE_meta_dE))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (REGMATCH(RE_meta_dy))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (REGMATCH(RE_meta_d))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (REGMATCH(RE_meta_ef))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (REGMATCH(RE_meta_ev))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (REGMATCH(RE_meta_encoding))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (REGMATCH(RE_meta_h))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (REGMATCH(RE_meta_password))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (REGMATCH(RE_meta_pset))
+
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -4022,9 +3308,10 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev2_wd, "\\pset") == 0)
+	else if (REGMATCH(RE_meta_pset_id))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		char *var = CAPTURE(1);
+		if (strcmp(var, "format") == 0)
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -4032,16 +3319,16 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (strcmp(var, "linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (strcmp(var, "unicode_border_linestyle") == 0 ||
+				 strcmp(var, "unicode_column_linestyle") == 0 ||
+				 strcmp(var, "unicode_header_linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -4050,72 +3337,73 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (REGMATCH(RE_meta_unset))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (REGMATCH(RE_meta_set))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (strcmp(prev2_wd, "\\set") == 0)
+	else if (REGMATCH(RE_meta_set_id))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
+		char *var = CAPTURE(1);
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		if (strcmp(var, "AUTOCOMMIT") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (strcmp(var, "COMP_KEYWORD_CASE") == 0)
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (strcmp(var, "ECHO") == 0)
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (strcmp(var, "ECHO_HIDDEN") == 0)
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (strcmp(var, "HISTCONTROL") == 0)
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (strcmp(var, "ON_ERROR_ROLLBACK") == 0)
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (strcmp(var, "ON_ERROR_STOP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (strcmp(var, "QUIET") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (strcmp(var, "SHOW_CONTEXT") == 0)
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (strcmp(var, "SINGLELINE") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (strcmp(var, "SINGLESTEP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (strcmp(var, "VERBOSITY") == 0)
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -4123,20 +3411,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (REGMATCH(RE_meta_sf))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (REGMATCH(RE_meta_sv))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (REGMATCH(RE_meta_FILECMDS))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
@@ -4147,13 +3426,14 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else
+	else if (REGMATCH(RE_AnySingleWord))
 	{
 		int			i;
+		char *thing = CAPTURE(1);
 
 		for (i = 0; words_after_create[i].name; i++)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
+			if (pg_strcasecmp(thing, words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query);
@@ -4178,14 +3458,6 @@ psql_completion(const char *text, int start, int end)
 #endif
 	}
 
-	/* free storage */
-	{
-		int			i;
-
-		for (i = 0; i < lengthof(previous_words); i++)
-			free(previous_words[i]);
-	}
-
 	/* Return our Grand List O' Matches */
 	return matches;
 }
@@ -4763,91 +4035,6 @@ exec_query(const char *query)
 
 
 /*
- * Return the nwords word(s) before point.  Words are returned right to left,
- * that is, previous_words[0] gets the last word before point.
- * If we run out of words, remaining array elements are set to empty strings.
- * Each array element is filled with a malloc'd string.
- */
-static void
-get_previous_words(int point, char **previous_words, int nwords)
-{
-	const char *buf = rl_line_buffer;	/* alias */
-	int			i;
-
-	/* first we look for a non-word char before the current point */
-	for (i = point - 1; i >= 0; i--)
-		if (strchr(WORD_BREAKS, buf[i]))
-			break;
-	point = i;
-
-	while (nwords-- > 0)
-	{
-		int			start,
-					end;
-		char	   *s;
-
-		/* now find the first non-space which then constitutes the end */
-		end = -1;
-		for (i = point; i >= 0; i--)
-		{
-			if (!isspace((unsigned char) buf[i]))
-			{
-				end = i;
-				break;
-			}
-		}
-
-		/*
-		 * If no end found we return an empty string, because there is no word
-		 * before the point
-		 */
-		if (end < 0)
-		{
-			point = end;
-			s = pg_strdup("");
-		}
-		else
-		{
-			/*
-			 * Otherwise we now look for the start. The start is either the
-			 * last character before any word-break character going backwards
-			 * from the end, or it's simply character 0. We also handle open
-			 * quotes and parentheses.
-			 */
-			bool		inquotes = false;
-			int			parentheses = 0;
-
-			for (start = end; start > 0; start--)
-			{
-				if (buf[start] == '"')
-					inquotes = !inquotes;
-				if (!inquotes)
-				{
-					if (buf[start] == ')')
-						parentheses++;
-					else if (buf[start] == '(')
-					{
-						if (--parentheses <= 0)
-							break;
-					}
-					else if (parentheses == 0 &&
-							 strchr(WORD_BREAKS, buf[start - 1]))
-						break;
-				}
-			}
-
-			point = start - 1;
-
-			/* make a copy of chars from start to end inclusive */
-			s = pg_malloc(end - start + 2);
-			strlcpy(s, &buf[start], end - start + 2);
-		}
-
-		*previous_words++ = s;
-	}
-}
-
-/*
  * Look up the type for the GUC variable with the passed name.
  *
  * Returns NULL if the variable is unknown. Otherwise the returned string,
-- 
1.8.3.1

#33Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Kyotaro HORIGUCHI (#32)
Re: Making tab-complete.c easier to maintain

On Thu, Nov 12, 2015 at 5:16 PM, Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello. How about regular expressions?

I've been thinking of better mechanism for tab-compltion for
these days since I found some bugs in it.

At Fri, 23 Oct 2015 14:50:58 -0300, Alvaro Herrera <
alvherre@2ndquadrant.com> wrote in <20151023175058.GA3391@alvherre.pgsql>

Jeff Janes wrote:

For the bigger picture, I don't think we should not apply this patch

simply

because there is something even better we might theoretically do at

some

point in the future.

Agreed.

Auto-generating from grammer should be the ultimate solution but
I don't think it will be available. But still I found that the
word-splitting-then-match-word-by-word-for-each-matching is
terriblly unmaintainable and poorly capable. So, how about
regular expressions?

I tried to use pg_regex in frontend and found that it is easily
doable. As a proof of the concept, the two patches attached to
this message does that changes.

1. 0001-Allow-regex-module-to-be-used-outside-server.patch

This small change makes pg_regex possible to be used in
frontend.

2. 0002-Replace-previous-matching-rule-with-regexps.patch

Simply replaces existing matching rules almost one-by-one with
regular expression matches.

I made these patches not to change the behavior except inevitable
ones.

We would have far powerful matching capability using regular
expressions and it makes tab-complete.c look simpler. On the
other hand, regular expressions - which are stashed away into new
file by this patch - is a chunk of complexity and (also) error
prone. For all that I think this is better than the current
situation in terms of maintainability and capability.

This should look stupid because it really be replaced stupidly
and of course this can be more sane/effective/maintainable by
refactoring. But before that issue, I'm not confident at all that
this is really a alternative with *gigantic* improvement.

Any opinions?

It's an interesting idea to use regular expressions, but it's a shame to
move the patterns so far away from the actions they trigger.

--
Thomas Munro
http://www.enterprisedb.com

#34Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#32)
Re: Making tab-complete.c easier to maintain

On Thu, Nov 12, 2015 at 1:16 PM, Kyotaro HORIGUCHI wrote:

1. 0001-Allow-regex-module-to-be-used-outside-server.patch

This small change makes pg_regex possible to be used in
frontend.

This is generic enough to live in src/common, then psql would directly
reuse it using lpgcommon.

2. 0002-Replace-previous-matching-rule-with-regexps.patch

Simply replaces existing matching rules almost one-by-one with
regular expression matches.

This makes the situation messier. At least with Thomas' patch one can
immediately know the list of words that are being matched for a given
code path while with this patch we need to have a look to the regex
where they are list. And this would get more and more complicated with
new commands added.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Kyotaro HORIGUCHI (#32)
Re: Making tab-complete.c easier to maintain

Kyotaro HORIGUCHI wrote:

Auto-generating from grammer should be the ultimate solution but
I don't think it will be available. But still I found that the
word-splitting-then-match-word-by-word-for-each-matching is
terriblly unmaintainable and poorly capable. So, how about
regular expressions?

I don't think this is an improvement. It seems to me that a lot more
work is required to maintain these regular expressions, which while
straightforward are not entirely trivial (harder to read).

If we could come up with a reasonable format that is pre-processed to a
regexp, that might be better. I think Thomas' proposed patch is closer
to that than Horiguchi-san's.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Thomas Munro (#31)
Re: Making tab-complete.c easier to maintain

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Alvaro Herrera (#28)
Re: Making tab-complete.c easier to maintain

Hello, thank you for many valuable opinions.

I am convinced that bare regular expressions cannot be applied
here:)

At Mon, 16 Nov 2015 18:59:06 +1300, Thomas Munro <thomas.munro@enterprisedb.com> wrote in <CAEepm=26AR3drcAUW+paLvJmXS6WV36kk6e72GWS4rAykOueOg@mail.gmail.com>

It's an interesting idea to use regular expressions, but it's a shame to
move the patterns so far away from the actions they trigger.

Yes, I agree. RE is powerful tool but too complicated. Some
intermediate expression would be needed but it also adds
different kind of complexity.

At Mon, 16 Nov 2015 23:09:52 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqR+uQj=QvXDbookA5OmbmCR6So2fJxCLbh6gJ6OvacDLA@mail.gmail.com>

This makes the situation messier. At least with Thomas' patch one can
immediately know the list of words that are being matched for a given
code path while with this patch we need to have a look to the regex
where they are list. And this would get more and more complicated with
new commands added.

Hmm, so I named the enum items to reflect its pattern but even
though I also think it is one of the problem of using regular
expressions.

At Mon, 16 Nov 2015 12:16:07 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20151116151606.GW614468@alvherre.pgsql>

I don't think this is an improvement. It seems to me that a lot more
work is required to maintain these regular expressions, which while
straightforward are not entirely trivial (harder to read).

If we could come up with a reasonable format that is pre-processed to a
regexp, that might be better. I think Thomas' proposed patch is closer
to that than Horiguchi-san's.

I aimed that matching mechanism can handle optional elements in
syntexes more robustly. But as all you are saying, bare regular
expression is too complex here.

regares,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Alvaro Herrera (#36)
Re: Making tab-complete.c easier to maintain

Hello,

At Mon, 16 Nov 2015 12:19:23 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20151116151923.GX614468@alvherre.pgsql>

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

Does it looks like this?

if (Match(BOL, "ALTER", "TABLE", EOL)) ...

It would be doable giving special meaning to
word_matches(BOL/EOL, *_wd). And I give +1 to that than having so
many similar macros.

The following is my delusion..

It could develop to some mini-laguages like the following, which
is a kind of subset of regular expressions, that is powerful than
current mechanism but not meesier than regular expressions by
narrowing its functionarity. Addition to that the custom minilang
could have specilized functionarity for matching SQL statements.

if (Match("^ALTER TABLE \id$"))...

It would be nice to have tokens to match to optional words.

if (Match("^ALTER TABLE(IF EXISTS) \id$"))...
if (Match("CREATE(OR REPLACE)FUNCTION \id (\CSL)$")

Mmm. this might be another kind of complexity? This is also
accomplished by multiple matching descriptions.

if (Match("^,ALTER,TABLE,\id,$") ||
Match("^,ALTER,TABLE,IF,EXISTS,\id,$"))...

Interpreting this kind of mini-language into regular expressions
could be doable..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#32)
2 attachment(s)
Re: Making tab-complete.c easier to maintain

Hello, I tried to implement the mini-language, which is a
simplified reglar expression for this specific use.

As a ultra-POC, the attached patch has very ad-hoc preprocess
function and does on-the-fly preprocessing, compilation then
execution of regular expression. And it is applied to only the
first ten or so matchings in psql_completion().

The first attachment is the same with that of previous patchset.

Every matching line looks like the following,

else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](.."))
COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));

As a temporary desin, "{}" means grouping, "|" means alternatives,
"[]" means capture and '#id' means any identifier.

The largest problem of this would be its computation cost:( This
in turn might be too slow if about three hundred matches run...

I see no straight-forward way to preprocess these strings.. A
possible solution would be extracting these strings then
auto-generate a c-srouce to preprocess them. And when running,
RM() retrieves the compiled regular expression using the string
as the key.

At Tue, 17 Nov 2015 15:35:43 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20151117.153543.183036803.horiguchi.kyotaro@lab.ntt.co.jp>

At Mon, 16 Nov 2015 12:16:07 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20151116151606.GW614468@alvherre.pgsql>

I don't think this is an improvement. It seems to me that a lot more
work is required to maintain these regular expressions, which while
straightforward are not entirely trivial (harder to read).

If we could come up with a reasonable format that is pre-processed to a
regexp, that might be better. I think Thomas' proposed patch is closer
to that than Horiguchi-san's.

I aimed that matching mechanism can handle optional elements in
syntexes more robustly. But as all you are saying, bare regular
expression is too complex here.

At Tue, 17 Nov 2015 16:09:25 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20151117.160925.45883793.horiguchi.kyotaro@lab.ntt.co.jp>

if (Match("^,ALTER,TABLE,\id,$") ||
Match("^,ALTER,TABLE,IF,EXISTS,\id,$"))...

Interpreting this kind of mini-language into regular expressions
could be doable..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Allow-regex-module-to-be-used-outside-server.patchtext/x-patch; charset=us-asciiDownload
>From cdc0b9cce43e38463af0b2b7ad4a0181f41995a2 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:03:18 +0900
Subject: [PATCH 1/2] Allow regex module to be used outside server.

To make regular expression available on frontend, enable pg_regex
module and the files included from it to be detached from the features
not available when used outside backend.
---
 src/backend/regex/regc_pg_locale.c |  7 +++++++
 src/backend/regex/regcomp.c        | 12 ++++++++++++
 src/include/regex/regcustom.h      |  6 ++++++
 src/include/utils/pg_locale.h      |  7 +++++--
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index b707b06..a631ac2 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -220,6 +220,13 @@ static const unsigned char pg_char_properties[128] = {
 };
 
 
+/* Get rid of using server-side feature elsewhere of server. */
+#ifdef FRONTEND
+#define lc_ctype_is_c(col) (true)
+#define GetDatabaseEncoding() PG_UTF8
+#define ereport(x,...) exit(1)
+#endif
+
 /*
  * pg_set_regex_collation: set collation for these functions to obey
  *
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index a165b3b..b35ccb4 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -2040,11 +2040,17 @@ rfree(regex_t *re)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rcancelrequested(void)
 {
+#ifndef FRONTEND
 	return InterruptPending && (QueryCancelPending || ProcDiePending);
+#else
+	return 0;
+#endif
 }
 
 /*
@@ -2056,11 +2062,17 @@ rcancelrequested(void)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rstacktoodeep(void)
 {
+#ifndef FRONTEND
 	return stack_is_too_deep();
+#else
+	return 0;
+#endif
 }
 
 #ifdef REG_DEBUG
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index dbb461a..ffc4031 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -29,7 +29,11 @@
  */
 
 /* headers if any */
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
 #include "postgres.h"
+#endif
 
 #include <ctype.h>
 #include <limits.h>
@@ -53,7 +57,9 @@
 #define MALLOC(n)		malloc(n)
 #define FREE(p)			free(VS(p))
 #define REALLOC(p,n)	realloc(VS(p),n)
+#ifndef assert
 #define assert(x)		Assert(x)
+#endif
 
 /* internal character type and related */
 typedef pg_wchar chr;			/* the type itself */
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 8e91033..d71ec07 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,14 +17,16 @@
 #include <xlocale.h>
 #endif
 
+/* Don't look GUCs elsewhere of server */
+#ifndef FRONTEND
 #include "utils/guc.h"
 
-
 /* GUC settings */
 extern char *locale_messages;
 extern char *locale_monetary;
 extern char *locale_numeric;
 extern char *locale_time;
+#endif
 
 /* lc_time localization cache */
 extern char *localized_abbrev_days[];
@@ -32,7 +34,7 @@ extern char *localized_full_days[];
 extern char *localized_abbrev_months[];
 extern char *localized_full_months[];
 
-
+#ifndef FRONTEND
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
@@ -41,6 +43,7 @@ extern bool check_locale_numeric(char **newval, void **extra, GucSource source);
 extern void assign_locale_numeric(const char *newval, void *extra);
 extern bool check_locale_time(char **newval, void **extra, GucSource source);
 extern void assign_locale_time(const char *newval, void *extra);
+#endif
 
 extern bool check_locale(int category, const char *locale, char **canonname);
 extern char *pg_perm_setlocale(int category, const char *locale);
-- 
1.8.3.1

0002-Replace-previous-matching-rule-with-regexps-take-2.patchtext/x-patch; charset=us-asciiDownload
>From 08965765409f0a339058e12b3e7f55a7c8792e6c Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:18:18 +0900
Subject: [PATCH 2/2] Replace previous matching rule with regexps take 2.

This patch simply replaces previous matching rule with regular
expressions. As a POC, this patch repalces only first several entries.
---
 src/bin/psql/Makefile       |  15 ++-
 src/bin/psql/tab-complete.c | 275 ++++++++++++++++++++++++++++++++++----------
 2 files changed, 226 insertions(+), 64 deletions(-)

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..bd0b5cc 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -23,12 +23,23 @@ override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/p
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
+	sql_help.o regcomp.o regexec.o regfree.o wstrncmp.o \
 	$(WIN32RES)
 
-
+CFLAGS+= -DFRONTEND
 all: psql
 
+
+regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c regfree.c: % :$(top_srcdir)/src/backend/regex/%
+	rm -f $@ && $(LN_S) $< .
+
+wstrncmp.c: % : $(top_srcdir)/src/backend/utils/mb/%
+	rm -f $@ && $(LN_S) $< .
+
+regcomp.o regexec.o regfree.o:regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c regfree.c wstrncmp.c
+
+tab-complete.o: tab-complete.c
+
 psql: $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..7941020 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -54,6 +54,8 @@
 #include "common.h"
 #include "settings.h"
 #include "stringutils.h"
+#include "regex/regex.h"
+#include "catalog/pg_collation.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -792,6 +794,18 @@ typedef struct
 #define THING_NO_DROP		(1 << 1)	/* should not show up after DROP */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP)
 
+#define CAPBUFLEN 128
+#define MATCHNUM 5
+
+#define RM(pat) (rematch(linebufw, wstrlen, pat, rmatches))
+#define MATCHBEG(n) (rmatches[n].rm_so)
+#define MATCHEND(n) (rmatches[n].rm_eo)
+#define MATCHLEN(n) (MATCHEND(n) - MATCHBEG(n))
+#define CAPCPYLEN(n)(MATCHLEN(n)<CAPBUFLEN ? MATCHLEN(n):CAPBUFLEN - 1)
+#define CAPTURE0(n) (strncpy(capbuf[n], linebuf + MATCHBEG(n), CAPCPYLEN(n)),capbuf[n][CAPCPYLEN(n)] = 0)
+#define CAPTURE(n)  (CAPTURE0(n), capbuf[n])
+#define CAPBUF(n)   (capbuf[n])
+
 static const pgsql_thing_t words_after_create[] = {
 	{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL},		/* Casts have complex structures for names, so
@@ -842,7 +856,6 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL}						/* end of list */
 };
 
-
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
 static char *create_command_generator(const char *text, int state);
@@ -868,11 +881,28 @@ static void get_previous_words(int point, char **previous_words, int nwords);
 
 static char *get_guctype(const char *varname);
 
+static bool rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches);
+
+
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
+static int
+pg_ascii2wchar_with_len(const char *from, pg_wchar *to, int len)
+{
+	int			cnt = 0;
+
+	while (len > 0 && *from)
+	{
+		*to++ = *from++;
+		len--;
+		cnt++;
+	}
+	*to = 0;
+	return cnt;
+}
 
 /*
  * Initialize the readline library for our purposes.
@@ -893,6 +923,31 @@ initialize_readline(void)
 	 */
 }
 
+static pg_wchar *
+expand_wchar_buffer(pg_wchar *p, int *buflen, int newlen, int limit)
+{
+	pg_wchar *ret = p;
+	int       len1 = newlen - 1;
+
+	Assert(newlen > 0);
+	if (newlen >= *buflen)
+	{
+		int mask;
+
+		/* Allocate in size of 2^n. Minimum 256 wchars */
+		for (mask = 255 ; mask < limit && (len1 & mask) != len1 ;
+			 mask = (mask << 1) | 1);
+		
+		/* mask is (<new buffer length> - 1) here */
+		if (mask >= limit) return NULL; /* Exceeds limit */
+		
+		*buflen = mask + 1;
+		
+		ret = pg_realloc(p, (*buflen) * sizeof(pg_wchar));
+	}		
+
+	return ret;
+}
 
 /*
  * The completion function.
@@ -905,6 +960,13 @@ initialize_readline(void)
 static char **
 psql_completion(const char *text, int start, int end)
 {
+	const char *linebuf = rl_line_buffer;
+	static pg_wchar	 *linebufw = NULL;
+	static int	     linebufwlen = 0;
+	int len, wstrlen;
+	regmatch_t rmatches[MATCHNUM];
+	char capbuf[MATCHNUM][CAPBUFLEN];
+
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
@@ -967,6 +1029,10 @@ psql_completion(const char *text, int start, int end)
 	 * probably intending to type.
 	 */
 	get_previous_words(start, previous_words, lengthof(previous_words));
+	len = strlen(linebuf);
+	/* Expand string buffer if needed */
+	linebufw = expand_wchar_buffer(linebufw, &linebufwlen, len + 1, 2048); 
+	wstrlen = pg_ascii2wchar_with_len(linebuf, linebufw, strlen(linebuf));
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -984,36 +1050,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (RM("^"))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (RM("^CREATE "))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (RM("^DROP "))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (RM("^ALTER TABLE "))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (RM("^ALTER "))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1026,9 +1087,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (RM("ALL IN TABLESPACE #id "))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1036,38 +1095,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (RM("ALL IN TABLESPACE #id OWNED BY "))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (RM("^ALTER {AGGREGATE|FUNCTION} #id "))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](.."))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+	else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
+		static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
-		else
-			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+	else if (RM("ALTER SCHEMA #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1076,8 +1121,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+	else if (RM("ALTER COLLATION #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1086,8 +1130,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
+	else if (RM("ALTER CONVERSION #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1096,8 +1139,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (RM("ALTER DATABASE #id "))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1107,17 +1149,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (RM("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (RM("ALTER EVENT TRIGGER #id "))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
 		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
@@ -1126,10 +1164,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (RM("ALTER EVENT TRIGGER #id ENABLE "))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
 		{"REPLICA", "ALWAYS", NULL};
@@ -1138,8 +1173,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (RM("ALTER EXTENSION #id "))
 	{
 		static const char *const list_ALTEREXTENSION[] =
 		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
@@ -1148,8 +1182,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (RM("ALTER FOREIGN "))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -1158,10 +1191,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (RM("ALTER FOREIGN DATA WRAPPER #id "))
 	{
 		static const char *const list_ALTER_FDW[] =
 		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
@@ -4185,12 +4215,10 @@ psql_completion(const char *text, int start, int end)
 		for (i = 0; i < lengthof(previous_words); i++)
 			free(previous_words[i]);
 	}
-
 	/* Return our Grand List O' Matches */
 	return matches;
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4923,4 +4951,127 @@ dequote_file_name(char *text, char quote_char)
 }
 #endif   /* NOT_USED */
 
+//#define WB  "[\\t\\n@$><=;|&{\\(\\) ]"				/* WORD BREAKS */
+//#define NWB "[^\\t\\n@$><=;|&{\\(\\) ]"				/* ^WORD BREAKS */
+#define WB  "[\\t\\n@$><=;|&{ ]"				/* WORD BREAKS */
+#define NWB "[^\\t\\n@$><=;|&{ ]"				/* ^WORD BREAKS */
+#define PB  "^(?:.*;)?\\s*"			/* bind to the beginning of the line */
+#define PM  "^(?:.*"NWB WB")?\\s*"					/* floating head */
+#define ID  NWB"+"									/* identifier */
+#define KWD "\\w+"									/* any keywords */
+
+bool
+rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches)
+{
+	pg_wchar rewstr[4096];
+	char restr[4096];
+	char *p, *rp;
+	int relen;
+	int ret;
+	regex_t re;
+	bool prev_is_ws = false;
+	char *NORMAL_TAIL = NWB"*$";
+	char *INPAREN_TAIL = "[^\\)]*$";
+	char *tail;
+
+	restr[0] = 0;
+	if (*pat != '^')
+		strcpy(restr, PM);
+	rp = restr + strlen(restr);
+	for (p = pat ; *p ; p++)
+	{
+		tail = NORMAL_TAIL;
+		switch (*p)
+		{
+		case '.':
+			if (strncmp(p+1, ".", 1) == 0)
+			{
+				p++;
+				strcpy(rp, "[^\\)]*");
+			}
+			else
+				exit(1);
+			break;
+		case '#':
+			if (strncmp(p+1, "id", 2) == 0)
+			{
+				p += 2;
+				if (!prev_is_ws)
+					strcpy(rp, WB"+");
+				strcpy(rp, ID NWB "+");
+				prev_is_ws = false;
+			}
+			else
+				exit(1);
+			break;
+		case '^':
+			strcpy(rp, "^(?:.*;)?\\s*");
+			prev_is_ws = true;
+			break;
+		case '$':
+			strcpy(rp, NWB"*$");
+			prev_is_ws = false;
+			break;
+		case '?':
+			strcpy(rp,"?");
+			prev_is_ws = false;
+			break;
+		case ' ':
+			if (!prev_is_ws)
+				strcpy(rp, WB"+");
+			prev_is_ws = true;
+			break;
+		case '(':
+			strcpy(rp, WB"*\\([\\)]*");
+			prev_is_ws = true;
+			tail = INPAREN_TAIL;
+			break;
+		case ')':
+			strcpy(rp, WB"*\\)"WB"*");
+			prev_is_ws = true;
+			break;
+		case '[':
+			strcpy(rp, "(");
+			break;
+		case ']':
+			strcpy(rp, ")");
+			break;
+		case '{':
+			strcpy(rp, "(?:");
+			break;
+		case '}':
+			strcpy(rp, ")");
+			break;
+		case '|':
+			strcpy(rp, "|");
+			break;
+		default:
+			if (*p)
+			{
+				if (!prev_is_ws)
+					strcpy(rp, WB"+");
+				while(isalnum(*p))
+					*rp++ = *p++;
+				*rp = 0;
+				p--;
+				prev_is_ws = false;
+			}
+		}
+		while (*rp) rp++;
+		/* No overrun check! */
+	}
+	strcpy(rp, tail);
+
+	relen = pg_ascii2wchar_with_len(restr, rewstr, strlen(restr));
+	ret = pg_regcomp(&re, rewstr, relen, REG_ADVANCED|REG_ICASE,
+					 C_COLLATION_OID);
+	if (ret != 0)
+		exit(1);
+
+	ret = pg_regexec(&re, line, linelen, 0, NULL, MATCHNUM, rmatches, 0) == 0;
+	pg_regfree(&re);
+
+	return ret;
+}
+
 #endif   /* USE_READLINE */
-- 
1.8.3.1

#40Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#39)
5 attachment(s)
Re: Making tab-complete.c easier to maintain

Hello, the attached are a patchset to introduce a special
matching expression which simplifies matching descriptions. The
result looks as if the comments in the previous implement run
as-is.

0001-Allow-regex-module-to-be-used-outside-server.patch

A small patch to allow pg_regex to be used outside backend. The
same with the previous one.

0002-Simplify-the-usages-of-COMPLETE_WITH_QUERY.patch

This is also the same with the previous one.

0003-Replace-previous-matching-rule-with-regexps-take-2.patch

Modifies to use this matching minilang. No bare regular
expression is seen for every matching. The difference from the
previous one is that sources for other than regcomp.o are no
longer symlinked to psql's directory. Compiled regular
expressions are cached until psql ends. The function patcomp is
the central of the messy of this patchset.

This cannot be compiled on Windows, I think.

0004-Remove-less-informative-comments.patch

Remove comments that no longer has reason to be placed there.

0005-Merge-mergable-completions.patch

Merge some of the matchings that are simply mergable by using
this minilang.

What do you think about this solution?

At Tue, 17 Nov 2015 19:25:24 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20151117.192524.95155716.horiguchi.kyotaro@lab.ntt.co.jp>

Hello, I tried to implement the mini-language, which is a
simplified reglar expression for this specific use.

As a ultra-POC, the attached patch has very ad-hoc preprocess
function and does on-the-fly preprocessing, compilation then
execution of regular expression. And it is applied to only the
first ten or so matchings in psql_completion().

The first attachment is the same with that of previous patchset.

Every matching line looks like the following,

else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](.."))
COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Allow-regex-module-to-be-used-outside-server.patchtext/x-patch; charset=us-asciiDownload
>From 8ba1b44ca23283ed2814ba237d48bfaba2f9d9eb Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:03:18 +0900
Subject: [PATCH 1/8] Allow regex module to be used outside server.

To make regular expression available on frontend, enable pg_regex
module and the files included from it to be detached from the features
not available when used outside backend.
---
 src/backend/regex/regc_pg_locale.c |  7 +++++++
 src/backend/regex/regcomp.c        | 12 ++++++++++++
 src/include/regex/regcustom.h      |  6 ++++++
 src/include/utils/pg_locale.h      |  7 +++++--
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index b707b06..a631ac2 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -220,6 +220,13 @@ static const unsigned char pg_char_properties[128] = {
 };
 
 
+/* Get rid of using server-side feature elsewhere of server. */
+#ifdef FRONTEND
+#define lc_ctype_is_c(col) (true)
+#define GetDatabaseEncoding() PG_UTF8
+#define ereport(x,...) exit(1)
+#endif
+
 /*
  * pg_set_regex_collation: set collation for these functions to obey
  *
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index a165b3b..b35ccb4 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -2040,11 +2040,17 @@ rfree(regex_t *re)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rcancelrequested(void)
 {
+#ifndef FRONTEND
 	return InterruptPending && (QueryCancelPending || ProcDiePending);
+#else
+	return 0;
+#endif
 }
 
 /*
@@ -2056,11 +2062,17 @@ rcancelrequested(void)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rstacktoodeep(void)
 {
+#ifndef FRONTEND
 	return stack_is_too_deep();
+#else
+	return 0;
+#endif
 }
 
 #ifdef REG_DEBUG
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index dbb461a..ffc4031 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -29,7 +29,11 @@
  */
 
 /* headers if any */
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
 #include "postgres.h"
+#endif
 
 #include <ctype.h>
 #include <limits.h>
@@ -53,7 +57,9 @@
 #define MALLOC(n)		malloc(n)
 #define FREE(p)			free(VS(p))
 #define REALLOC(p,n)	realloc(VS(p),n)
+#ifndef assert
 #define assert(x)		Assert(x)
+#endif
 
 /* internal character type and related */
 typedef pg_wchar chr;			/* the type itself */
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 8e91033..d71ec07 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,14 +17,16 @@
 #include <xlocale.h>
 #endif
 
+/* Don't look GUCs elsewhere of server */
+#ifndef FRONTEND
 #include "utils/guc.h"
 
-
 /* GUC settings */
 extern char *locale_messages;
 extern char *locale_monetary;
 extern char *locale_numeric;
 extern char *locale_time;
+#endif
 
 /* lc_time localization cache */
 extern char *localized_abbrev_days[];
@@ -32,7 +34,7 @@ extern char *localized_full_days[];
 extern char *localized_abbrev_months[];
 extern char *localized_full_months[];
 
-
+#ifndef FRONTEND
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
@@ -41,6 +43,7 @@ extern bool check_locale_numeric(char **newval, void **extra, GucSource source);
 extern void assign_locale_numeric(const char *newval, void *extra);
 extern bool check_locale_time(char **newval, void **extra, GucSource source);
 extern void assign_locale_time(const char *newval, void *extra);
+#endif
 
 extern bool check_locale(int category, const char *locale, char **canonname);
 extern char *pg_perm_setlocale(int category, const char *locale);
-- 
1.8.3.1

0002-Simplify-the-usages-of-COMPLETE_WITH_QUERY.patchtext/x-patch; charset=us-asciiDownload
>From 2e49b1880df9790992953cd0bd9da2ec9cafece9 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 18 Nov 2015 10:15:54 +0900
Subject: [PATCH 2/8] Simplify the usages of COMPLETE_WITH_QUERY

Many of the usage of COMPLETE_WITH_QUERY is accompanied by assignment
to completion_info_charp. This patch adds new second parameter to the
macro COMPLETE_WITH_QUERY according to COMPLETE_WITH_SCHEMA_QUERY so
that its usage looks simpler.
---
 src/bin/psql/tab-complete.c | 184 ++++++++++++++++++++------------------------
 1 file changed, 85 insertions(+), 99 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..55ace8a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -144,9 +144,10 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define COMPLETE_WITH_QUERY(query, info) \
 do { \
 	completion_charp = query; \
+	completion_info_charp = info; \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
@@ -1042,7 +1043,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev4_wd, "BY") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -1111,7 +1112,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
@@ -1366,8 +1367,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, prev3_wd);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
@@ -1438,7 +1438,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
 			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
 			  pg_strcasecmp(prev_wd, "RESET") == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
 	/* ALTER VIEW <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
@@ -1483,7 +1483,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
@@ -1507,8 +1507,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1524,8 +1523,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
 	}
 
 	/*
@@ -1580,32 +1578,28 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev4_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev4_wd);
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
@@ -1637,16 +1631,14 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
@@ -1724,8 +1716,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, prev3_wd);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -1793,8 +1784,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev3_wd);
 	}
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
 	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
@@ -1809,7 +1799,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
@@ -1870,8 +1860,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
 			 pg_strcasecmp(prev_wd, "INDEX") == 0)
 	{
-		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev5_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
@@ -2039,7 +2028,7 @@ psql_completion(const char *text, int start, int end)
 			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
 			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
 			 pg_strcasecmp(prev_wd, "USER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* BEGIN, END, ABORT */
 	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
@@ -2106,8 +2095,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
 	}
 
 	/*
@@ -2117,8 +2105,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
 	}
 
 /* COMMENT */
@@ -2160,7 +2147,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, NULL);
 	}
 	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
@@ -2173,8 +2160,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
@@ -2188,7 +2174,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
@@ -2270,13 +2256,13 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
 	/* CREATE EXTENSION <name> */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
@@ -2291,8 +2277,8 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "EXTENSION") == 0 &&
 			 pg_strcasecmp(prev_wd, "VERSION") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
+							prev2_wd);
 	}
 
 	/* CREATE FOREIGN */
@@ -2382,7 +2368,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
 	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
 			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
 			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
@@ -2473,7 +2459,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
@@ -2934,7 +2920,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
 			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
@@ -2956,8 +2942,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
@@ -2979,14 +2964,14 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* DROP POLICY <name>  */
 	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev_wd, "POLICY") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
 	}
 	/* DROP POLICY <name> ON */
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
@@ -2999,8 +2984,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, prev2_wd);
 	}
 
 	/* DROP RULE */
@@ -3013,8 +2997,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
@@ -3029,7 +3012,7 @@ psql_completion(const char *text, int start, int end)
 /* EXECUTE, but not EXECUTE embedded in other commands */
 	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
 			 prev2_wd[0] == '\0')
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, NULL);
 
 /* EXPLAIN */
 
@@ -3103,7 +3086,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
 			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
 			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 /* FOREIGN TABLE */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
@@ -3114,7 +3097,7 @@ psql_completion(const char *text, int start, int end)
 /* FOREIGN SERVER */
 	else if (pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
 			 pg_strcasecmp(prev_wd, "SERVER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
@@ -3134,7 +3117,8 @@ psql_completion(const char *text, int start, int end)
 							" UNION SELECT 'TEMPORARY'"
 							" UNION SELECT 'EXECUTE'"
 							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+							" UNION SELECT 'ALL'",
+							NULL);
 	}
 
 	/*
@@ -3233,21 +3217,21 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ON") == 0)
 	{
 		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
 		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
 		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 		else if (pg_strcasecmp(prev_wd, "SEQUENCE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
 		else if (pg_strcasecmp(prev_wd, "TABLE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
@@ -3313,7 +3297,7 @@ psql_completion(const char *text, int start, int end)
 			   pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
 			   pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
 			  pg_strcasecmp(prev_wd, "FROM") == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
 	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
@@ -3331,12 +3315,12 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	}
 	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
 			 pg_strcasecmp(prev_wd, "FROM") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	}
 
 /* GROUP BY */
@@ -3433,7 +3417,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY */
 	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
 /* OPTIONS */
 	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
@@ -3442,7 +3426,7 @@ psql_completion(const char *text, int start, int end)
 /* OWNER TO  - complete with available roles */
 	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* ORDER BY */
 	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
@@ -3477,7 +3461,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
@@ -3486,7 +3470,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
@@ -3551,10 +3535,10 @@ psql_completion(const char *text, int start, int end)
 		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
 				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
 
 /* SECURITY LABEL */
@@ -3600,9 +3584,9 @@ psql_completion(const char *text, int start, int end)
 	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
 			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
 			 pg_strcasecmp(prev_wd, "RESET") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
 	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, NULL);
 	/* Complete "SET TRANSACTION" */
 	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
@@ -3693,7 +3677,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete SET ROLE */
 	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "ROLE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "SESSION") == 0)
@@ -3707,7 +3691,8 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "SET") == 0
 			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
 			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
+							NULL);
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
 			 pg_strcasecmp(prev_wd, "SESSION") == 0)
@@ -3741,7 +3726,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+								" UNION SELECT 'DEFAULT' ",
+								NULL);
 		}
 		else
 		{
@@ -3754,7 +3740,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, 1024, Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, NULL);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 			{
@@ -3787,7 +3773,7 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLESAMPLE */
 	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, NULL);
 
 	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
 		COMPLETE_WITH_CONST("(");
@@ -3798,7 +3784,7 @@ psql_completion(const char *text, int start, int end)
 
 /* UNLISTEN */
 	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
@@ -3835,13 +3821,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+							" UNION SELECT 'USER'", NULL);
 	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
 			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
 			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
 			 pg_strcasecmp(prev_wd, "FOR") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
 	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
 			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
 			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
@@ -3935,44 +3921,44 @@ psql_completion(const char *text, int start, int end)
 	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
 	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
 	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
 	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
 	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
 	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, NULL);
 	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, NULL);
 	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, NULL);
 	/* must be at end of \dF */
 	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, NULL);
 
 	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
 	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
 			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
@@ -3984,17 +3970,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
 			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, NULL);
 	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
 	/* must be at end of \d list */
 	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
@@ -4006,11 +3992,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, NULL);
 	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (strcmp(prev_wd, "\\password") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (strcmp(prev_wd, "\\pset") == 0)
 	{
 		static const char *const my_list[] =
@@ -4156,7 +4142,7 @@ psql_completion(const char *text, int start, int end)
 			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
+					COMPLETE_WITH_QUERY(words_after_create[i].query, NULL);
 				else if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
-- 
1.8.3.1

0003-Replace-existing-completions-with-regular-expression.patchtext/x-patch; charset=us-asciiDownload
>From 5b92fa5164b270119fcd99d9bc6824cdcf8a936a Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:18:18 +0900
Subject: [PATCH 3/7] Replace existing completions with regular expressions as
 is.

This patch simply replaces each completions with regular
expressoins. No optimization or enhancement is done.
---
 src/bin/psql/Makefile       |   17 +-
 src/bin/psql/tab-complete.c | 2498 ++++++++++++++++++-------------------------
 2 files changed, 1067 insertions(+), 1448 deletions(-)

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..afb4950 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -17,18 +17,27 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
+regex_srcdir= $(top_srcdir)/src/backend/regex
 
-override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/pg_dump $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(regex_srcdir) -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/pg_dump $(CPPFLAGS)
 
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
-	$(WIN32RES)
-
+	sql_help.o regcomp.o $(regex_srcdir)/regexec.o $(regex_srcdir)/regfree.o \
+	$(top_srcdir)/src/backend/utils/mb/wstrncmp.o $(WIN32RES)
 
+CFLAGS+= -DFRONTEND
 all: psql
 
+
+regcomp.c regc_pg_locale.c: % :$(top_srcdir)/src/backend/regex/%
+	rm -f $@ && $(LN_S) $< .
+
+regcomp.o:regcomp.c regc_pg_locale.c $(regex_srcdir)/regc_color.c $(regex_srcdir)/regc_cvec.c $(regex_srcdir)/regc_lex.c $(regex_srcdir)/regc_locale.c $(regex_srcdir)/regc_nfa.c 
+
+tab-complete.o: tab-complete.c
+
 psql: $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 55ace8a..7ede2a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -54,6 +54,8 @@
 #include "common.h"
 #include "settings.h"
 #include "stringutils.h"
+#include "regex/regex.h"
+#include "catalog/pg_collation.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -789,10 +791,53 @@ typedef struct
 	const bits32 flags;			/* visibility flags, see below */
 } pgsql_thing_t;
 
+/* This is a list of preprocessed (compiled) regular expressions */
+typedef struct re_hashent_t
+{
+	char *repat;				/* pointer to match pattern */
+	int	  cflags;				/* flags for pg_regcomp */
+	regex_t re;					/* compiled regular expression */
+	struct re_hashent_t *next;	/* next entry sharing the same hash */
+#ifdef DEBUG_COMPRE
+	char *restr;				/* intermediate regular expession */
+#endif
+} re_hashent;
+
+#define RE_HASH_SIZE 256		/* size of regexp hash table */
+static re_hashent re_hash[RE_HASH_SIZE];	/* regexp hash table */
+
 #define THING_NO_CREATE		(1 << 0)	/* should not show up after CREATE */
 #define THING_NO_DROP		(1 << 1)	/* should not show up after DROP */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP)
 
+#define CAPBUFLEN 128			/* storage length for each regexp capture
+								 * substr */
+#define MATCHNUM 8				/* maximum number of regexp capture  */
+
+/*
+ * Macros for patter matching.
+ * 
+ * MATCH matches the pattern case-insensitively.
+ * CMATCH matches the pattern case-sensitively.
+ * MATCHBEG returns start index of inebufw for designated capture string
+ * MATCHEND returns index of the next character in inebufw for designated
+ * 			capture string
+ * CAPTURE0 just copies match substr into capbuf.
+ * CAPTURE  captures match substr then returns it. CAPTURE0 then repeated
+ *          CAPBUF would be effective when captured substring is repeatedly
+ *          read.
+ * CAPBUF	returns capture buffer. 
+ */
+#define MATCH(pat) (rematch(linebufw, wstrlen, pat, rmatches, REG_ADVANCED|REG_ICASE))
+#define CMATCH(pat) (rematch(linebufw, wstrlen, pat, rmatches, REG_ADVANCED))
+#define MATCHBEG(n) (rmatches[n].rm_so)
+#define MATCHEND(n) (rmatches[n].rm_eo)
+#define MATCHLEN(n) (MATCHEND(n) - MATCHBEG(n))
+#define CAPCPYLEN(n)(MATCHLEN(n)<CAPBUFLEN ? MATCHLEN(n):CAPBUFLEN - 1)
+#define CAPTURE0(n) (strncpy(capbuf[n], linebuf + MATCHBEG(n), CAPCPYLEN(n)),capbuf[n][CAPCPYLEN(n)] = 0)
+#define CAPTURE(n)  (CAPTURE0(n), capbuf[n])
+#define CAPBUF(n)   (capbuf[n])
+
 static const pgsql_thing_t words_after_create[] = {
 	{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL},		/* Casts have complex structures for names, so
@@ -843,7 +888,6 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL}						/* end of list */
 };
 
-
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
 static char *create_command_generator(const char *text, int state);
@@ -865,15 +909,33 @@ static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
-static void get_previous_words(int point, char **previous_words, int nwords);
-
 static char *get_guctype(const char *varname);
 
+static uint32 hash_uint32(uint32 k);
+static int pathash(char *pat);
+static regex_t *patcomp(char *pat, int cflags);
+static bool rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches, int cflags);
+
+
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
+static int
+pg_ascii2wchar_with_len(const char *from, pg_wchar *to, int len)
+{
+	int			cnt = 0;
+
+	while (len > 0 && *from)
+	{
+		*to++ = *from++;
+		len--;
+		cnt++;
+	}
+	*to = 0;
+	return cnt;
+}
 
 /*
  * Initialize the readline library for our purposes.
@@ -894,6 +956,31 @@ initialize_readline(void)
 	 */
 }
 
+static pg_wchar *
+expand_wchar_buffer(pg_wchar *p, int *buflen, int newlen, int limit)
+{
+	pg_wchar *ret = p;
+	int       len1 = newlen - 1;
+
+	Assert(newlen > 0);
+	if (newlen >= *buflen)
+	{
+		int mask;
+
+		/* Allocate in size of 2^n. Minimum 256 wchars */
+		for (mask = 255 ; mask < limit && (len1 & mask) != len1 ;
+			 mask = (mask << 1) | 1);
+		
+		/* mask is (<new buffer length> - 1) here */
+		if (mask >= limit) return NULL; /* Exceeds limit */
+		
+		*buflen = mask + 1;
+		
+		ret = pg_realloc(p, (*buflen) * sizeof(pg_wchar));
+	}		
+
+	return ret;
+}
 
 /*
  * The completion function.
@@ -906,23 +993,16 @@ initialize_readline(void)
 static char **
 psql_completion(const char *text, int start, int end)
 {
+	const char *linebuf = rl_line_buffer;
+	static pg_wchar	 *linebufw = NULL;
+	static int	     linebufwlen = 0;
+	int len, wstrlen;
+	regmatch_t rmatches[MATCHNUM];
+	char capbuf[MATCHNUM][CAPBUFLEN];
+
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
-	/* This array will contain some scannage of the input line. */
-	char	   *previous_words[9];
-
-	/* For compactness, we use these macros to reference previous_words[]. */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
@@ -962,12 +1042,10 @@ psql_completion(const char *text, int start, int end)
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
 
-	/*
-	 * Scan the input line before our current position for the last few words.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	get_previous_words(start, previous_words, lengthof(previous_words));
+	len = strlen(linebuf);
+	/* Expand string buffer if needed */
+	linebufw = expand_wchar_buffer(linebufw, &linebufwlen, len + 1, 2048); 
+	wstrlen = pg_ascii2wchar_with_len(linebuf, linebufw, strlen(linebuf));
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -985,36 +1063,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (MATCH("^"))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (MATCH("^CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (MATCH("^ALTER TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (MATCH("^ALTER"))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1027,9 +1100,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALL IN TABLESPACE #id"))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1037,38 +1108,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (MATCH("ALL IN TABLESPACE #id OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (MATCH("^ALTER {AGGREGATE|FUNCTION} #id"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](.."))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
+		static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
-		else
-			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+	else if (MATCH("ALTER SCHEMA #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1077,8 +1134,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+	else if (MATCH("ALTER COLLATION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1087,8 +1143,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
+	else if (MATCH("ALTER CONVERSION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1097,8 +1152,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCH("ALTER DATABASE #id"))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1108,17 +1162,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER #id"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
 		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
@@ -1127,10 +1177,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER #id ENABLE"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
 		{"REPLICA", "ALWAYS", NULL};
@@ -1139,8 +1186,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (MATCH("ALTER EXTENSION #id"))
 	{
 		static const char *const list_ALTEREXTENSION[] =
 		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
@@ -1149,8 +1195,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("ALTER FOREIGN"))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -1159,10 +1204,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (MATCH("ALTER FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_ALTER_FDW[] =
 		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
@@ -1171,9 +1213,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCH("ALTER FOREIGN TABLE #id"))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1184,15 +1224,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
+	else if (MATCH("ALTER INDEX"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 	/* ALTER INDEX <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (MATCH("ALTER INDEX #id"))
 	{
 		static const char *const list_ALTERINDEX[] =
 		{"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
@@ -1200,9 +1238,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEX);
 	}
 	/* ALTER INDEX <name> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER INDEX #id SET"))
 	{
 		static const char *const list_ALTERINDEXSET[] =
 		{"(", "TABLESPACE", NULL};
@@ -1210,25 +1246,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
 	}
 	/* ALTER INDEX <name> RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("ALTER INDEX #id RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER INDEX #id RESET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor", "fastupdate", "gin_pending_list_limit", NULL};
 
 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER INDEX #id SET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor =", "fastupdate =", "gin_pending_list_limit =", NULL};
@@ -1237,8 +1265,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LANGUAGE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
+	else if (MATCH("ALTER LANGUAGE #id"))
 	{
 		static const char *const list_ALTERLANGUAGE[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1247,9 +1274,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OBJECT") == 0)
+	else if (MATCH("ALTER LARGE OBJECT #id"))
 	{
 		static const char *const list_ALTERLARGEOBJECT[] =
 		{"OWNER TO", NULL};
@@ -1258,19 +1283,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (MATCH("ALTER MATERIALIZED VIEW"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 
 	/* ALTER USER,ROLE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
+	else if (MATCH("ALTER {{USER !MAPPING}|{ROLE #id}}"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1284,10 +1304,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "USER") == 0 ||
-			   pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCH("ALTER {USER|ROLE} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1302,16 +1319,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCH("ALTER {ROLE|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
+
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
+	else if (MATCH("ALTER DEFAULT PRIVILEGES"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
 		{"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
@@ -1319,10 +1331,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
 	}
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("ALTER DEFAULT PRIVILEGES FOR"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
 		{"ROLE", "USER", NULL};
@@ -1330,10 +1339,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
 	}
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-			  pg_strcasecmp(prev3_wd, "IN") == 0))
+	else if (MATCH("DEFAULT PRIVILEGES {FOR|IN} #kwd #id"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
 		{"GRANT", "REVOKE", NULL};
@@ -1341,8 +1347,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
 	}
 	/* ALTER DOMAIN <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
+	else if (MATCH("ALTER DOMAIN #id"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
@@ -1350,9 +1355,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> DROP */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("ALTER DOMAIN #id DROP"))
 	{
 		static const char *const list_ALTERDOMAIN2[] =
 		{"CONSTRAINT", "DEFAULT", "NOT NULL", NULL};
@@ -1360,19 +1363,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
 	}
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, prev3_wd);
-	}
+	else if (MATCH("ALTER DOMAIN [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, CAPTURE(1));
+
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCH("ALTER DOMAIN #id RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"CONSTRAINT", "TO", NULL};
@@ -1380,15 +1375,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+	else if (MATCH("ALTER DOMAIN #id RENAME CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER DOMAIN #id SET"))
 	{
 		static const char *const list_ALTERDOMAIN3[] =
 		{"DEFAULT", "NOT NULL", "SCHEMA", NULL};
@@ -1396,8 +1387,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
 	}
 	/* ALTER SEQUENCE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
+	else if (MATCH("ALTER SEQUENCE #id"))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1406,9 +1396,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("ALTER SEQUENCE #id NO"))
 	{
 		static const char *const list_ALTERSEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -1416,8 +1404,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
 	/* ALTER SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("ALTER SERVER #id"))
 	{
 		static const char *const list_ALTER_SERVER[] =
 		{"VERSION", "OPTIONS", "OWNER TO", NULL};
@@ -1425,8 +1412,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_SERVER);
 	}
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "SYSTEM") == 0)
+	else if (MATCH("ALTER SYSTEM"))
 	{
 		static const char *const list_ALTERSYSTEM[] =
 		{"SET", "RESET", NULL};
@@ -1434,14 +1420,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
 	}
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCH("ALTER SYSTEM {SET|RESET}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
+
 	/* ALTER VIEW <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("ALTER VIEW #id"))
 	{
 		static const char *const list_ALTERVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1449,9 +1432,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("ALTER MATERIALIZED VIEW #id"))
 	{
 		static const char *const list_ALTERMATVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1460,18 +1441,15 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCH("ALTER POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* ALTER POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("ALTER POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id"))
 	{
 		static const char *const list_ALTERPOLICY[] =
 		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
@@ -1479,72 +1457,51 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERPOLICY);
 	}
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
+
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id WITH CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCH("ALTER RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
-	}
+	else if (MATCH("ALTER RULE [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
 
 	/* ALTER RULE <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0)
+	else if (MATCH("ALTER RULE #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
-	}
+	else if (MATCH("ALTER TRIGGER [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
 
-	/*
-	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
-	 */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+//  !!! This duplicates with the entry just above
+//	/*
+//	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
+//	 */
+//	else if (MATCH("ALTER TRIGGER #id ON"))
+//		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("ALTER TRIGGER #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCH("ALTER TABLE #id"))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1554,96 +1511,55 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (MATCH("ALTER TABLE #id ENABLE"))
 	{
 		static const char *const list_ALTERENABLE[] =
 		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
-			  pg_strcasecmp(prev_wd, "ALWAYS") == 0))
+	else if (MATCH("ALTER TABLE #id ENABLE {REPLICA|ALWAYS}"))
 	{
 		static const char *const list_ALTERENABLE2[] =
 		{"RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE2);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev4_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev4_wd);
-	}
+	else if (MATCH("ALTER TABLE [#id] ENABLE RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
 	/* ALTER TABLE xxx INHERIT */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCH("ALTER TABLE #id INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
+
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "NO") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCH("ALTER TABLE #id NO INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
+
 	/* ALTER TABLE xxx DISABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
+	else if (MATCH("ALTER TABLE #id DISABLE"))
 	{
 		static const char *const list_ALTERDISABLE[] =
 		{"ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERDISABLE);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (MATCH("ALTER TABLE [#id] DISABLE RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] DISABLE TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
+	else if (MATCH("DISABLE ROW LEVEL SECURITY"))
 	{
 		static const char *const list_DISABLERLS[] =
 		{"CASCADE", NULL};
@@ -1652,45 +1568,33 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALTER") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+	else if (MATCH("ALTER TABLE [#id] ALTER"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+	else if (MATCH("ALTER TABLE [#id] RENAME"))
+		COMPLETE_WITH_ATTR(CAPTURE(1),
+						   " UNION SELECT 'COLUMN'"
+						   " UNION SELECT 'CONSTRAINT'"
+						   " UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("TABLE [#id] {ALTER|RENAME} COLUMN"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCH("TABLE #id RENAME {COLUMN|CONSTRAINT} !TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("TABLE #id DROP"))
 	{
 		static const char *const list_TABLEDROP[] =
 		{"COLUMN", "CONSTRAINT", NULL};
@@ -1698,31 +1602,18 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLEDROP);
 	}
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("ALTER TABLE [#id] DROP COLUMN"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, prev3_wd);
-	}
+	else if (MATCH("ALTER TABLE [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, CAPTURE(1));
+
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id"))
 	{
 		static const char *const list_COLUMNALTER[] =
 		{"TYPE", "SET", "RESET", "DROP", NULL};
@@ -1730,11 +1621,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNALTER);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET"))
 	{
 		static const char *const list_COLUMNSET[] =
 		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
@@ -1742,11 +1629,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSET);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET ("))
 	{
 		static const char *const list_COLUMNOPTIONS[] =
 		{"n_distinct", "n_distinct_inherited", NULL};
@@ -1754,11 +1637,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET STORAGE"))
 	{
 		static const char *const list_COLUMNSTORAGE[] =
 		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
@@ -1766,29 +1645,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id DROP"))
 	{
 		static const char *const list_COLUMNDROP[] =
 		{"DEFAULT", "NOT NULL", NULL};
 
 		COMPLETE_WITH_LIST(list_COLUMNDROP);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
+	else if (MATCH("ALTER TABLE #id CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev3_wd);
-	}
+
+	else if (MATCH("ALTER TABLE [#id] CLUSTER ON"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
+
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER TABLE #id SET"))
 	{
 		static const char *const list_TABLESET[] =
 		{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
@@ -1796,14 +1667,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET);
 	}
 	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALTER TABLE #id SET TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
+
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITHOUT") == 0)
+	else if (MATCH("ALTER TABLE #id SET WITHOUT"))
 	{
 		static const char *const list_TABLESET2[] =
 		{"CLUSTER", "OIDS", NULL};
@@ -1811,14 +1679,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET2);
 	}
 	/* ALTER TABLE <foo> RESET */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("ALTER TABLE #id RESET"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLE #id {RE}?SET("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1855,38 +1720,24 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev5_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
+	else if (MATCH("[#id] REPLICA IDENTITY USING INDEX"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
+
+	else if (MATCH("TABLE #id REPLICA IDENTITY USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
+
+	else if (MATCH("TABLE #id REPLICA IDENTITY"))
 	{
 		static const char *const list_REPLICAID[] =
 		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_REPLICAID);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
-	{
+	else if (MATCH("TABLE #id REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
-	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALTER TABLESPACE #id"))
 	{
 		static const char *const list_ALTERTSPC[] =
 		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
@@ -1894,17 +1745,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTSPC);
 	}
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCH("ALTER TABLESPACE #id {RE}?SET"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLESPACE #id {RE}?SET("))
 	{
 		static const char *const list_TABLESPACEOPTIONS[] =
 		{"seq_page_cost", "random_page_cost", "effective_io_concurrency", NULL};
@@ -1913,20 +1758,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("ALTER TEXT SEARCH"))
 	{
 		static const char *const list_ALTERTEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "PARSER") == 0))
+	else if (MATCH("ALTER TEXT SEARCH {TEMPLATE|PARSER} #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH2[] =
 		{"RENAME TO", "SET SCHEMA", NULL};
@@ -1934,10 +1773,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
+	else if (MATCH("ALTER TEXT SEARCH DICTIONARY #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH3[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1945,10 +1781,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (MATCH("ALTER TEXT SEARCH CONFIGURATION #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH4[] =
 		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1957,8 +1790,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TYPE") == 0)
+	else if (MATCH("ALTER TYPE #id"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
@@ -1967,9 +1799,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ADD") == 0)
+	else if (MATCH("ALTER TYPE #id ADD"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "VALUE", NULL};
@@ -1977,9 +1807,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCH("ALTER TYPE #id RENAME"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "TO", NULL};
@@ -1987,30 +1815,22 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+	else if (MATCH("TYPE #id RENAME ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
 	 * attributes
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("TYPE [#id] {ALTER|DROP|RENAME} ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
-	{
+	else if (MATCH("ALTER ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TYPE");
-	}
+
 	/* complete ALTER GROUP <foo> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "GROUP") == 0)
+	else if (MATCH("ALTER GROUP #id"))
 	{
 		static const char *const list_ALTERGROUP[] =
 		{"ADD USER", "DROP USER", "RENAME TO", NULL};
@@ -2018,22 +1838,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGROUP);
 	}
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev_wd, "DROP") == 0))
+	else if (MATCH("ALTER GROUP #id {ADD|DROP}"))
 		COMPLETE_WITH_CONST("USER");
+
 	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
-	else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev_wd, "USER") == 0)
+	else if (MATCH("ALTER GROUP #id {ADD|DROP} USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* BEGIN, END, ABORT */
-	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
-			 pg_strcasecmp(prev_wd, "END") == 0 ||
-			 pg_strcasecmp(prev_wd, "ABORT") == 0)
+	else if (MATCH("{BEGIN|END|ABORT}"))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", NULL};
@@ -2041,7 +1854,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* COMMIT */
-	else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
+	else if (MATCH("COMMIT"))
 	{
 		static const char *const list_COMMIT[] =
 		{"WORK", "TRANSACTION", "PREPARED", NULL};
@@ -2049,10 +1862,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COMMIT);
 	}
 /* RELEASE SAVEPOINT */
-	else if (pg_strcasecmp(prev_wd, "RELEASE") == 0)
+	else if (MATCH("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
+
 /* ROLLBACK*/
-	else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0)
+	else if (MATCH("ROLLBACK"))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL};
@@ -2064,125 +1878,83 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+	else if (MATCH(" !WITHOUT CLUSTER"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   "UNION SELECT 'VERBOSE'");
 
 	/*
 	 * If the previous words are CLUSTER VERBOSE produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+	else if (MATCH("CLUSTER VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") != 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-	{
+	else if (MATCH("CLUSTER !ON|VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
-	}
+
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
-	{
+	else if (MATCH("CLUSTER VERBOSE #id"))
 		COMPLETE_WITH_CONST("USING");
-	}
 
 	/*
 	 * If we have CLUSTER <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
-	}
+	else if (MATCH("CLUSTER [#id] USING"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
 	/*
 	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
-	}
+	else if (MATCH("CLUSTER VERBOSE [#id] USING"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
 /* COMMENT */
-	else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
+	else if (MATCH("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+
+	else if (MATCH("COMMENT ON"))
 	{
 		static const char *const list_COMMENT[] =
-		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
-			"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
-			"SERVER", "INDEX", "LANGUAGE", "POLICY", "RULE", "SCHEMA", "SEQUENCE",
-			"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
-			"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
-		"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
+		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER",
+		 "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "SERVER",
+		 "INDEX", "LANGUAGE", "POLICY", "RULE", "SCHEMA", "SEQUENCE",
+		 "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE",
+		 "FUNCTION", "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN",
+		 "LARGE OBJECT", "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("COMMENT ON FOREIGN"))
 	{
 		static const char *const list_TRANS2[] =
 		{"DATA WRAPPER", "TABLE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("COMMENT ON TEXT SEARCH"))
 	{
 		static const char *const list_TRANS2[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
+	else if (MATCH("COMMENT ON CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
-	{
+
+	else if (MATCH("COMMENT ON CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+
+	else if (MATCH("COMMENT ON CONSTRAINT [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint,
+							CAPTURE(1));
+
+	else if (MATCH("COMMENT ON MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+
+	else if (MATCH("COMMENT ON EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
-	}
-	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev4_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev5_wd, "ON") == 0)) &&
-			 pg_strcasecmp(prev_wd, "IS") != 0)
+
+	else if (MATCH("COMMENT ON {#kwd }+ !IS"))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2191,15 +1963,11 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (pg_strcasecmp(prev_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev_wd, "\\copy") == 0 ||
-			 (pg_strcasecmp(prev2_wd, "COPY") == 0 &&
-			  pg_strcasecmp(prev_wd, "BINARY") == 0))
+	else if (MATCH("\\?COPY{ BINARY}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
-	else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
-			 pg_strcasecmp(prev2_wd, "BINARY") == 0)
+	else if (MATCH("\\?COPY{ BINARY}? #id"))
 	{
 		static const char *const list_FROMTO[] =
 		{"FROM", "TO", NULL};
@@ -2207,22 +1975,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FROMTO);
 	}
 	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
-	else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev_wd, "TO") == 0))
+	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO}"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename */
-	else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev4_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev2_wd, "TO") == 0))
+	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO} #nwb"))
 	{
 		static const char *const list_COPY[] =
 		{"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
@@ -2231,9 +1991,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-	else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev3_wd, "TO") == 0))
+	else if (MATCH("{FROM|TO} #nwb CSV"))
 	{
 		static const char *const list_CSV[] =
 		{"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
@@ -2242,8 +2000,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* CREATE DATABASE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCH("CREATE DATABASE #id"))
 	{
 		static const char *const list_DATABASE[] =
 		{"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS_TEMPLATE",
@@ -2253,19 +2010,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DATABASE);
 	}
 
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+	else if (MATCH("CREATE DATABASE #id TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+	else if (MATCH("CREATE EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
+
 	/* CREATE EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (MATCH("CREATE EXTENSION #id"))
 	{
 		static const char *const list_CREATE_EXTENSION[] =
 		{"WITH SCHEMA", "CASCADE", "VERSION", NULL};
@@ -2273,17 +2027,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
 	}
 	/* CREATE EXTENSION <name> VERSION */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EXTENSION") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERSION") == 0)
-	{
+	else if (MATCH("CREATE EXTENSION [#id] VERSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
-							prev2_wd);
-	}
+							CAPTURE(1));
 
 	/* CREATE FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("CREATE FOREIGN"))
 	{
 		static const char *const list_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2292,10 +2041,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (MATCH("CREATE FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
 		{"HANDLER", "VALIDATOR", NULL};
@@ -2305,31 +2051,24 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
+	else if (MATCH("CREATE UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
+
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
-	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX [<name>] ON with a list of tables  */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("INDEX {CONCURRENTLY }? #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("INDEX #id CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 	{
 		static const char *const list_CREATE_INDEX[] =
 		{"CONCURRENTLY", "ON", NULL};
@@ -2341,54 +2080,38 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("INDEX #id {CONCURRENTLY }? ON #id"))
 	{
 		static const char *const list_CREATE_INDEX2[] =
 		{"(", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
 	}
-	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("INDEX #id {CONCURRENTLY }? ON [#id]("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* same if you put in USING */
-	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+	else if (MATCH("ON [#id] USING #id("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* Complete USING with an index method */
-	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("INDEX #id {#nwb }* ON #id USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
-	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
-			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0)
+
+	else if (MATCH("^{!POLICY|FOR }+ON #id USING #id"))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCH("CREATE POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("CREATE POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"FOR", "TO", "USING", "WITH CHECK", NULL};
@@ -2400,10 +2123,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id FOR"))
 	{
 		static const char *const list_POLICYCMDS[] =
 		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
@@ -2411,10 +2131,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYCMDS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id FOR INSERT"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "WITH CHECK", NULL};
@@ -2426,11 +2143,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
 	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
-			  pg_strcasecmp(prev_wd, "DELETE") == 0))
+	else if (MATCH("CREATE POLICY #id ON #id FOR {SELECT|DELETE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", NULL};
@@ -2443,11 +2156,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
 	 * CHECK"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
-			  pg_strcasecmp(prev_wd, "UPDATE") == 0))
+	else if (MATCH("CREATE POLICY #id ON #id FOR {ALL|UPDATE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", "WITH CHECK", NULL};
@@ -2455,32 +2164,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
+
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCH("CREATE RULE #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE RULE <sth> AS with "ON" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE RULE #id AS"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
-	else if (pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("RULE #id AS ON"))
 	{
 		static const char *const rule_events[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -2488,24 +2189,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(rule_events);
 	}
 	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
-	else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
-			  pg_toupper((unsigned char) prev_wd[5]) == 'T'))
+	else if (MATCH("AS ON {SELECT|UPDATE|INSERT|DELETE}"))
 		COMPLETE_WITH_CONST("TO");
+
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("AS ON #kwd TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0))
+	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id"))
 	{
 		static const char *const list_CREATESEQUENCE[] =
 		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
@@ -2514,13 +2206,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev4_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev4_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0)) &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id NO"))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -2529,8 +2215,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("CREATE SERVER #id"))
 	{
 		static const char *const list_CREATE_SERVER[] =
 		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
@@ -2540,9 +2225,7 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
-			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
+	else if (MATCH("CREATE {TEMP{ORARY}?}"))
 	{
 		static const char *const list_TEMP[] =
 		{"SEQUENCE", "TABLE", "VIEW", NULL};
@@ -2550,8 +2233,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TEMP);
 	}
 	/* Complete "CREATE UNLOGGED" with TABLE */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
+	else if (MATCH("CREATE UNLOGGED"))
 	{
 		static const char *const list_UNLOGGED[] =
 		{"TABLE", "MATERIALIZED VIEW", NULL};
@@ -2560,8 +2242,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE TABLESPACE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("CREATE TABLESPACE #id"))
 	{
 		static const char *const list_CREATETABLESPACE[] =
 		{"OWNER", "LOCATION", NULL};
@@ -2569,32 +2250,23 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
 	}
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNER") == 0)
-	{
+	else if (MATCH("CREATE TABLESPACE #id OWNER"))
 		COMPLETE_WITH_CONST("LOCATION");
-	}
 
 /* CREATE TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("CREATE TEXT SEARCH"))
 	{
 		static const char *const list_CREATETEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (MATCH("TEXT SEARCH CONFIGURATION #id"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("CREATE TRIGGER #id"))
 	{
 		static const char *const list_CREATETRIGGER[] =
 		{"BEFORE", "AFTER", "INSTEAD OF", NULL};
@@ -2602,10 +2274,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev_wd, "AFTER") == 0))
+	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER}"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
@@ -2613,10 +2282,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev_wd, "OF") == 0)
+	else if (MATCH("CREATE TRIGGER #id INSTEAD OF"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", NULL};
@@ -2624,13 +2290,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0)) ||
-			 (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			  pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
-			  pg_strcasecmp(prev2_wd, "OF") == 0))
+	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER|INSTEAD_OF} #kwd"))
 	{
 		static const char *const list_CREATETRIGGER2[] =
 		{"ON", "OR", NULL};
@@ -2642,27 +2302,19 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("TRIGGER #id {BEFORE|AFTER} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OF") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("INSTEAD OF #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "GRANT") == 0 && prev3_wd[0] == '\0') &&
-			 prev2_wd[0] != '\0')
+	else if (MATCH("CREATE TRIGGER {#nwb }+ EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+	else if (MATCH("CREATE {{USER !MAPPING_}|ROLE |GROUP } #id"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2676,11 +2328,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			   pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2698,18 +2346,11 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
+
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 pg_strcasecmp(prev_wd, "IN") == 0)
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id IN"))
 	{
 		static const char *const list_CREATEROLE3[] =
 		{"GROUP", "ROLE", NULL};
@@ -2719,45 +2360,35 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("CREATE VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCH("CREATE MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("CREATE MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE MATERIALIZED VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
+	else if (MATCH("CREATE EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
+
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("CREATE EVENT TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("CREATE EVENT TRIGGER #id ON"))
 	{
 		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
 		{"ddl_command_start", "ddl_command_end", "sql_drop", NULL};
@@ -2766,7 +2397,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DECLARE */
-	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
+	else if (MATCH("DECLARE #id"))
 	{
 		static const char *const list_DECLARE[] =
 		{"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
@@ -2775,7 +2406,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CURSOR */
-	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
+	else if (MATCH("CURSOR"))
 	{
 		static const char *const list_DECLARECURSOR[] =
 		{"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
@@ -2790,19 +2421,15 @@ psql_completion(const char *text, int start, int end)
 	 * Complete DELETE with FROM (only if the word before that is not "ON"
 	 * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
 	 */
-	else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-			   pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0))
+	else if (MATCH("!ON|GRANT|BEFORE|AFTER DELETE"))
 		COMPLETE_WITH_CONST("FROM");
+
 	/* Complete DELETE FROM with a list of tables */
-	else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
+	else if (MATCH("DELETE FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+
 	/* Complete DELETE FROM <table> */
-	else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FROM") == 0)
+	else if (MATCH("DELETE FROM #id"))
 	{
 		static const char *const list_DELETE[] =
 		{"USING", "WHERE", "SET", NULL};
@@ -2812,7 +2439,7 @@ psql_completion(const char *text, int start, int end)
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
+	else if (MATCH("DISCARD"))
 	{
 		static const char *const list_DISCARD[] =
 		{"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
@@ -2825,7 +2452,7 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (pg_strcasecmp(prev_wd, "DO") == 0)
+	else if (MATCH("DO"))
 	{
 		static const char *const list_DO[] =
 		{"LANGUAGE", NULL};
@@ -2835,46 +2462,13 @@ psql_completion(const char *text, int start, int end)
 
 /* DROP (when not the previous word) */
 	/* DROP AGGREGATE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+	else if (MATCH("DROP AGGREGATE #id"))
 		COMPLETE_WITH_CONST("(");
 
 	/* DROP object with CASCADE / RESTRICT */
-	else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
-			   pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-			  prev_wd[strlen(prev_wd) - 1] == ')') ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			  pg_strcasecmp(prev2_wd, "TRIGGER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
-			   pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
-		)
-	{
-		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
+	else if (MATCH("DROP {{{COLLATION|CONVERSION|DOMAIN|EXTENSION|[FUNCTION]|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW|{EVENT TRIGGER}|{FOREGN DATA WRAPPER}|{TEXT SEARCH {CONFIGURATION|DICTIONARY|PARSER|TEMPLATE}}} #id }|{AGGREGATE #id(..)}}"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "FUNCTION") == 0)
 		{
 			COMPLETE_WITH_CONST("(");
 		}
@@ -2886,8 +2480,7 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(list_DROPCR);
 		}
 	}
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("DROP FOREIGN"))
 	{
 		static const char *const drop_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2896,34 +2489,22 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
-	{
+	else if (MATCH("DROP MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+
+	else if (MATCH("DROP MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
 
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+	else if (MATCH("DROP {AGGREGATE|FUNCTION} [#id]("))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+
 	/* DROP OWNED BY */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "OWNED") == 0)
+	else if (MATCH("DROP OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (MATCH("DROP OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+
+	else if (MATCH("DROP TEXT SEARCH"))
 	{
 
 		static const char *const list_ALTERTEXTSEARCH[] =
@@ -2933,20 +2514,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
+	else if (MATCH("DROP TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("DROP TRIGGER [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
+	else if (MATCH("DROP TRIGGER #id ON #id"))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -2955,53 +2527,32 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
-	{
+	else if (MATCH("DROP EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+
+	else if (MATCH("DROP_EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
-	}
 
 	/* DROP POLICY <name>  */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "POLICY") == 0)
-	{
+	else if (MATCH("DROP POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
-	}
+
 	/* DROP POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
-	{
+	else if (MATCH("DROP POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
+
 	/* DROP POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, prev2_wd);
-	}
+	else if (MATCH("DROP POLICY [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, CAPTURE(1));
 
 	/* DROP RULE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
-	{
+	else if (MATCH("DROP RULE #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+
+	else if (MATCH("DROP RULE [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
+
+	else if (MATCH("DROP RULE #id ON #id"))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -3010,8 +2561,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* EXECUTE, but not EXECUTE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, NULL);
 
 /* EXPLAIN */
@@ -3019,26 +2569,21 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
+	else if (MATCH("EXPLAIN"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("EXPLAIN ANALYZE"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0))
+	else if (MATCH("EXPLAIN{ ANALYZE}? VERBOSE"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
@@ -3048,8 +2593,7 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (pg_strcasecmp(prev_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE}"))
 	{
 		static const char *const list_FETCH1[] =
 		{"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL};
@@ -3057,8 +2601,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FETCH1);
 	}
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev2_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE} #id"))
 	{
 		static const char *const list_FETCH2[] =
 		{"ALL", "NEXT", "PRIOR", NULL};
@@ -3071,8 +2614,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev3_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE} #id #id"))
 	{
 		static const char *const list_FROMIN[] =
 		{"FROM", "IN", NULL};
@@ -3082,27 +2624,20 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+	else if (MATCH("{ALTER|DROP} FOREIGN DATA WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 /* FOREIGN TABLE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
+	else if (MATCH("CREATE FOREIGN TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SERVER") == 0)
+	else if (MATCH("FOREIGN SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
+	else if (MATCH("{GRANT|REVOKE}"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
@@ -3125,32 +2660,12 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev2_wd, "REVOKE") == 0)
-	{
-		if (pg_strcasecmp(prev_wd, "SELECT") == 0
-			|| pg_strcasecmp(prev_wd, "INSERT") == 0
-			|| pg_strcasecmp(prev_wd, "UPDATE") == 0
-			|| pg_strcasecmp(prev_wd, "DELETE") == 0
-			|| pg_strcasecmp(prev_wd, "TRUNCATE") == 0
-			|| pg_strcasecmp(prev_wd, "REFERENCES") == 0
-			|| pg_strcasecmp(prev_wd, "TRIGGER") == 0
-			|| pg_strcasecmp(prev_wd, "CREATE") == 0
-			|| pg_strcasecmp(prev_wd, "CONNECT") == 0
-			|| pg_strcasecmp(prev_wd, "TEMPORARY") == 0
-			|| pg_strcasecmp(prev_wd, "TEMP") == 0
-			|| pg_strcasecmp(prev_wd, "EXECUTE") == 0
-			|| pg_strcasecmp(prev_wd, "USAGE") == 0
-			|| pg_strcasecmp(prev_wd, "ALL") == 0)
+	else if (MATCH("{GRANT|REVOKE} {SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMP{ORARY}?|EXECUTE|USAGE|ALL}"))
 			COMPLETE_WITH_CONST("ON");
-		else
-		{
-			if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
-				COMPLETE_WITH_CONST("TO");
-			else
-				COMPLETE_WITH_CONST("FROM");
-		}
-	}
+	else if (MATCH("GRANT #id"))
+		COMPLETE_WITH_CONST("TO");
+	else if (MATCH("REVOKE #id"))
+		COMPLETE_WITH_CONST("FROM");
 
 	/*
 	 * Complete GRANT/REVOKE <sth> ON with a list of tables, views, and
@@ -3163,9 +2678,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -3183,10 +2696,7 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALL") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON ALL"))
 	{
 		static const char *const list_privilege_all[] =
 		{"FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA", "TABLES IN SCHEMA",
@@ -3195,10 +2705,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_privilege_all);
 	}
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON FOREIGN"))
 	{
 		static const char *const list_privilege_foreign[] =
 		{"DATA WRAPPER", "SERVER", NULL};
@@ -3208,74 +2715,40 @@ psql_completion(const char *text, int start, int end)
 
 	/*
 	 * Complete "GRANT/REMOVE * ON DATABASE/DOMAIN/..." with a list of
-	 * appropriate objects.
+	 * appropriate objects later.
 	 *
-	 * Complete "GRANT/REVOKE * ON * " with "TO/FROM".
+	 * Complete only "GRANT/REVOKE * ON * " with "TO/FROM" here.
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON !DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
 	{
-		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
-		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-		else if (pg_strcasecmp(prev_wd, "SEQUENCE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
-		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if ((pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev8_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev6_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev5_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
-	{
-		if (pg_strcasecmp(prev8_wd, "GRANT") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev7_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		if (pg_strcasecmp(prev7_wd, "GRANT") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN DATA WRAPPER #id"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev6_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN SERVER #id"))
 	{
-		if (pg_strcasecmp(prev6_wd, "GRANT") == 0)
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -3285,76 +2758,55 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (((pg_strcasecmp(prev9_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev5_wd, "GRANT") == 0) &&
-			  pg_strcasecmp(prev_wd, "TO") == 0) ||
-			 ((pg_strcasecmp(prev9_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev8_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev7_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
-			  pg_strcasecmp(prev_wd, "FROM") == 0))
+	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
-	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("GRANT #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("REVOKE #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("FROM");
 
 	/*
 	 * Complete "GRANT/REVOKE * TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
-	{
+	else if (MATCH("GRANT #kwd TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-	}
-	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
-	{
+
+	else if (MATCH("REVOKE #kwd FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-	}
 
 /* GROUP BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "GROUP") == 0)
+	else if (MATCH("FROM #id GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+	else if (MATCH("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+
+	else if (MATCH("IMPORT FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (MATCH("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
+
 	/* Complete INSERT INTO with table names */
-	else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev_wd, "INTO") == 0)
+	else if (MATCH("INSERT INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("INSERT INTO [#id]("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INTO") == 0)
+	else if (MATCH("INSERT INTO #id"))
 	{
 		static const char *const list_INSERT[] =
 		{"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
@@ -3366,9 +2818,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 prev_wd[strlen(prev_wd) - 1] == ')')
+	else if (MATCH("INSERT INTO #id(..)"))
 	{
 		static const char *const list_INSERT[] =
 		{"SELECT", "TABLE", "VALUES", NULL};
@@ -3377,33 +2827,26 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (pg_strcasecmp(prev_wd, "VALUES") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
+	else if (MATCH("!DEFAULT VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+	else if (MATCH("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LOCK") == 0)
+
+	else if (MATCH("LOCK TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-			  pg_strcasecmp(prev_wd, "TABLE") != 0) ||
-			 (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "LOCK") == 0))
+	else if (MATCH("LOCK{ TABLE}? !TABLE"))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (pg_strcasecmp(prev_wd, "IN") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "LOCK") == 0 ||
-			  (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev4_wd, "LOCK") == 0)))
+	else if (MATCH("LOCK{ TABLE}? #id IN"))
 	{
 		static const char *const lock_modes[] =
 		{"ACCESS SHARE MODE",
@@ -3416,30 +2859,26 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* NOTIFY */
-	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
+	else if (MATCH("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
 /* OPTIONS */
-	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+	else if (MATCH("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("OWNER TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* ORDER BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ORDER") == 0)
+	else if (MATCH("FROM #id ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+
+	else if (MATCH("FROM [#id] ORDER BY"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* PREPARE xx AS */
-	else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "PREPARE") == 0)
+	else if (MATCH("PREPARE #id AS"))
 	{
 		static const char *const list_PREPARE[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -3453,116 +2892,68 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED");
-	else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY #id"))
 		COMPLETE_WITH_CONST("TO");
-	else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
-			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+	else if (MATCH("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCH("REFRESH MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("WITH");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY #id"))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id WITH"))
 	{
 		static const char *const list_WITH_DATA[] =
 		{"NO DATA", "DATA", NULL};
 
 		COMPLETE_WITH_LIST(list_WITH_DATA);
 	}
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY #id WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id WITH NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
+	else if (MATCH("REINDEX"))
 	{
 		static const char *const list_REINDEX[] =
 		{"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
 
 		COMPLETE_WITH_LIST(list_REINDEX);
 	}
-	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
-	{
-		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
-				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
-	}
+	/* REINDEX {TABLE|INDEX|SCHEMA|DATABASE} are completed later */
+	else if (MATCH("REINDEX SYSTEM"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 
 /* SECURITY LABEL */
-	else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (MATCH("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "LABEL") == 0)
+	else if (MATCH("SECURITY LABEL"))
 	{
 		static const char *const list_SECURITY_LABEL_preposition[] =
 		{"ON", "FOR"};
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
 	}
-	else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (MATCH("SECURITY LABEL FOR #id"))
 		COMPLETE_WITH_CONST("ON");
-	else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev3_wd, "FOR") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0))
+	else if (MATCH("SECURITY LABEL{ FOR #id}? ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
@@ -3571,88 +2962,54 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("SECURITY LABEL ON #nwb #nwb"))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
-	/* naah . . . */
+	/* naah . . . */ 
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("^{RE}?SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
-	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+	else if (MATCH("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, NULL);
+
 	/* Complete "SET TRANSACTION" */
-	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "START") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "WORK") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev4_wd, "SESSION") == 0
-				 && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0
-				 && pg_strcasecmp(prev2_wd, "AS") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"))
 	{
 		static const char *const my_list[] =
 		{"ISOLATION LEVEL", "READ", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0
-			  || pg_strcasecmp(prev3_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev3_wd, "START") == 0
-			  || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0
-				  && pg_strcasecmp(prev3_wd, "AS") == 0))
-			 && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev2_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev_wd, "ISOLATION") == 0)
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"
+				" ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if ((pg_strcasecmp(prev4_wd, "SET") == 0
-			  || pg_strcasecmp(prev4_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev4_wd, "START") == 0
-			  || pg_strcasecmp(prev4_wd, "AS") == 0)
-			 && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev3_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev2_wd, "ISOLATION") == 0
-			 && pg_strcasecmp(prev_wd, "LEVEL") == 0)
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"
+				" ISOLATION LEVEL"))
 	{
 		static const char *const my_list[] =
 		{"READ", "REPEATABLE", "SERIALIZABLE", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (MATCH("{TRANSACTION|WORK} ISOLATION LEVEL READ"))
 	{
 		static const char *const my_list[] =
 		{"UNCOMMITTED", "COMMITTED", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPEATABLE") == 0)
+	else if (MATCH("{TRANSACTION|WORK} ISOLATION LEVEL REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BEGIN") == 0 ||
-			  pg_strcasecmp(prev3_wd, "START") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AS") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev2_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (MATCH("{SET|BEGIN|START|AS} {TRANSACTION|WORK} READ"))
 	{
 		static const char *const my_list[] =
 		{"ONLY", "WRITE", NULL};
@@ -3660,14 +3017,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* SET CONSTRAINTS */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
+	else if (MATCH("SET CONSTRAINTS"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	}
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
+	else if (MATCH("SET CONSTRAINTS #id"))
 	{
 		static const char *const constraint_list[] =
 		{"DEFERRED", "IMMEDIATE", NULL};
@@ -3675,12 +3030,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(constraint_list);
 	}
 	/* Complete SET ROLE */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "ROLE") == 0)
+	else if (MATCH("SET ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (MATCH("SET SESSION"))
 	{
 		static const char *const my_list[] =
 		{"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL};
@@ -3688,30 +3041,23 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0
-			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
-			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
+	else if (MATCH("SET SESSION AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
 							NULL);
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (MATCH("RESET SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
+
 	/* Complete SET <var> with "TO" */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
-			 pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
-			 prev_wd[strlen(prev_wd) - 1] != ')' &&
-			 prev_wd[strlen(prev_wd) - 1] != '=' &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
+	else if (MATCH("^{!UPDATE }*SET !TABLESPACE|SCHEMA|="))
 		COMPLETE_WITH_CONST("TO");
+
 	/* Suggest possible variable values */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+	else if (MATCH("SET [#id] {TO|=}"))
 	{
+		CAPTURE0(1);
 		/* special cased code for individual GUCs */
-		if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
+		if (pg_strcasecmp(CAPBUF(1), "DateStyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3721,7 +3067,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (pg_strcasecmp(prev2_wd, "search_path") == 0)
+		else if (pg_strcasecmp(CAPBUF(1), "search_path") == 0)
 		{
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
@@ -3733,13 +3079,13 @@ psql_completion(const char *text, int start, int end)
 		{
 			/* generic, type based, GUC support */
 
-			char	   *guctype = get_guctype(prev2_wd);
+			char	   *guctype = get_guctype(CAPBUF(1));
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
-				snprintf(querybuf, 1024, Query_for_enum, prev2_wd);
+				snprintf(querybuf, 1024, Query_for_enum, CAPBUF(1));
 				COMPLETE_WITH_QUERY(querybuf, NULL);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
@@ -3763,35 +3109,34 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* START TRANSACTION */
-	else if (pg_strcasecmp(prev_wd, "START") == 0)
+	else if (MATCH("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
+	else if (MATCH("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, NULL);
 
-	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
+	else if (MATCH("TABLESAMPLE #id"))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
+	else if (MATCH("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
+	else if (MATCH("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE #id"))
 		COMPLETE_WITH_CONST("SET");
 
 	/*
@@ -3799,83 +3144,55 @@ psql_completion(const char *text, int start, int end)
 	 * word) the word before it was (hopefully) a table name and we'll now
 	 * make a list of attributes.
 	 */
-	else if (pg_strcasecmp(prev_wd, "SET") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("[#id] SET"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* UPDATE xx SET yy = */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE #id SET #id"))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+	else if (MATCH("{ALTER|DROP|CREATE} USER MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+
+	else if (MATCH("CREATE USER MAPPING FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'", NULL);
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("{ALTER|DROP} USER MAPPING FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+
+	else if (MATCH("{CREATE|ALTER|DROP} USER MAPPING FOR #id"))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
+	else if (MATCH("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE}"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE} ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE} VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+	else if (MATCH("VACUUM VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("VACUUM ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+	else if (MATCH("{{VERBOSE ANALYZE}|{ANALYZE VERBOSE}}"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3884,120 +3201,115 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
-	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("[#id] WHERE"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
-			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+	else if (MATCH("^{!\\?COPY }+FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+	else if (MATCH("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (MATCH("\\#?_")) /* _ is necessary due to patcomp()'s design */
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (CMATCH("\\c{onnect}?"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
-	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
+	else if (CMATCH("\\c{onnect}? [#id]"))
 	{
-		if (!recognized_connection_string(prev_wd))
+		if (!recognized_connection_string(CAPTURE(1)))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (CMATCH("\\da#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (CMATCH("\\db#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (CMATCH("\\dD#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (CMATCH("\\des#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (CMATCH("\\deu#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (CMATCH("\\dew#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (CMATCH("\\df#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (CMATCH("\\dFd#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, NULL);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (CMATCH("\\dFp#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, NULL);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (CMATCH("\\dFt#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, NULL);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (CMATCH("\\dF#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, NULL);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (CMATCH("\\di#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (CMATCH("\\dL#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (CMATCH("\\dn#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (CMATCH("\\{dp|z}#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (CMATCH("\\ds#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (CMATCH("\\dt#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (CMATCH("\\dT#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (CMATCH("\\d{u|g}#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (CMATCH("\\dv#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (CMATCH("\\dx#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, NULL);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (CMATCH("\\dm#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (CMATCH("\\dE#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (CMATCH("\\dy#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (CMATCH("\\d#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (CMATCH("\\ef#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (CMATCH("\\ev#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (CMATCH("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, NULL);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (CMATCH("\\h{elp}?"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (CMATCH("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (CMATCH("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -4008,9 +3320,10 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev2_wd, "\\pset") == 0)
+	else if (CMATCH("\\pset [#kwd]"))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		CAPTURE0(1);
+		if (strcmp(CAPBUF(1), "format") == 0)
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -4018,16 +3331,16 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (strcmp(CAPBUF(1), "linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (strcmp(CAPBUF(1), "unicode_border_linestyle") == 0 ||
+				 strcmp(CAPBUF(1), "unicode_column_linestyle") == 0 ||
+				 strcmp(CAPBUF(1), "unicode_header_linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -4036,72 +3349,74 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (CMATCH("\\unset"))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (CMATCH("\\set"))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (strcmp(prev2_wd, "\\set") == 0)
+	else if (CMATCH("\\set [#kwd]"))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		CAPTURE0(1);
+
+		if (strcmp(CAPBUF(1), "AUTOCOMMIT") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (strcmp(CAPBUF(1), "COMP_KEYWORD_CASE") == 0)
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (strcmp(CAPBUF(1), "ECHO") == 0)
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (strcmp(CAPBUF(1), "ECHO_HIDDEN") == 0)
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (strcmp(CAPBUF(1), "HISTCONTROL") == 0)
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (strcmp(CAPBUF(1), "ON_ERROR_ROLLBACK") == 0)
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (strcmp(CAPBUF(1), "ON_ERROR_STOP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (strcmp(CAPBUF(1), "QUIET") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (strcmp(CAPBUF(1), "SHOW_CONTEXT") == 0)
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (strcmp(CAPBUF(1), "SINGLELINE") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (strcmp(CAPBUF(1), "SINGLESTEP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (strcmp(CAPBUF(1), "VERBOSITY") == 0)
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -4109,20 +3424,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (CMATCH("\\sf#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (CMATCH("\\sv#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (CMATCH("\\{cd|e|edit|g|i|include|ir|include_relative|o|out|s|w|write|lo_import}"))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
@@ -4133,13 +3439,14 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else
+	else if(MATCH("[#kwd]"))
 	{
 		int			i;
 
+		CAPTURE0(1);
 		for (i = 0; words_after_create[i].name; i++)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
+			if (pg_strcasecmp(CAPBUF(1), words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query, NULL);
@@ -4164,19 +3471,10 @@ psql_completion(const char *text, int start, int end)
 #endif
 	}
 
-	/* free storage */
-	{
-		int			i;
-
-		for (i = 0; i < lengthof(previous_words); i++)
-			free(previous_words[i]);
-	}
-
 	/* Return our Grand List O' Matches */
 	return matches;
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4747,92 +4045,6 @@ exec_query(const char *query)
 	return result;
 }
 
-
-/*
- * Return the nwords word(s) before point.  Words are returned right to left,
- * that is, previous_words[0] gets the last word before point.
- * If we run out of words, remaining array elements are set to empty strings.
- * Each array element is filled with a malloc'd string.
- */
-static void
-get_previous_words(int point, char **previous_words, int nwords)
-{
-	const char *buf = rl_line_buffer;	/* alias */
-	int			i;
-
-	/* first we look for a non-word char before the current point */
-	for (i = point - 1; i >= 0; i--)
-		if (strchr(WORD_BREAKS, buf[i]))
-			break;
-	point = i;
-
-	while (nwords-- > 0)
-	{
-		int			start,
-					end;
-		char	   *s;
-
-		/* now find the first non-space which then constitutes the end */
-		end = -1;
-		for (i = point; i >= 0; i--)
-		{
-			if (!isspace((unsigned char) buf[i]))
-			{
-				end = i;
-				break;
-			}
-		}
-
-		/*
-		 * If no end found we return an empty string, because there is no word
-		 * before the point
-		 */
-		if (end < 0)
-		{
-			point = end;
-			s = pg_strdup("");
-		}
-		else
-		{
-			/*
-			 * Otherwise we now look for the start. The start is either the
-			 * last character before any word-break character going backwards
-			 * from the end, or it's simply character 0. We also handle open
-			 * quotes and parentheses.
-			 */
-			bool		inquotes = false;
-			int			parentheses = 0;
-
-			for (start = end; start > 0; start--)
-			{
-				if (buf[start] == '"')
-					inquotes = !inquotes;
-				if (!inquotes)
-				{
-					if (buf[start] == ')')
-						parentheses++;
-					else if (buf[start] == '(')
-					{
-						if (--parentheses <= 0)
-							break;
-					}
-					else if (parentheses == 0 &&
-							 strchr(WORD_BREAKS, buf[start - 1]))
-						break;
-				}
-			}
-
-			point = start - 1;
-
-			/* make a copy of chars from start to end inclusive */
-			s = pg_malloc(end - start + 2);
-			strlcpy(s, &buf[start], end - start + 2);
-		}
-
-		*previous_words++ = s;
-	}
-}
-
 /*
  * Look up the type for the GUC variable with the passed name.
  *
@@ -4909,4 +4121,402 @@ dequote_file_name(char *text, char quote_char)
 }
 #endif   /* NOT_USED */
 
+/* Element regular expressions */
+#define WB0  "[\\t\\n@$><=;|&{\\(\\) ]"			/* WORD BREAKS */
+#define NWB0 "[^\\t\\n@$><=;|&{\\(\\) ]"		/* ^WORD BREAKS */
+#define WB  "[\\t\\n@$><=;|&{ ]"				/* WORD BREAKS  - "()"*/
+#define WB2  "[\\t\\n@$><;|&{ ]"				/* WORD BREAKS  - "()="*/
+#define NWB "[^\\t\\n@$><=;|&{ ]"				/* ^(WORD BREAKS - "()") */
+#define PB  "^(?:.*;)?\\s*"			/* bind to the beginning of the line */
+#define PM  "^(?:.*"NWB0 WB0")?\\s*"					/* floating head */
+#define ID  NWB0"+"									/* identifier */
+#define KWD "\\w+"									/* any keywords */
+#define PSIZE_IS_64 (sizeof(void*) == 8)
+
+/* See hashfun.c for details */
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c, 4); \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+/* See hashfunc.c for details */
+static uint32
+hash_uint32(uint32 k)
+{
+	register uint32 a,
+				b,
+				c;
+
+	a = b = c = 0x9e3779b9 + (uint32) sizeof(uint32) + 3923095;
+	a += k;
+
+	final(a, b, c);
+
+	/* report the result */
+	return c;
+}
+
+/*
+ * Calculate hash code for the *pointer* of the pattern string, not the
+ * content of it.
+ */
+static int
+pathash(char *pat)
+{
+	uint32 t = 0;
+#if SIZEOF_VOID_P == 8
+	t = hash_uint32((uint32) ((uint64) pat >> 32));
+	t ^= hash_uint32((uint32) (uint64) pat);
+#else
+	t = hash_uint32((uint32) pat);
+#endif
+	return t % RE_HASH_SIZE;
+}
+
+/*
+ * Returns compiled regular expression for the given pattern and cflags.
+ *
+ * Converts the given pattern into regular expression and store in hash table
+ * for the first time, then returns the stored one for the same pattern.
+ *
+ * NOTE: pattern is not well-defined so it may yeild unexpected regular
+ * expression. Regular expressions is a intermediate data so it won't be
+ * stored usually but defining DEBUG_COMPRE will store them. The stored
+ * regular expressions can be examined using perl.
+ */
+#define INTBUF_LEN 4096
+static regex_t *
+patcomp(char *pat, int cflags)
+{
+	int n = pathash(pat);
+	re_hashent *ent = &re_hash[n];
+
+	/* Search for the stored entry for the pattern and cflags */
+	while (ent && ent->next &&
+		   !(ent->repat == pat && ent->cflags == cflags) )
+		ent = ent->next;
+
+	/* Create new entry if not found */
+	if (ent->repat != pat)
+	{
+		re_hashent *newent = pg_malloc(sizeof(re_hashent));
+		int ret;
+		char restr[INTBUF_LEN];			/* generated regular expression */
+		pg_wchar rewstr[INTBUF_LEN];	/* pg_regcomp requires RE in pg_wchar */
+		char *p, *rp;
+		int relen;
+		int nest_level = 1;				/* grouping nest level  */
+		int neg_nest_level = 0;			/* nest level for negative lookahead */
+		bool prev_is_ws = true;			/* previous character was white space */
+		bool no_ws = false;				/* inhibit prefixing white space */
+		bool prev_is_id = false;		/* previous item was identifier (#id)*/
+		/*
+		 * the following two strings cannot be defined as macos because the
+		 * pointers of them have significant in this function
+		 */
+		char *NORMAL_TAIL = NWB0"*$"; /* terminal string outside parens */
+		char *INPAREN_TAIL = "[^\\)]*$";/* terminal string inside parens */
+		char *tail;						/* terminal string of regexp */
+
+		Assert(ent->next == NULL);
+
+		newent->repat = pat;
+		newent->cflags = cflags;
+		newent->next = NULL;
+		ent->next = newent;
+
+		restr[0] = 0;
+
+		/*
+		 * If the pattern is not a whole-matching, prefix an appropriate
+		 * regular expression.
+		 */
+		if (*pat != '^')
+			strcpy(restr, PM);
+		rp = restr + strlen(restr);
+		no_ws = false;
+
+		/* Loop through the given pattern */
+		for (p = pat ; *p ; p++)
+		{
+			tail = NORMAL_TAIL;
+			prev_is_id = false;
+
+			/* @ inhibits automatic white-spacing */
+			if (*p ==  '@')
+			{
+				no_ws = true;
+				p++;
+			}
+			switch (*p)
+			{
+			case '.':
+				/* .. means the stuff of a pair of parens */
+				if (strncmp(p+1, ".", 1) == 0)
+				{
+					p++;
+					strcpy(rp, "[^\\)]*");
+				}
+				else
+				{
+					/* unknown pattern. restore terminal modes then exit */
+					rl_deprep_terminal();
+					exit(1);
+				}
+				break;
+			case '#':
+				/*
+				 * #id yields a regexp matches to any identifier including
+				 * double-quotes.
+				 */
+				if (strncmp(p+1, "id", 2) == 0)
+				{
+					p += 2;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, ID);
+					prev_is_id = true;
+					prev_is_ws = false;
+				}
+				/* #kwd matches keywords, which consists of regular word chars*/
+				else if (strncmp(p+1, "kwd", 3) == 0)
+				{
+					p += 3;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, "\\w+");
+					prev_is_ws = false;
+				}
+				/* #nwb matches a sequence of any non-word-breaking chars */
+				else if (strncmp(p+1, "nwb", 3) == 0)
+				{
+					p += 3;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, NWB"+");
+					prev_is_ws = false;
+				}
+				/* #nsp matches a sequence of any non-whitespaces */
+				else if (strncmp(p+1, "nsp", 3) == 0)
+				{
+					p += 3;
+					strcpy(rp, "[^ ]*");
+					prev_is_ws = false;
+				}
+				/*  #? matches literally a '?' */
+				else if (strncmp(p+1, "?", 1) == 0)
+				{
+					p += 1;
+					strcpy(rp, "\\?");
+					prev_is_ws = true;
+				}
+				else
+				{
+					/* unknown pattern. restore terminal modes then exit */
+					rl_deprep_terminal();
+					exit(1);
+				}
+				break;
+			case '^':
+				/* force prefix matching in a statement */
+				strcpy(rp, PB);
+				prev_is_ws = true;
+				break;
+			case ' ':
+				/* add a white space, if necessary or requested by @ */
+				if (!no_ws && !prev_is_ws)
+					strcpy(rp, WB"+");
+				prev_is_ws = true;
+				break;
+			case '(':
+				/* ( means literally an open paren  */
+				strcpy(rp, WB"*\\(");
+				prev_is_ws = true;
+				tail = INPAREN_TAIL;
+				break;
+			case ')':
+				/* ) means literally a close paren */
+				strcpy(rp, "\\)"WB"*");
+				prev_is_ws = true;
+				break;
+			case '%':
+				/* % inhibits suffix matching. this is used to filter the same
+                   kind of commands. */
+				tail = "";
+				break;
+			case '|':
+			case '?':
+			case '*':
+			case '+':
+				/* these characters have the same as regular expression */
+				rp[0] = *p;
+				rp[1] = 0;
+				break;
+			case '{':
+				/* { opens non-capturing group */
+				strcpy(rp, "(?:");
+				nest_level++;
+				break;
+			case '[':
+				/* [ opens capturing group */
+				strcpy(rp, "(");
+				nest_level++;
+				break;
+			case '}':
+			case ']':
+				/* closing groups. close negative lookahead if needed */
+				if (nest_level-- == neg_nest_level)
+				{
+					strcpy(rp, ")" NWB "+");
+					while(*rp) rp++;
+					neg_nest_level = 0;
+				}
+				strcpy(rp, ")");
+				break;
+			case '!':
+				/*
+				 * ! means negative lookahead. This is a bit a complex than
+				 * other elements. This group will be closed automatically by
+				 * some conditions. One is a space, and the another is closing
+				 * of outer parens.
+				 */
+				strcpy(rp, "(?!");
+				neg_nest_level = nest_level;
+				break;
+			default:
+				if (*p)
+				{
+					if (*p == '=')
+					{
+						/* Treat this as a word, not a character */
+						if (!no_ws && !prev_is_ws && neg_nest_level > 0)
+						{
+							strcpy(rp, WB"*");
+							while(*rp) rp++;
+						}
+						
+						strcpy(rp, "=");
+						while(*rp) rp++;
+					}
+					else
+					{
+						/* loop through a literal word */
+						do {
+							if (*p == '\\')
+							{
+								/* backslashes are treated literally in a
+								 * word */
+								strcpy(rp, "\\\\");
+								rp += 2;
+								p++;
+								prev_is_ws = false;
+							}
+							else if(*p == '_')
+							{
+								/* underscore means white space in word, which
+								 * is usable for multi-word token or so */
+								strcpy(rp, WB"+");
+								while(*rp) rp++;
+								p++;
+								prev_is_ws = true;
+							}
+							else if(isalnum(*p))
+							{
+								*rp++ = *p++;
+								prev_is_ws = false;
+							}
+							else
+								break;	/* end of a word. break this loop */
+						} while (1);
+						*rp = 0;
+						p--;
+					}
+				}
+
+				/* Close negative lookahread if no alternative successives */
+				if (neg_nest_level == nest_level &&
+					(p[1] == ' ' || p[1] == 0))
+				{
+					strcpy(rp, ")" NWB "+");
+					neg_nest_level = 0;
+				}
+			}
+
+			no_ws = false;
+			while (*rp) rp++;
+		}
+
+		/*
+		 * A special treat. If pattern ends with '?', forciblly add white space
+		 */
+		if (!no_ws && (!prev_is_ws || p[-1] == '?') && tail == NORMAL_TAIL)
+		{
+			if (prev_is_id)
+				strcpy(rp, WB2"+");
+			else
+				strcpy(rp, WB2"*");
+			while (*rp) rp++;
+		}
+		strcpy(rp, tail);
+
+		/* now we have a regular expression to use */
+#ifdef DEBUG_COMPRE
+		newent->restr = strdup(restr);
+#endif		
+		relen = pg_ascii2wchar_with_len(restr, rewstr, strlen(restr));
+		ret = pg_regcomp(&newent->re, rewstr, relen, cflags, C_COLLATION_OID);
+		if (ret != 0)
+		{
+			fprintf(stderr, "Failed to compile regular expression(%d): %s : /%s/\n",
+					ret, pat, restr);
+			rl_deprep_terminal();
+			exit(1);
+		}
+		ent = newent;
+	}
+
+	return &ent->re;
+}
+
+/* Apply given patterns to line buffer */
+bool
+rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches,
+		int cflags)
+{
+	int ret;
+	regex_t *re;
+	
+	re = patcomp(pat, cflags);
+	ret = pg_regexec(re, line, linelen, 0, NULL, MATCHNUM, rmatches, 0);
+
+	return ret == 0;
+}
+
+#ifdef DEBUG_COMPRE
+void dump_regexp(void);
+void
+dump_regexp(void)
+{
+	int i;
+
+	for (i = 0 ; i < RE_HASH_SIZE ; i++)
+	{
+		re_hashent *p = re_hash + i;
+
+		while (p) {
+			if (p->repat)
+				fprintf(stderr, "%03d: \"%s\" : /%s/\n", i, p->repat, p->restr);
+			p = p->next;
+		}
+	}
+}
+#endif /* DEBUG_COMPRE */
+
 #endif   /* USE_READLINE */
-- 
1.8.3.1

0004-Remove-less-informative-comments.patchtext/x-patch; charset=us-asciiDownload
>From e32dafeed39000e4e223676f2192fcd2e6e1bcb2 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 19 Nov 2015 16:03:48 +0900
Subject: [PATCH 4/7] Remove less informative comments.

Regular expressions made each matchings self-descriptive so most of
the comments simply mentioning what the following matching is are no
more necessary.
---
 src/bin/psql/tab-complete.c | 307 +++-----------------------------------------
 1 file changed, 19 insertions(+), 288 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7ede2a2..96073d1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1067,18 +1067,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
-	/* complete with something you can create */
 	else if (MATCH("^CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
-	/* complete with something you can drop */
 	else if (MATCH("^DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
-
-	/* ALTER TABLE */
 	else if (MATCH("^ALTER TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
@@ -1090,12 +1086,13 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("^ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE",
+		 "DEFAULT PRIVILEGES", "DOMAIN", "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "GROUP",
+		 "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW",
+		 "OPERATOR", "POLICY", "ROLE", "RULE", "SCHEMA", "SERVER",
+		 "SEQUENCE", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH",
+		 "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
@@ -1110,12 +1107,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
 	else if (MATCH("ALL IN TABLESPACE #id OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	/* ALTER AGGREGATE,FUNCTION <name> */
+
 	else if (MATCH("^ALTER {AGGREGATE|FUNCTION} #id"))
 		COMPLETE_WITH_CONST("(");
-	/* ALTER AGGREGATE,FUNCTION <name> (...) */
+
 	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](.."))
 		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+
 	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
 		static const char *const list_ALTERAGG[] =
@@ -1124,7 +1122,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
-	/* ALTER SCHEMA <name> */
 	else if (MATCH("ALTER SCHEMA #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1133,7 +1130,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER COLLATION <name> */
 	else if (MATCH("ALTER COLLATION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1142,7 +1138,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER CONVERSION <name> */
 	else if (MATCH("ALTER CONVERSION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1151,7 +1146,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER DATABASE <name> */
 	else if (MATCH("ALTER DATABASE #id"))
 	{
 		static const char *const list_ALTERDATABASE[] =
@@ -1161,13 +1155,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDATABASE);
 	}
 
-	/* ALTER EVENT TRIGGER */
 	else if (MATCH("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
-	/* ALTER EVENT TRIGGER <name> */
 	else if (MATCH("ALTER EVENT TRIGGER #id"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
@@ -1176,7 +1168,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER);
 	}
 
-	/* ALTER EVENT TRIGGER <name> ENABLE */
 	else if (MATCH("ALTER EVENT TRIGGER #id ENABLE"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
@@ -1185,7 +1176,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER_ENABLE);
 	}
 
-	/* ALTER EXTENSION <name> */
 	else if (MATCH("ALTER EXTENSION #id"))
 	{
 		static const char *const list_ALTEREXTENSION[] =
@@ -1194,7 +1184,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTEREXTENSION);
 	}
 
-	/* ALTER FOREIGN */
 	else if (MATCH("ALTER FOREIGN"))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
@@ -1203,7 +1192,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
 	}
 
-	/* ALTER FOREIGN DATA WRAPPER <name> */
 	else if (MATCH("ALTER FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_ALTER_FDW[] =
@@ -1212,7 +1200,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FDW);
 	}
 
-	/* ALTER FOREIGN TABLE <name> */
 	else if (MATCH("ALTER FOREIGN TABLE #id"))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
@@ -1222,14 +1209,10 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
-
-	/* ALTER INDEX */
 	else if (MATCH("ALTER INDEX"))
-	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
-	/* ALTER INDEX <name> */
+
 	else if (MATCH("ALTER INDEX #id"))
 	{
 		static const char *const list_ALTERINDEX[] =
@@ -1237,7 +1220,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERINDEX);
 	}
-	/* ALTER INDEX <name> SET */
 	else if (MATCH("ALTER INDEX #id SET"))
 	{
 		static const char *const list_ALTERINDEXSET[] =
@@ -1245,10 +1227,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
 	}
-	/* ALTER INDEX <name> RESET */
 	else if (MATCH("ALTER INDEX #id RESET"))
 		COMPLETE_WITH_CONST("(");
-	/* ALTER INDEX <foo> SET|RESET ( */
+
 	else if (MATCH("ALTER INDEX #id RESET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
@@ -1264,7 +1245,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
 	}
 
-	/* ALTER LANGUAGE <name> */
 	else if (MATCH("ALTER LANGUAGE #id"))
 	{
 		static const char *const list_ALTERLANGUAGE[] =
@@ -1273,7 +1253,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
 	}
 
-	/* ALTER LARGE OBJECT <oid> */
 	else if (MATCH("ALTER LARGE OBJECT #id"))
 	{
 		static const char *const list_ALTERLARGEOBJECT[] =
@@ -1282,14 +1261,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
 	}
 
-	/* ALTER MATERIALIZED VIEW */
 	else if (MATCH("ALTER MATERIALIZED VIEW"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 
-	/* ALTER USER,ROLE <name> */
 	else if (MATCH("ALTER {{USER !MAPPING}|{ROLE #id}}"))
 	{
 		static const char *const list_ALTERUSER[] =
@@ -1303,7 +1280,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERUSER);
 	}
 
-	/* ALTER USER,ROLE <name> WITH */
 	else if (MATCH("ALTER {USER|ROLE} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
@@ -1318,11 +1294,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
 	}
 
-	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
 	else if (MATCH("ALTER {ROLE|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 
-	/* ALTER DEFAULT PRIVILEGES */
 	else if (MATCH("ALTER DEFAULT PRIVILEGES"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
@@ -1330,7 +1304,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
 	}
-	/* ALTER DEFAULT PRIVILEGES FOR */
+
 	else if (MATCH("ALTER DEFAULT PRIVILEGES FOR"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
@@ -1346,7 +1320,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
 	}
-	/* ALTER DOMAIN <name> */
 	else if (MATCH("ALTER DOMAIN #id"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
@@ -1354,7 +1327,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
-	/* ALTER DOMAIN <sth> DROP */
 	else if (MATCH("ALTER DOMAIN #id DROP"))
 	{
 		static const char *const list_ALTERDOMAIN2[] =
@@ -1362,11 +1334,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
 	}
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
 	else if (MATCH("ALTER DOMAIN [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, CAPTURE(1));
 
-	/* ALTER DOMAIN <sth> RENAME */
 	else if (MATCH("ALTER DOMAIN #id RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
@@ -1374,11 +1344,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
-	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
 	else if (MATCH("ALTER DOMAIN #id RENAME CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* ALTER DOMAIN <sth> SET */
 	else if (MATCH("ALTER DOMAIN #id SET"))
 	{
 		static const char *const list_ALTERDOMAIN3[] =
@@ -1386,7 +1354,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
 	}
-	/* ALTER SEQUENCE <name> */
 	else if (MATCH("ALTER SEQUENCE #id"))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
@@ -1395,7 +1362,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
-	/* ALTER SEQUENCE <name> NO */
 	else if (MATCH("ALTER SEQUENCE #id NO"))
 	{
 		static const char *const list_ALTERSEQUENCE2[] =
@@ -1403,7 +1369,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
-	/* ALTER SERVER <name> */
 	else if (MATCH("ALTER SERVER #id"))
 	{
 		static const char *const list_ALTER_SERVER[] =
@@ -1411,7 +1376,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_SERVER);
 	}
-	/* ALTER SYSTEM SET, RESET, RESET ALL */
 	else if (MATCH("ALTER SYSTEM"))
 	{
 		static const char *const list_ALTERSYSTEM[] =
@@ -1419,11 +1383,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
 	}
-	/* ALTER SYSTEM SET|RESET <name> */
 	else if (MATCH("ALTER SYSTEM {SET|RESET}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
 
-	/* ALTER VIEW <name> */
 	else if (MATCH("ALTER VIEW #id"))
 	{
 		static const char *const list_ALTERVIEW[] =
@@ -1431,7 +1393,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
-	/* ALTER MATERIALIZED VIEW <name> */
 	else if (MATCH("ALTER MATERIALIZED VIEW #id"))
 	{
 		static const char *const list_ALTERMATVIEW[] =
@@ -1440,15 +1401,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
 	}
 
-	/* ALTER POLICY <name> ON */
 	else if (MATCH("ALTER POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* ALTER POLICY <name> ON <table> */
 	else if (MATCH("ALTER POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* ALTER POLICY <name> ON <table> - show options */
 	else if (MATCH("ALTER POLICY #id ON #id"))
 	{
 		static const char *const list_ALTERPOLICY[] =
@@ -1456,51 +1414,33 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERPOLICY);
 	}
-	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (MATCH("ALTER POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (MATCH("ALTER POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
 	else if (MATCH("ALTER POLICY #id ON #id WITH CHECK"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER RULE <name>, add ON */
 	else if (MATCH("ALTER RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* If we have ALTER RULE <name> ON, then add the correct tablename */
 	else if (MATCH("ALTER RULE [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
 
-	/* ALTER RULE <name> ON <name> */
 	else if (MATCH("ALTER RULE #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
-	/* ALTER TRIGGER <name>, add ON */
 	else if (MATCH("ALTER TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
 	else if (MATCH("ALTER TRIGGER [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
 
-//  !!! This duplicates with the entry just above
-//	/*
-//	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
-//	 */
-//	else if (MATCH("ALTER TRIGGER #id ON"))
-//		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-
-	/* ALTER TRIGGER <name> ON <name> */
 	else if (MATCH("ALTER TRIGGER #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
-	/*
-	 * If we detect ALTER TABLE <name>, suggest sub commands
-	 */
 	else if (MATCH("ALTER TABLE #id"))
 	{
 		static const char *const list_ALTER2[] =
@@ -1510,7 +1450,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
-	/* ALTER TABLE xxx ENABLE */
 	else if (MATCH("ALTER TABLE #id ENABLE"))
 	{
 		static const char *const list_ALTERENABLE[] =
@@ -1537,15 +1476,12 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
 
-	/* ALTER TABLE xxx INHERIT */
 	else if (MATCH("ALTER TABLE #id INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-	/* ALTER TABLE xxx NO INHERIT */
 	else if (MATCH("ALTER TABLE #id NO INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-	/* ALTER TABLE xxx DISABLE */
 	else if (MATCH("ALTER TABLE #id DISABLE"))
 	{
 		static const char *const list_ALTERDISABLE[] =
@@ -1567,33 +1503,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DISABLERLS);
 	}
 
-	/* ALTER TABLE xxx ALTER */
 	else if (MATCH("ALTER TABLE [#id] ALTER"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
-	/* ALTER TABLE xxx RENAME */
 	else if (MATCH("ALTER TABLE [#id] RENAME"))
 		COMPLETE_WITH_ATTR(CAPTURE(1),
 						   " UNION SELECT 'COLUMN'"
 						   " UNION SELECT 'CONSTRAINT'"
 						   " UNION SELECT 'TO'");
 
-	/*
-	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
-	 * columns
-	 */
 	else if (MATCH("TABLE [#id] {ALTER|RENAME} COLUMN"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* ALTER TABLE xxx RENAME yyy */
 	else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
 	else if (MATCH("TABLE #id RENAME {COLUMN|CONSTRAINT} !TO"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (MATCH("TABLE #id DROP"))
 	{
 		static const char *const list_TABLEDROP[] =
@@ -1601,18 +1528,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEDROP);
 	}
-	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (MATCH("ALTER TABLE [#id] DROP COLUMN"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
-	 * provide list of constraints
-	 */
 	else if (MATCH("ALTER TABLE [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, CAPTURE(1));
 
-	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id"))
 	{
 		static const char *const list_COLUMNALTER[] =
@@ -1620,7 +1541,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNALTER);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET"))
 	{
 		static const char *const list_COLUMNSET[] =
@@ -1628,7 +1548,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNSET);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET ("))
 	{
 		static const char *const list_COLUMNOPTIONS[] =
@@ -1636,7 +1555,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET STORAGE"))
 	{
 		static const char *const list_COLUMNSTORAGE[] =
@@ -1644,7 +1562,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id DROP"))
 	{
 		static const char *const list_COLUMNDROP[] =
@@ -1658,7 +1575,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TABLE [#id] CLUSTER ON"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
-	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
 	else if (MATCH("ALTER TABLE #id SET"))
 	{
 		static const char *const list_TABLESET[] =
@@ -1666,11 +1582,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLESET);
 	}
-	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
 	else if (MATCH("ALTER TABLE #id SET TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 
-	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
 	else if (MATCH("ALTER TABLE #id SET WITHOUT"))
 	{
 		static const char *const list_TABLESET2[] =
@@ -1678,11 +1592,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLESET2);
 	}
-	/* ALTER TABLE <foo> RESET */
 	else if (MATCH("ALTER TABLE #id RESET"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER TABLE <foo> SET|RESET ( */
 	else if (MATCH("ALTER TABLE #id {RE}?SET("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
@@ -1736,7 +1648,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("TABLE #id REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
-	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
 	else if (MATCH("ALTER TABLESPACE #id"))
 	{
 		static const char *const list_ALTERTSPC[] =
@@ -1744,11 +1655,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTSPC);
 	}
-	/* ALTER TABLESPACE <foo> SET|RESET */
 	else if (MATCH("ALTER TABLESPACE #id {RE}?SET"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (MATCH("ALTER TABLESPACE #id {RE}?SET("))
 	{
 		static const char *const list_TABLESPACEOPTIONS[] =
@@ -1757,7 +1666,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
 	}
 
-	/* ALTER TEXT SEARCH */
 	else if (MATCH("ALTER TEXT SEARCH"))
 	{
 		static const char *const list_ALTERTEXTSEARCH[] =
@@ -1789,7 +1697,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
 	}
 
-	/* complete ALTER TYPE <foo> with actions */
 	else if (MATCH("ALTER TYPE #id"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1798,7 +1705,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (MATCH("ALTER TYPE #id ADD"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1806,7 +1712,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* ALTER TYPE <foo> RENAME	*/
 	else if (MATCH("ALTER TYPE #id RENAME"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1814,22 +1719,15 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
 	else if (MATCH("TYPE #id RENAME ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	/*
-	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
-	 * attributes
-	 */
 	else if (MATCH("TYPE [#id] {ALTER|DROP|RENAME} ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	else if (MATCH("ALTER ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TYPE");
 
-	/* complete ALTER GROUP <foo> */
 	else if (MATCH("ALTER GROUP #id"))
 	{
 		static const char *const list_ALTERGROUP[] =
@@ -1837,11 +1735,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERGROUP);
 	}
-	/* complete ALTER GROUP <foo> ADD|DROP with USER */
 	else if (MATCH("ALTER GROUP #id {ADD|DROP}"))
 		COMPLETE_WITH_CONST("USER");
 
-	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
 	else if (MATCH("ALTER GROUP #id {ADD|DROP} USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
@@ -1874,37 +1770,22 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* CLUSTER */
-
-	/*
-	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
-	 */
 	else if (MATCH(" !WITHOUT CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   "UNION SELECT 'VERBOSE'");
 
-	/*
-	 * If the previous words are CLUSTER VERBOSE produce list of tables
-	 */
 	else if (MATCH("CLUSTER VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
-	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (MATCH("CLUSTER !ON|VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
 
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
 	else if (MATCH("CLUSTER VERBOSE #id"))
 		COMPLETE_WITH_CONST("USING");
 
-	/*
-	 * If we have CLUSTER <sth> USING, then add the index as well.
-	 */
 	else if (MATCH("CLUSTER [#id] USING"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
-	/*
-	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
-	 */
 	else if (MATCH("CLUSTER VERBOSE [#id] USING"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
@@ -1958,15 +1839,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
-
-	/*
-	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
-	 * list of tables (Also cover the analogous backslash command)
-	 */
 	else if (MATCH("\\?COPY{ BINARY}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
 	else if (MATCH("\\?COPY{ BINARY}? #id"))
 	{
 		static const char *const list_FROMTO[] =
@@ -1974,14 +1849,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_FROMTO);
 	}
-	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
 	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO}"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
-	/* Handle COPY|BINARY <sth> FROM|TO filename */
 	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO} #nwb"))
 	{
 		static const char *const list_COPY[] =
@@ -1990,7 +1863,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COPY);
 	}
 
-	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
 	else if (MATCH("{FROM|TO} #nwb CSV"))
 	{
 		static const char *const list_CSV[] =
@@ -1999,7 +1871,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CSV);
 	}
 
-	/* CREATE DATABASE */
 	else if (MATCH("CREATE DATABASE #id"))
 	{
 		static const char *const list_DATABASE[] =
@@ -2013,12 +1884,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE DATABASE #id TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
-	/* CREATE EXTENSION */
-	/* Complete with available extensions rather than installed ones. */
 	else if (MATCH("CREATE EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
 
-	/* CREATE EXTENSION <name> */
 	else if (MATCH("CREATE EXTENSION #id"))
 	{
 		static const char *const list_CREATE_EXTENSION[] =
@@ -2026,12 +1894,11 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
 	}
-	/* CREATE EXTENSION <name> VERSION */
+
 	else if (MATCH("CREATE EXTENSION [#id] VERSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
 							CAPTURE(1));
 
-	/* CREATE FOREIGN */
 	else if (MATCH("CREATE FOREIGN"))
 	{
 		static const char *const list_CREATE_FOREIGN[] =
@@ -2040,7 +1907,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
 	}
 
-	/* CREATE FOREIGN DATA WRAPPER */
 	else if (MATCH("CREATE FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
@@ -2049,25 +1915,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_FOREIGN_DATA_WRAPPER);
 	}
 
-	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (MATCH("CREATE UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
 	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX [<name>] ON with a list of tables  */
+
 	else if (MATCH("INDEX {CONCURRENTLY }? #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
-	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
 	else if (MATCH("INDEX #id CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
 	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 	{
 		static const char *const list_CREATE_INDEX[] =
@@ -2090,11 +1952,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("INDEX #id {CONCURRENTLY }? ON [#id]("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* same if you put in USING */
 	else if (MATCH("ON [#id] USING #id("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* Complete USING with an index method */
 	else if (MATCH("INDEX #id {#nwb }* ON #id USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
 
@@ -2102,15 +1962,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
-	/* Complete "CREATE POLICY <name> ON" */
 	else if (MATCH("CREATE POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (MATCH("CREATE POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (MATCH("CREATE POLICY #id ON #id"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2118,11 +1975,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR
-	 * ALL|SELECT|INSERT|UPDATE|DELETE"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR"))
 	{
 		static const char *const list_POLICYCMDS[] =
@@ -2130,7 +1982,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYCMDS);
 	}
-	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
 	else if (MATCH("CREATE POLICY #id ON #id FOR INSERT"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2138,11 +1989,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
-	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR {SELECT|DELETE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2150,12 +1996,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK"
-	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
-	 * CHECK"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR {ALL|UPDATE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2163,24 +2003,19 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (MATCH("CREATE POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (MATCH("CREATE POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
-	/* Complete "CREATE RULE <sth>" with "AS" */
 	else if (MATCH("CREATE RULE #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE RULE <sth> AS with "ON" */
 	else if (MATCH("CREATE RULE #id AS"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
 	else if (MATCH("RULE #id AS ON"))
 	{
 		static const char *const rule_events[] =
@@ -2188,15 +2023,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(rule_events);
 	}
-	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
 	else if (MATCH("AS ON {SELECT|UPDATE|INSERT|DELETE}"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* Complete "AS ON <sth> TO" with a table name */
 	else if (MATCH("AS ON #kwd TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE TEMP/TEMPORARY SEQUENCE <name> */
 	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id"))
 	{
 		static const char *const list_CREATESEQUENCE[] =
@@ -2205,7 +2037,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
-/* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
 	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id NO"))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
@@ -2214,7 +2045,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE2);
 	}
 
-/* CREATE SERVER <name> */
 	else if (MATCH("CREATE SERVER #id"))
 	{
 		static const char *const list_CREATE_SERVER[] =
@@ -2224,7 +2054,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE TABLE */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (MATCH("CREATE {TEMP{ORARY}?}"))
 	{
 		static const char *const list_TEMP[] =
@@ -2232,7 +2061,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TEMP);
 	}
-	/* Complete "CREATE UNLOGGED" with TABLE */
 	else if (MATCH("CREATE UNLOGGED"))
 	{
 		static const char *const list_UNLOGGED[] =
@@ -2249,7 +2077,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
 	}
-	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
 	else if (MATCH("CREATE TABLESPACE #id OWNER"))
 		COMPLETE_WITH_CONST("LOCATION");
 
@@ -2265,7 +2092,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
-	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
 	else if (MATCH("CREATE TRIGGER #id"))
 	{
 		static const char *const list_CREATETRIGGER[] =
@@ -2273,7 +2099,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER);
 	}
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
 	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER}"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
@@ -2281,7 +2106,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
-	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
 	else if (MATCH("CREATE TRIGGER #id INSTEAD OF"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
@@ -2289,7 +2113,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
 	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER|INSTEAD_OF} #kwd"))
 	{
 		static const char *const list_CREATETRIGGER2[] =
@@ -2297,23 +2120,15 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
 	}
-
-	/*
-	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
-	 * tables
-	 */
 	else if (MATCH("TRIGGER #id {BEFORE|AFTER} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (MATCH("INSTEAD OF #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (MATCH("CREATE TRIGGER {#nwb }+ EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
-/* CREATE ROLE,USER,GROUP <name> */
 	else if (MATCH("CREATE {{USER !MAPPING_}|ROLE |GROUP } #id"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2327,7 +2142,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATEROLE);
 	}
 
-/* CREATE ROLE,USER,GROUP <name> WITH */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
@@ -2342,14 +2156,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
 	}
 
-	/*
-	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
-	 * PASSWORD
-	 */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 
-	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id IN"))
 	{
 		static const char *const list_CREATEROLE3[] =
@@ -2359,11 +2168,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE VIEW */
-	/* Complete CREATE VIEW <name> with AS */
 	else if (MATCH("CREATE VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (MATCH("CREATE VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
@@ -2371,11 +2178,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (MATCH("CREATE MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
 	else if (MATCH("CREATE MATERIALIZED VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
@@ -2383,11 +2188,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 
-	/* Complete CREATE EVENT TRIGGER <name> with ON */
 	else if (MATCH("CREATE EVENT TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
 	else if (MATCH("CREATE EVENT TRIGGER #id ON"))
 	{
 		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
@@ -2424,11 +2227,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("!ON|GRANT|BEFORE|AFTER DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 
-	/* Complete DELETE FROM with a list of tables */
 	else if (MATCH("DELETE FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 
-	/* Complete DELETE FROM <table> */
 	else if (MATCH("DELETE FROM #id"))
 	{
 		static const char *const list_DELETE[] =
@@ -2448,10 +2249,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DO */
-
-	/*
-	 * Complete DO with LANGUAGE.
-	 */
 	else if (MATCH("DO"))
 	{
 		static const char *const list_DO[] =
@@ -2461,11 +2258,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DROP (when not the previous word) */
-	/* DROP AGGREGATE */
 	else if (MATCH("DROP AGGREGATE #id"))
 		COMPLETE_WITH_CONST("(");
 
-	/* DROP object with CASCADE / RESTRICT */
 	else if (MATCH("DROP {{{COLLATION|CONVERSION|DOMAIN|EXTENSION|[FUNCTION]|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW|{EVENT TRIGGER}|{FOREGN DATA WRAPPER}|{TEXT SEARCH {CONFIGURATION|DICTIONARY|PARSER|TEMPLATE}}} #id }|{AGGREGATE #id(..)}}"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "FUNCTION") == 0)
@@ -2488,7 +2283,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
 	}
 
-	/* DROP MATERIALIZED VIEW */
 	else if (MATCH("DROP MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
@@ -2498,7 +2292,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("DROP {AGGREGATE|FUNCTION} [#id]("))
 		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
 
-	/* DROP OWNED BY */
 	else if (MATCH("DROP OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (MATCH("DROP OWNED BY"))
@@ -2513,7 +2306,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
 	}
 
-	/* DROP TRIGGER */
 	else if (MATCH("DROP TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 	else if (MATCH("DROP TRIGGER [#id] ON"))
@@ -2526,26 +2318,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DROPCR);
 	}
 
-	/* DROP EVENT TRIGGER */
 	else if (MATCH("DROP EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 
 	else if (MATCH("DROP_EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
-	/* DROP POLICY <name>  */
 	else if (MATCH("DROP POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
 
-	/* DROP POLICY <name> ON */
 	else if (MATCH("DROP POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* DROP POLICY <name> ON <table> */
 	else if (MATCH("DROP POLICY [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, CAPTURE(1));
 
-	/* DROP RULE */
 	else if (MATCH("DROP RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2592,7 +2379,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* FETCH && MOVE */
-	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	else if (MATCH("{FETCH|MOVE}"))
 	{
 		static const char *const list_FETCH1[] =
@@ -2600,7 +2386,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_FETCH1);
 	}
-	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
 	else if (MATCH("{FETCH|MOVE} #id"))
 	{
 		static const char *const list_FETCH2[] =
@@ -2623,20 +2408,16 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* FOREIGN DATA WRAPPER */
-	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (MATCH("{ALTER|DROP} FOREIGN DATA WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
-/* FOREIGN TABLE */
 	else if (MATCH("CREATE FOREIGN TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
-/* FOREIGN SERVER */
 	else if (MATCH("FOREIGN SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
-	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (MATCH("{GRANT|REVOKE}"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
@@ -2655,11 +2436,6 @@ psql_completion(const char *text, int start, int end)
 							" UNION SELECT 'ALL'",
 							NULL);
 	}
-
-	/*
-	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
-	 * TO/FROM
-	 */
 	else if (MATCH("{GRANT|REVOKE} {SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMP{ORARY}?|EXECUTE|USAGE|ALL}"))
 			COMPLETE_WITH_CONST("ON");
 	else if (MATCH("GRANT #id"))
@@ -2727,7 +2503,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2736,7 +2511,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN DATA WRAPPER #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2745,7 +2519,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN SERVER #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2754,35 +2527,24 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/*
-	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
-	 * CURRENT_USER, or SESSION_USER.
-	 */
 	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
 	else if (MATCH("GRANT #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	else if (MATCH("REVOKE #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("FROM");
 
-	/*
-	 * Complete "GRANT/REVOKE * TO/FROM" with username, PUBLIC,
-	 * CURRENT_USER, or SESSION_USER.
-	 */
 	else if (MATCH("GRANT #kwd TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	else if (MATCH("REVOKE #kwd FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-/* GROUP BY */
 	else if (MATCH("FROM #id GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
-/* IMPORT FOREIGN SCHEMA */
 	else if (MATCH("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
 
@@ -2790,22 +2552,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
-	/* Complete INSERT with "INTO" */
 	else if (MATCH("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 
-	/* Complete INSERT INTO with table names */
 	else if (MATCH("INSERT INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 
-	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (MATCH("INSERT INTO [#id]("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/*
-	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
-	 * "TABLE" or "DEFAULT VALUES"
-	 */
 	else if (MATCH("INSERT INTO #id"))
 	{
 		static const char *const list_INSERT[] =
@@ -2814,10 +2569,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INSERT);
 	}
 
-	/*
-	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
-	 * "TABLE"
-	 */
 	else if (MATCH("INSERT INTO #id(..)"))
 	{
 		static const char *const list_INSERT[] =
@@ -2826,12 +2577,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INSERT);
 	}
 
-	/* Insert an open parenthesis after "VALUES" */
 	else if (MATCH("!DEFAULT VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
-	/* Complete LOCK [TABLE] with a list of tables */
 	else if (MATCH("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
@@ -2841,11 +2590,9 @@ psql_completion(const char *text, int start, int end)
 
 	/* For the following, handle the case of a single table only for now */
 
-	/* Complete LOCK [TABLE] <table> with "IN" */
 	else if (MATCH("LOCK{ TABLE}? !TABLE"))
 		COMPLETE_WITH_CONST("IN");
 
-	/* Complete LOCK [TABLE] <table> IN with a lock mode */
 	else if (MATCH("LOCK{ TABLE}? #id IN"))
 	{
 		static const char *const lock_modes[] =
@@ -2858,26 +2605,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(lock_modes);
 	}
 
-/* NOTIFY */
 	else if (MATCH("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
-/* OPTIONS */
 	else if (MATCH("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
-/* OWNER TO  - complete with available roles */
 	else if (MATCH("OWNER TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
-/* ORDER BY */
 	else if (MATCH("FROM #id ORDER"))
 		COMPLETE_WITH_CONST("BY");
 
 	else if (MATCH("FROM [#id] ORDER BY"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-/* PREPARE xx AS */
 	else if (MATCH("PREPARE #id AS"))
 	{
 		static const char *const list_PREPARE[] =
@@ -2969,7 +2711,6 @@ psql_completion(const char *text, int start, int end)
 	/* naah . . . */ 
 
 /* SET, RESET, SHOW */
-	/* Complete with a variable name */
 	else if (MATCH("^{RE}?SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
 	else if (MATCH("SHOW"))
@@ -3029,10 +2770,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(constraint_list);
 	}
-	/* Complete SET ROLE */
 	else if (MATCH("SET ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
+
 	else if (MATCH("SET SESSION"))
 	{
 		static const char *const my_list[] =
@@ -3040,15 +2780,14 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (MATCH("SET SESSION AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
 							NULL);
-	/* Complete RESET SESSION with AUTHORIZATION */
+
 	else if (MATCH("RESET SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 
-	/* Complete SET <var> with "TO" */
+
 	else if (MATCH("^{!UPDATE }*SET !TABLESPACE|SCHEMA|="))
 		COMPLETE_WITH_CONST("TO");
 
@@ -3108,7 +2847,6 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-/* START TRANSACTION */
 	else if (MATCH("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
@@ -3123,19 +2861,16 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("TABLESAMPLE #id"))
 		COMPLETE_WITH_CONST("(");
 
-/* TRUNCATE */
 	else if (MATCH("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* UNLISTEN */
 	else if (MATCH("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
-	/* If prev. word is UPDATE suggest a list of tables */
 	else if (MATCH("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
-	/* Complete UPDATE <table> with "SET" */
+
 	else if (MATCH("UPDATE #id"))
 		COMPLETE_WITH_CONST("SET");
 
@@ -3147,11 +2882,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("[#id] SET"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-/* UPDATE xx SET yy = */
 	else if (MATCH("UPDATE #id SET #id"))
 		COMPLETE_WITH_CONST("=");
 
-/* USER MAPPING */
 	else if (MATCH("{ALTER|DROP|CREATE} USER MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 
@@ -3205,12 +2938,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
-	/* If the previous word is ANALYZE, produce list of tables */
 	else if (MATCH("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
-	/* Simple case of the word before the where being the table name */
 	else if (MATCH("[#id] WHERE"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-- 
1.8.3.1

0005-Merge-mergable-completions.patchtext/x-patch; charset=us-asciiDownload
>From a265bd114e77b112488da29a641cefe416c520cf Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 19 Nov 2015 17:32:34 +0900
Subject: [PATCH 5/7] Merge mergable completions.

Some entries are mergable by regular expressions. This commit merges
such entries.
---
 src/bin/psql/tab-complete.c | 184 +++++++++++---------------------------------
 1 file changed, 46 insertions(+), 138 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 96073d1..875583c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -853,15 +853,15 @@ static const pgsql_thing_t words_after_create[] = {
 	{"DATABASE", Query_for_list_of_databases},
 	{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW},
 	{"DOMAIN", NULL, &Query_for_list_of_domains},
-	{"EVENT TRIGGER", NULL, NULL},
+	{"EVENT TRIGGER", Query_for_list_of_event_triggers, NULL},
 	{"EXTENSION", Query_for_list_of_extensions},
-	{"FOREIGN DATA WRAPPER", NULL, NULL},
+	{"FOREIGN DATA WRAPPER", Query_for_list_of_fdws, NULL},
 	{"FOREIGN TABLE", NULL, NULL},
 	{"FUNCTION", NULL, &Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
 	{"LANGUAGE", Query_for_list_of_languages},
 	{"INDEX", NULL, &Query_for_list_of_indexes},
-	{"MATERIALIZED VIEW", NULL, NULL},
+	{"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
 	{"OPERATOR", NULL, NULL},	/* Querying for this is probably not such a
 								 * good idea. */
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
@@ -1130,15 +1130,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	else if (MATCH("ALTER COLLATION #id"))
-	{
-		static const char *const list_ALTERGEN[] =
-		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERGEN);
-	}
-
-	else if (MATCH("ALTER CONVERSION #id"))
+	else if (MATCH("ALTER {COLLATION|CONVERSION} #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1156,9 +1148,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	else if (MATCH("ALTER EVENT TRIGGER"))
-	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
-	}
 
 	else if (MATCH("ALTER EVENT TRIGGER #id"))
 	{
@@ -1262,10 +1252,8 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	else if (MATCH("ALTER MATERIALIZED VIEW"))
-	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	else if (MATCH("ALTER {{USER !MAPPING}|{ROLE #id}}"))
 	{
@@ -1386,21 +1374,13 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER SYSTEM {SET|RESET}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
 
-	else if (MATCH("ALTER VIEW #id"))
+	else if (MATCH("ALTER{ MATERIALIZED}? VIEW #id"))
 	{
 		static const char *const list_ALTERVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
-	else if (MATCH("ALTER MATERIALIZED VIEW #id"))
-	{
-		static const char *const list_ALTERMATVIEW[] =
-		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
-
-		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
-	}
-
 	else if (MATCH("ALTER POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
@@ -1417,28 +1397,19 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	else if (MATCH("ALTER POLICY #id ON #id USING"))
+	else if (MATCH("ALTER POLICY #id ON #id {USING|{WITH CHECK}}"))
 		COMPLETE_WITH_CONST("(");
 
-	else if (MATCH("ALTER POLICY #id ON #id WITH CHECK"))
-		COMPLETE_WITH_CONST("(");
-
-	else if (MATCH("ALTER RULE #id"))
+	else if (MATCH("ALTER {RULE|TRIGGER} #id"))
 		COMPLETE_WITH_CONST("ON");
 
 	else if (MATCH("ALTER RULE [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
 
-	else if (MATCH("ALTER RULE #id ON #id"))
-		COMPLETE_WITH_CONST("RENAME TO");
-
-	else if (MATCH("ALTER TRIGGER #id"))
-		COMPLETE_WITH_CONST("ON");
-
 	else if (MATCH("ALTER TRIGGER [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
 
-	else if (MATCH("ALTER TRIGGER #id ON #id"))
+	else if (MATCH("ALTER {RULE|TRIGGER} #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	else if (MATCH("ALTER TABLE #id"))
@@ -1464,22 +1435,13 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE2);
 	}
-	else if (MATCH("ALTER TABLE [#id] ENABLE RULE"))
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
-
-	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd RULE"))
+	else if (MATCH("ALTER TABLE [#id] ENABLE{ {REPLICA|ALWAYS}}? RULE"))
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
 
-	else if (MATCH("ALTER TABLE [#id] ENABLE TRIGGER"))
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
-
-	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd TRIGGER"))
+	else if (MATCH("ALTER TABLE [#id] ENABLE{ {REPLICA|ALWAYS}}? TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
 
-	else if (MATCH("ALTER TABLE #id INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-
-	else if (MATCH("ALTER TABLE #id NO INHERIT"))
+	else if (MATCH("ALTER TABLE #id{ NO}? INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	else if (MATCH("ALTER TABLE #id DISABLE"))
@@ -1495,14 +1457,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TABLE [#id] DISABLE TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
 
-	else if (MATCH("DISABLE ROW LEVEL SECURITY"))
-	{
-		static const char *const list_DISABLERLS[] =
-		{"CASCADE", NULL};
-
-		COMPLETE_WITH_LIST(list_DISABLERLS);
-	}
-
 	else if (MATCH("ALTER TABLE [#id] ALTER"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
@@ -1692,7 +1646,8 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TEXT SEARCH CONFIGURATION #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH4[] =
-		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR",
+		 "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
 	}
@@ -1770,23 +1725,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* CLUSTER */
-	else if (MATCH(" !WITHOUT CLUSTER"))
+	else if (MATCH("^CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   "UNION SELECT 'VERBOSE'");
 
-	else if (MATCH("CLUSTER VERBOSE"))
+	else if (MATCH("^CLUSTER{ VERBOSE}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
-	else if (MATCH("CLUSTER !ON|VERBOSE"))
-		COMPLETE_WITH_CONST("USING");
-
-	else if (MATCH("CLUSTER VERBOSE #id"))
+	else if (MATCH("^CLUSTER{ VERBOSE}? #id"))
 		COMPLETE_WITH_CONST("USING");
 
-	else if (MATCH("CLUSTER [#id] USING"))
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
-
-	else if (MATCH("CLUSTER VERBOSE [#id] USING"))
+	else if (MATCH("^CLUSTER{ VERBOSE}? [#id] USING"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
 /* COMMENT */
@@ -1919,37 +1868,29 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
+	else if (MATCH("CREATE{ UNIQUE}? INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 
-	else if (MATCH("INDEX {CONCURRENTLY }? #id ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-
-	else if (MATCH("INDEX #id CONCURRENTLY"))
+	else if (MATCH("INDEX{ CONCURRENTLY}? #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
-	{
-		static const char *const list_CREATE_INDEX[] =
-		{"CONCURRENTLY", "ON", NULL};
-
-		COMPLETE_WITH_LIST(list_CREATE_INDEX);
-	}
+	else if (MATCH("INDEX{ CONCURRENTLY}? #id ON"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (MATCH("INDEX #id {CONCURRENTLY }? ON #id"))
+	else if (MATCH("INDEX{ CONCURRENTLY}?{ #id}? ON #id"))
 	{
 		static const char *const list_CREATE_INDEX2[] =
 		{"(", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
 	}
-	else if (MATCH("INDEX #id {CONCURRENTLY }? ON [#id]("))
+	else if (MATCH("INDEX{ CONCURRENTLY }?{ #id}? ON [#id]("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	else if (MATCH("ON [#id] USING #id("))
@@ -2029,7 +1970,7 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("AS ON #kwd TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id"))
+	else if (MATCH("CREATE{ TEMP{ORARY}?}? SEQUENCE #id"))
 	{
 		static const char *const list_CREATESEQUENCE[] =
 		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
@@ -2037,7 +1978,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
-	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id NO"))
+	else if (MATCH("CREATE{ TEMP{ORARY}?}? SEQUENCE #id NO"))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -2126,7 +2067,7 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("INSTEAD OF #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (MATCH("CREATE TRIGGER {#nwb }+ EXECUTE"))
+	else if (MATCH("CREATE TRIGGER{ #nwb}+ EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 	else if (MATCH("CREATE {{USER !MAPPING_}|ROLE |GROUP } #id"))
@@ -2495,31 +2436,16 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete only "GRANT/REVOKE * ON * " with "TO/FROM" here.
 	 */
-	else if (MATCH("[GRANT|REVOKE] #kwd ON !DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
-	{
-		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
-
-	else if (MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id"))
-	{
-		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
+	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN DATA WRAPPER #id"))
-	{
-		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
-			COMPLETE_WITH_CONST("TO");
-		else
-			COMPLETE_WITH_CONST("FROM");
-	}
+	else if (MATCH("{GRANT|REVOKE} #kwd TO"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN SERVER #id"))
+	else if (MATCH("[GRANT|REVOKE] #kwd ON !DATABASE|DOMAIN|FOREIGN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE") ||
+			 MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id") ||
+			 MATCH("[GRANT|REVOKE] #kwd ON FOREIGN {SERVER|{DATA WRAPPER}} #id") ||
+			 MATCH("[GRANT|REVOKE] #kwd ON #kwd #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
@@ -2527,21 +2453,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-
-	else if (MATCH("GRANT #kwd ON #kwd #id"))
-		COMPLETE_WITH_CONST("TO");
-
-	else if (MATCH("REVOKE #kwd ON #kwd #id"))
-		COMPLETE_WITH_CONST("FROM");
-
-	else if (MATCH("GRANT #kwd TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-
-	else if (MATCH("REVOKE #kwd FROM"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-
 	else if (MATCH("FROM #id GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
@@ -2638,23 +2549,20 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("OWNED");
 	else if (MATCH("REASSIGN OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (MATCH("REASSIGN OWNED BY"))
+	else if (MATCH("REASSIGN OWNED BY") ||
+			 MATCH("REASSIGN OWNED BY #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (MATCH("REASSIGN OWNED BY #id"))
 		COMPLETE_WITH_CONST("TO");
-	else if (MATCH("REASSIGN OWNED BY #id TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (MATCH("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
 	else if (MATCH("REFRESH MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (MATCH("REFRESH MATERIALIZED VIEW"))
+	else if (MATCH("REFRESH MATERIALIZED VIEW{ CONCURRENTLY}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (MATCH("REFRESH MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("WITH");
 	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY #id"))
@@ -2909,23 +2817,22 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (MATCH("VACUUM {FULL|FREEZE}"))
+	else if (MATCH("VACUUM FULL"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (MATCH("VACUUM {FULL|FREEZE} ANALYZE"))
+	else if (MATCH("VACUUM{ FULL}? FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (MATCH("VACUUM {FULL|FREEZE} VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
-								   " UNION SELECT 'ANALYZE'");
-	else if (MATCH("VACUUM VERBOSE"))
+	else if (MATCH("VACUUM{ FULL}?{ FREEZE}? VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (MATCH("VACUUM ANALYZE"))
+	else if (MATCH("VACUUM{ FULL}?{ FREEZE}? ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (MATCH("{{VERBOSE ANALYZE}|{ANALYZE VERBOSE}}"))
+	else if (MATCH("VACUUM{ FULL}?{ FREEZE}?{ ANALYZE VERBOSE|VERBOSE ANALYZE}"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3170,7 +3077,8 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else if(MATCH("[#kwd]"))
+	else if(MATCH("[{FOREIGN DATA WRAPPER}|{MATERIALIZED VIEW}|{EVENT TRIGGER}]") ||
+			MATCH("[#kwd]"))
 	{
 		int			i;
 
-- 
1.8.3.1

#41Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#40)
Re: Making tab-complete.c easier to maintain

On Thu, Nov 26, 2015 at 2:45 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

What do you think about this solution?

<please do not top-post, it breaks the logic of the thread>

This patch fails to compile on OSX:
Undefined symbols for architecture x86_64:
"_ExceptionalCondition", referenced from:
_pg_regexec in regexec.o
_cfind in regexec.o
_find in regexec.o
_newdfa in regexec.o
_cfindloop in regexec.o
_shortest in regexec.o
_cdissect in regexec.o
...
So, to begin with, this may be better if replugged as a standalone
library, aka moving the regexp code into src/common for example or
similar. Also, per the comments on top of rcancelrequested,
rstacktoodeep and rcancelrequested, returning unconditionally 0 is not
a good idea for -DFRONTEND. Callbacks should be defined and made
available for callers.

-       {"EVENT TRIGGER", NULL, NULL},
+       {"EVENT TRIGGER", Query_for_list_of_event_triggers, NULL},
        {"EXTENSION", Query_for_list_of_extensions},
-       {"FOREIGN DATA WRAPPER", NULL, NULL},
+       {"FOREIGN DATA WRAPPER", Query_for_list_of_fdws, NULL},
        {"FOREIGN TABLE", NULL, NULL},
        {"FUNCTION", NULL, &Query_for_list_of_functions},
        {"GROUP", Query_for_list_of_roles},
        {"LANGUAGE", Query_for_list_of_languages},
        {"INDEX", NULL, &Query_for_list_of_indexes},
-       {"MATERIALIZED VIEW", NULL, NULL},
+       {"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
This has value as a separate patch.

The patch has many whitespaces, and unrelated diffs.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#36)
Re: Making tab-complete.c easier to maintain

On Tue, Nov 17, 2015 at 12:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

+       /* TODO:TM -- begin temporary, not part of the patch! */
+       Assert(!word_matches(NULL, ""));
+ [...]
+       Assert(!word_matches("foo", ""));
+       /* TODO:TM -- end temporary */

Be sure to not forget to remove that later.

-       else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-                        pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-                        (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-                         pg_strcasecmp(prev3_wd, "IN") == 0))
-       {
-               static const char *const
list_ALTER_DEFAULT_PRIVILEGES_REST[] =
-               {"GRANT", "REVOKE", NULL};
-
-               COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
-       }
+       else if (TailMatches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE",
MatchAny) ||
+                        TailMatches5("DEFAULT", "PRIVILEGES", "IN",
"SCHEMA", MatchAny))
+               CompleteWithList2("GRANT", "REVOKE");
For this chunk I think that you need to check for ROLE|USER and not only
ROLE.

+ else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
{
static const char *const list_ALTERDOMAIN[] =
{"CONSTRAINT", "TO", NULL};
I think you should remove COMPLETE_WITH_LIST here for consistency with the
rest.

-       else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-                        pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+       else if (TailMatches3("RENAME", "CONSTRAINT", MatchAny))
                COMPLETE_WITH_CONST("TO");
Perhaps you are missing DOMAIN here?
-       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-                        pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-                        pg_strcasecmp(prev_wd, "NO") == 0)
-       {
-               static const char *const list_ALTERSEQUENCE2[] =
-               {"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-               COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
-       }
+       else if (TailMatches4("ALTER", "SEQUEMCE", MatchAny, "NO"))
+               CompleteWithList3("MINVALUE", "MAXVALUE", "CYCLE");
Typo here: s/SEQUEMCE/SEQUENCE.
-       else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-                        (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-                         pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-                        pg_strcasecmp(prev_wd, "TO") != 0)
+       else if (TailMatches6("ALTER", "TABLE", MatchAny, "RENAME",
"COLUMN|CONSTRAINT", MatchAny) &&
+                        !TailMatches1("TO"))
This should use TailMatches5 without ALTER for consistency with the
existing code?
-       else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-                        pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
+       else if (TailMatches1("CLUSTER") && !TailMatches2("WITHOUT",
"CLUSTER"))
Here removing CLUSTER should be fine.
-       else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-                        pg_strcasecmp(prev_wd, "ON") != 0 &&
-                        pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-       {
+       else if (TailMatches2("CLUSTER", MatchAny) &&
!TailMatches1("VERBOSE"))
Handling of ON has been forgotten.
-       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-                        !(pg_strcasecmp(prev2_wd, "USER") == 0 &&
pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-                        (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-                         pg_strcasecmp(prev2_wd, "GROUP") == 0 ||
pg_strcasecmp(prev2_wd, "USER") == 0))
+       else if (TailMatches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+                        !TailMatches3("CREATE", "USER", "MAPPING"))
!TailMatches2("USER", "MAPPING")?
-       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+       else if (TailMatches3("CREATE", "MATERIALIZED", "VIEW"))
Forgot a MatchAny here?
-       else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-                        !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-                          pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-                          pg_strcasecmp(prev2_wd, "AFTER") == 0))
+       else if (TailMatches1("DELETE") &&
!TailMatches2("ON|GRANT|BEFORE|AFTER", "DELETE"))
                COMPLETE_WITH_CONST("FROM");
In the second clause checking for DELETE is not necessary.
-       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-                        prev2_wd[0] == '\0')
+       else if (Matches1("EXECUTE"))
This looks not complete.
+       else if (TailMatches1("EXPLAIN"))
+               CompleteWithList7("SELECT", "INSERT", "DELETE", "UPDATE",
"DECLARE",
+                                                       "ANALYZE",
"VERBOSE");
+       else if (TailMatches2("EXPLAIN", "ANALYZE|ANALYSE"))
ANALYSE should be removed, former code only checked for ANALYZE => I heard
about the grammar issues here :)
-               else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+               else if (TailMatches4("GRANT", MatchAny, MatchAny,
MatchAny))
+                       COMPLETE_WITH_CONST("TO");
+               else
+                       COMPLETE_WITH_CONST("FROM");
+       }
+
+       /* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
+       else if (TailMatches5("GRANT|REVOKE", MatchAny, "ON", MatchAny,
MatchAny))
+       {
+               if (TailMatches5("GRANT", MatchAny, MatchAny, MatchAny,
MatchAny))
Isn't the first check with TailMatches4 enough here?
-               if (pg_strcasecmp(prev6_wd, "GRANT") == 0)
+               if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny,
MatchAny, MatchAny))
HeadMatches1 perhaps?
-- 
Michael
#43Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#41)
Re: Making tab-complete.c easier to maintain

Thank you for looking on this and the comment.

At Mon, 7 Dec 2015 15:00:32 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqQ69EPZkOqHAVuZXPxNzb_c2R5hs88tedLYqU9=Zu6xOw@mail.gmail.com>

On Thu, Nov 26, 2015 at 2:45 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

What do you think about this solution?

<please do not top-post, it breaks the logic of the thread>

I believe I haven't ripped off any in CC: list or In-Reply-To and
References in the previous post. (I read "top-post" as a post
without these headers.) Could you let me know how the message
was broken?

This patch fails to compile on OSX:
Undefined symbols for architecture x86_64:
"_ExceptionalCondition", referenced from:
_pg_regexec in regexec.o
_cfind in regexec.o
_find in regexec.o
_newdfa in regexec.o
_cfindloop in regexec.o
_shortest in regexec.o
_cdissect in regexec.o
...
So, to begin with, this may be better if replugged as a standalone
library, aka moving the regexp code into src/common for example or
similar.

I agree to that. I'll consider doing so. (But my middle finger
tip injury makes me further slower than usual..)

Also, per the comments on top of rcancelrequested,
rstacktoodeep and rcancelrequested, returning unconditionally 0 is not
a good idea for -DFRONTEND. Callbacks should be defined and made
available for callers.

cancel_pressed is usable for the purpose and I'll add
cancel_callback feeature to separate it from both frontend and
backend.

-       {"EVENT TRIGGER", NULL, NULL},
+       {"EVENT TRIGGER", Query_for_list_of_event_triggers, NULL},
{"EXTENSION", Query_for_list_of_extensions},
-       {"FOREIGN DATA WRAPPER", NULL, NULL},
+       {"FOREIGN DATA WRAPPER", Query_for_list_of_fdws, NULL},
{"FOREIGN TABLE", NULL, NULL},
{"FUNCTION", NULL, &Query_for_list_of_functions},
{"GROUP", Query_for_list_of_roles},
{"LANGUAGE", Query_for_list_of_languages},
{"INDEX", NULL, &Query_for_list_of_indexes},
-       {"MATERIALIZED VIEW", NULL, NULL},
+       {"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
This has value as a separate patch.

I carelessly merged it in the fourth (Merge mergable...)
patch. I'll separate it.

The patch has many whitespaces, and unrelated diffs.

Mmm, thanks for pointing it out. I haven't see such lines differ
only in whitespaces or found unrelated diffs so far but I'll
check it out.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#43)
Re: Making tab-complete.c easier to maintain

On Tue, Dec 8, 2015 at 6:31 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Thank you for looking on this and the comment.

At Mon, 7 Dec 2015 15:00:32 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqQ69EPZkOqHAVuZXPxNzb_c2R5hs88tedLYqU9=Zu6xOw@mail.gmail.com>

On Thu, Nov 26, 2015 at 2:45 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

What do you think about this solution?

<please do not top-post, it breaks the logic of the thread>

I believe I haven't ripped off any in CC: list or In-Reply-To and
References in the previous post. (I read "top-post" as a post
without these headers.) Could you let me know how the message
was broken?

20151126.144512.10228250.horiguchi.kyotaro@lab.ntt.co.jp just did that
when you sent this new patch series:
A: Because it reverses the logical flow of conversation.
Q: Why is top posting frowned upon?
https://www.freebsd.org/doc/en/articles/mailing-list-faq/etiquette.html#idp55416272
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#44)
Re: Making tab-complete.c easier to maintain

Hello,

At Tue, 8 Dec 2015 20:50:39 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqR7UkYAhiFu=-W6M30Ku-9zSFPQerT1LDLwktyAXzLBQA@mail.gmail.com>

On Tue, Dec 8, 2015 at 6:31 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

<please do not top-post, it breaks the logic of the thread>

I believe I haven't ripped off any in CC: list or In-Reply-To and
References in the previous post. (I read "top-post" as a post
without these headers.) Could you let me know how the message
was broken?

20151126.144512.10228250.horiguchi.kyotaro@lab.ntt.co.jp just did that
when you sent this new patch series:
A: Because it reverses the logical flow of conversation.
Q: Why is top posting frowned upon?
https://www.freebsd.org/doc/en/articles/mailing-list-faq/etiquette.html#idp55416272

Thank you for the reference. I made it because I didn't regard it
as a reply to (my) previous message but an update. I'll take care
to avoid top-posting in any case, though.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Kyotaro HORIGUCHI (#43)
4 attachment(s)
Re: Making tab-complete.c easier to maintain

Hello, please find the attached revised patches.

At Tue, 08 Dec 2015 18:31:10 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> wrote in <20151208.183110.229901672.horiguchi.kyotaro@lab.ntt.co.jp>

This patch fails to compile on OSX:
Undefined symbols for architecture x86_64:
"_ExceptionalCondition", referenced from:
_pg_regexec in regexec.o
...

I have fixed it.

So, to begin with, this may be better if replugged as a standalone
library, aka moving the regexp code into src/common for example or
similar.

I agree to that. I'll consider doing so. (But my middle finger
tip injury makes me further slower than usual..)

Done. They are moved into common/regex.

.../backend/utils/mb/wstrncase.o still remains in psql/Makefile
but moving it would makes it more odd so it is left as it is.

Also, per the comments on top of rcancelrequested,
rstacktoodeep and rcancelrequested, returning unconditionally 0 is not
a good idea for -DFRONTEND. Callbacks should be defined and made
available for callers.

Done. pg_regcomp now has additinal parameter, which can be NULL
on backend.

cancel_pressed is usable for the purpose and I'll add
cancel_callback feeature to separate it from both frontend and
backend.

- {"EVENT TRIGGER", NULL, NULL},

...

-       {"MATERIALIZED VIEW", NULL, NULL},
+       {"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
This has value as a separate patch.

I carelessly merged it in the fourth (Merge mergable...)
patch. I'll separate it.

This is not added in this patchset. I'll post it later.

The patch has many whitespaces, and unrelated diffs.

Mmm, thanks for pointing it out. I haven't see such lines differ
only in whitespaces or found unrelated diffs so far but I'll
check it out.

I think they are whitespaces in existing file and they Some lines in
header comments have trailing space and I can remove them not
only for files I moved but for all files. But it should be as
another patch. Is it worth doing?

I found a bug in matching for "DELETE" so fixed it.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

Attachments:

0001-Allow-regex-module-to-be-used-outside-server.patchtext/x-patch; charset=us-asciiDownload
>From 256adb40dca85c5c064a915e125e89a784e4d7c9 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 9 Dec 2015 12:12:42 +0900
Subject: [PATCH 1/4] Allow regex module to be used outside server.

Moved backend/regex to common/regex and make it usable outside
backend.
---
 contrib/pg_trgm/trgm_regexp.c      |    3 +-
 src/backend/Makefile               |    2 +-
 src/backend/libpq/hba.c            |    3 +-
 src/backend/regex/COPYRIGHT        |   84 -
 src/backend/regex/Makefile         |   23 -
 src/backend/regex/README           |  371 -----
 src/backend/regex/re_syntax.n      |  979 -----------
 src/backend/regex/regc_color.c     |  790 ---------
 src/backend/regex/regc_cvec.c      |  136 --
 src/backend/regex/regc_lex.c       | 1171 -------------
 src/backend/regex/regc_locale.c    |  700 --------
 src/backend/regex/regc_nfa.c       | 3181 ------------------------------------
 src/backend/regex/regc_pg_locale.c |  878 ----------
 src/backend/regex/regcomp.c        | 2221 -------------------------
 src/backend/regex/rege_dfa.c       |  929 -----------
 src/backend/regex/regerror.c       |  120 --
 src/backend/regex/regexec.c        | 1425 ----------------
 src/backend/regex/regexport.c      |  292 ----
 src/backend/regex/regfree.c        |   54 -
 src/backend/regex/regprefix.c      |  257 ---
 src/backend/tsearch/spell.c        |    3 +-
 src/backend/utils/adt/regexp.c     |    3 +-
 src/common/Makefile                |   18 +-
 src/common/regex/COPYRIGHT         |   84 +
 src/common/regex/Makefile          |   44 +
 src/common/regex/README            |  371 +++++
 src/common/regex/re_syntax.n       |  979 +++++++++++
 src/common/regex/regc_color.c      |  790 +++++++++
 src/common/regex/regc_cvec.c       |  136 ++
 src/common/regex/regc_lex.c        | 1171 +++++++++++++
 src/common/regex/regc_locale.c     |  700 ++++++++
 src/common/regex/regc_nfa.c        | 3181 ++++++++++++++++++++++++++++++++++++
 src/common/regex/regc_pg_locale.c  |  885 ++++++++++
 src/common/regex/regcomp.c         | 2234 +++++++++++++++++++++++++
 src/common/regex/rege_dfa.c        |  929 +++++++++++
 src/common/regex/regerror.c        |  120 ++
 src/common/regex/regexec.c         | 1425 ++++++++++++++++
 src/common/regex/regexport.c       |  292 ++++
 src/common/regex/regfree.c         |   54 +
 src/common/regex/regprefix.c       |  257 +++
 src/include/regex/regcustom.h      |   10 +-
 src/include/regex/regex.h          |   15 +-
 src/include/regex/regguts.h        |   15 +-
 src/include/utils/pg_locale.h      |    6 +-
 44 files changed, 13705 insertions(+), 13636 deletions(-)
 delete mode 100644 src/backend/regex/COPYRIGHT
 delete mode 100644 src/backend/regex/Makefile
 delete mode 100644 src/backend/regex/README
 delete mode 100644 src/backend/regex/re_syntax.n
 delete mode 100644 src/backend/regex/regc_color.c
 delete mode 100644 src/backend/regex/regc_cvec.c
 delete mode 100644 src/backend/regex/regc_lex.c
 delete mode 100644 src/backend/regex/regc_locale.c
 delete mode 100644 src/backend/regex/regc_nfa.c
 delete mode 100644 src/backend/regex/regc_pg_locale.c
 delete mode 100644 src/backend/regex/regcomp.c
 delete mode 100644 src/backend/regex/rege_dfa.c
 delete mode 100644 src/backend/regex/regerror.c
 delete mode 100644 src/backend/regex/regexec.c
 delete mode 100644 src/backend/regex/regexport.c
 delete mode 100644 src/backend/regex/regfree.c
 delete mode 100644 src/backend/regex/regprefix.c
 create mode 100644 src/common/regex/COPYRIGHT
 create mode 100644 src/common/regex/Makefile
 create mode 100644 src/common/regex/README
 create mode 100644 src/common/regex/re_syntax.n
 create mode 100644 src/common/regex/regc_color.c
 create mode 100644 src/common/regex/regc_cvec.c
 create mode 100644 src/common/regex/regc_lex.c
 create mode 100644 src/common/regex/regc_locale.c
 create mode 100644 src/common/regex/regc_nfa.c
 create mode 100644 src/common/regex/regc_pg_locale.c
 create mode 100644 src/common/regex/regcomp.c
 create mode 100644 src/common/regex/rege_dfa.c
 create mode 100644 src/common/regex/regerror.c
 create mode 100644 src/common/regex/regexec.c
 create mode 100644 src/common/regex/regexport.c
 create mode 100644 src/common/regex/regfree.c
 create mode 100644 src/common/regex/regprefix.c

diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c
index a91e618..6031808 100644
--- a/contrib/pg_trgm/trgm_regexp.c
+++ b/contrib/pg_trgm/trgm_regexp.c
@@ -746,7 +746,8 @@ RE_compile(regex_t *regex, text *text_re, int cflags, Oid collation)
 								pattern,
 								pattern_len,
 								cflags,
-								collation);
+								collation,
+								NULL);
 
 	pfree(pattern);
 
diff --git a/src/backend/Makefile b/src/backend/Makefile
index fb60420..91b68a0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -18,7 +18,7 @@ top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
-	main nodes optimizer port postmaster regex replication rewrite \
+	main nodes optimizer port postmaster replication rewrite \
 	storage tcop tsearch utils $(top_builddir)/src/timezone
 
 include $(srcdir)/common.mk
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 94f7cfa..76af879 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1900,7 +1900,8 @@ parse_ident_line(List *line, int line_number)
 		wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,
 									wstr, strlen(parsedline->ident_user + 1));
 
-		r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+		r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED,
+					   C_COLLATION_OID, NULL);
 		if (r)
 		{
 			char		errstr[100];
diff --git a/src/backend/regex/COPYRIGHT b/src/backend/regex/COPYRIGHT
deleted file mode 100644
index e50cfb1..0000000
--- a/src/backend/regex/COPYRIGHT
+++ /dev/null
@@ -1,84 +0,0 @@
-This regular expression package was originally developed by Henry Spencer.
-It bears the following copyright notice:
-
-**********************************************************************
-
-Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
-
-Development of this software was funded, in part, by Cray Research Inc.,
-UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
-Corporation, none of whom are responsible for the results.  The author
-thanks all of them. 
-
-Redistribution and use in source and binary forms -- with or without
-modification -- are permitted for any purpose, provided that
-redistributions in source form retain this entire copyright notice and
-indicate the origin and nature of any modifications.
-
-I'd appreciate being given credit for this package in the documentation
-of software which uses it, but that is not a requirement.
-
-THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
-HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-**********************************************************************
-
-PostgreSQL adopted the code out of Tcl 8.4.1.  Portions of regc_locale.c
-and re_syntax.n were developed by Tcl developers other than Henry; these
-files bear the Tcl copyright and license notice:
-
-**********************************************************************
-
-This software is copyrighted by the Regents of the University of
-California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
-Corporation and other parties.  The following terms apply to all files
-associated with the software unless explicitly disclaimed in
-individual files.
-
-The authors hereby grant permission to use, copy, modify, distribute,
-and license this software and its documentation for any purpose, provided
-that existing copyright notices are retained in all copies and that this
-notice is included verbatim in any distributions. No written agreement,
-license, or royalty fee is required for any of the authorized uses.
-Modifications to this software may be copyrighted by their authors
-and need not follow the licensing terms described here, provided that
-the new terms are clearly indicated on the first page of each file where
-they apply.
-
-IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
-FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
-ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
-DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-
-THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
-IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
-NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
-MODIFICATIONS.
-
-GOVERNMENT USE: If you are acquiring this software on behalf of the
-U.S. government, the Government shall have only "Restricted Rights"
-in the software and related documentation as defined in the Federal 
-Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
-are acquiring the software on behalf of the Department of Defense, the
-software shall be classified as "Commercial Computer Software" and the
-Government shall have only "Restricted Rights" as defined in Clause
-252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
-authors grant the U.S. Government and others acting in its behalf
-permission to use and distribute the software in accordance with the
-terms specified in this license. 
-
-**********************************************************************
-
-Subsequent modifications to the code by the PostgreSQL project follow
-the same license terms as the rest of PostgreSQL.
diff --git a/src/backend/regex/Makefile b/src/backend/regex/Makefile
deleted file mode 100644
index a6100ad..0000000
--- a/src/backend/regex/Makefile
+++ /dev/null
@@ -1,23 +0,0 @@
-#-------------------------------------------------------------------------
-#
-# Makefile--
-#    Makefile for backend/regex
-#
-# IDENTIFICATION
-#    src/backend/regex/Makefile
-#
-#-------------------------------------------------------------------------
-
-subdir = src/backend/regex
-top_builddir = ../../..
-include $(top_builddir)/src/Makefile.global
-
-OBJS = regcomp.o regerror.o regexec.o regfree.o regprefix.o regexport.o
-
-include $(top_srcdir)/src/backend/common.mk
-
-# mark inclusion dependencies between .c files explicitly
-regcomp.o: regcomp.c regc_lex.c regc_color.c regc_nfa.c regc_cvec.c \
-        regc_locale.c regc_pg_locale.c
-
-regexec.o: regexec.c rege_dfa.c
diff --git a/src/backend/regex/README b/src/backend/regex/README
deleted file mode 100644
index 6c9f483..0000000
--- a/src/backend/regex/README
+++ /dev/null
@@ -1,371 +0,0 @@
-Implementation notes about Henry Spencer's regex library
-========================================================
-
-If Henry ever had any internals documentation, he didn't publish it.
-So this file is an attempt to reverse-engineer some docs.
-
-General source-file layout
---------------------------
-
-There are six separately-compilable source files, five of which expose
-exactly one exported function apiece:
-	regcomp.c: pg_regcomp
-	regexec.c: pg_regexec
-	regerror.c: pg_regerror
-	regfree.c: pg_regfree
-	regprefix.c: pg_regprefix
-(The pg_ prefixes were added by the Postgres project to distinguish this
-library version from any similar one that might be present on a particular
-system.  They'd need to be removed or replaced in any standalone version
-of the library.)
-
-The sixth file, regexport.c, exposes multiple functions that allow extraction
-of info about a compiled regex (see regexport.h).
-
-There are additional source files regc_*.c that are #include'd in regcomp,
-and similarly additional source files rege_*.c that are #include'd in
-regexec.  This was done to avoid exposing internal symbols globally;
-all functions not meant to be part of the library API are static.
-
-(Actually the above is a lie in one respect: there is one more global
-symbol, pg_set_regex_collation in regcomp.  It is not meant to be part of
-the API, but it has to be global because both regcomp and regexec call it.
-It'd be better to get rid of that, as well as the static variables it
-sets, in favor of keeping the needed locale state in the regex structs.
-We have not done this yet for lack of a design for how to add
-application-specific state to the structs.)
-
-What's where in src/backend/regex/:
-
-regcomp.c		Top-level regex compilation code
-regc_color.c		Color map management
-regc_cvec.c		Character vector (cvec) management
-regc_lex.c		Lexer
-regc_nfa.c		NFA handling
-regc_locale.c		Application-specific locale code from Tcl project
-regc_pg_locale.c	Postgres-added application-specific locale code
-regexec.c		Top-level regex execution code
-rege_dfa.c		DFA creation and execution
-regerror.c		pg_regerror: generate text for a regex error code
-regfree.c		pg_regfree: API to free a no-longer-needed regex_t
-regexport.c		Functions for extracting info from a regex_t
-regprefix.c		Code for extracting a common prefix from a regex_t
-
-The locale-specific code is concerned primarily with case-folding and with
-expanding locale-specific character classes, such as [[:alnum:]].  It
-really needs refactoring if this is ever to become a standalone library.
-
-The header files for the library are in src/include/regex/:
-
-regcustom.h		Customizes library for particular application
-regerrs.h		Error message list
-regex.h			Exported API
-regexport.h		Exported API for regexport.c
-regguts.h		Internals declarations
-
-
-DFAs, NFAs, and all that
-------------------------
-
-This library is a hybrid DFA/NFA regex implementation.  (If you've never
-heard either of those terms, get thee to a first-year comp sci textbook.)
-It might not be clear at first glance what that really means and how it
-relates to what you'll see in the code.  Here's what really happens:
-
-* Initial parsing of a regex generates an NFA representation, with number
-of states approximately proportional to the length of the regexp.
-
-* The NFA is then optimized into a "compact NFA" representation, which is
-basically the same idea but without fields that are not going to be needed
-at runtime.  It is simplified too: the compact format only allows "plain"
-and "LACON" arc types.  The cNFA representation is what is passed from
-regcomp to regexec.
-
-* Unlike traditional NFA-based regex engines, we do not execute directly
-from the NFA representation, as that would require backtracking and so be
-very slow in some cases.  Rather, we execute a DFA, which ideally can
-process an input string in linear time (O(M) for M characters of input)
-without backtracking.  Each state of the DFA corresponds to a set of
-states of the NFA, that is all the states that the NFA might have been in
-upon reaching the current point in the input string.  Therefore, an NFA
-with N states might require as many as 2^N states in the corresponding
-DFA, which could easily require unreasonable amounts of memory.  We deal
-with this by materializing states of the DFA lazily (only when needed) and
-keeping them in a limited-size cache.  The possible need to build the same
-state of the DFA repeatedly makes this approach not truly O(M) time, but
-in the worst case as much as O(M*N).  That's still far better than the
-worst case for a backtracking NFA engine.
-
-If that were the end of it, we'd just say this is a DFA engine, with the
-use of NFAs being merely an implementation detail.  However, a DFA engine
-cannot handle some important regex features such as capturing parens and
-back-references.  If the parser finds that a regex uses these features
-(collectively called "messy cases" in the code), then we have to use
-NFA-style backtracking search after all.
-
-When using the NFA mode, the representation constructed by the parser
-consists of a tree of sub-expressions ("subre"s).  Leaf tree nodes are
-either plain regular expressions (which are executed as DFAs in the manner
-described above) or back-references (which try to match the input to some
-previous substring).  Non-leaf nodes are capture nodes (which save the
-location of the substring currently matching their child node),
-concatenation, alternation, or iteration nodes.  At execution time, the
-executor recursively scans the tree.  At concatenation, alternation, or
-iteration nodes, it considers each possible alternative way of matching the
-input string, that is each place where the string could be split for a
-concatenation or iteration, or each child node for an alternation.  It
-tries the next alternative if the match fails according to the child nodes.
-This is exactly the sort of backtracking search done by a traditional NFA
-regex engine.  If there are many tree levels it can get very slow.
-
-But all is not lost: we can still be smarter than the average pure NFA
-engine.  To do this, each subre node has an associated DFA, which
-represents what the node could possibly match insofar as a mathematically
-pure regex can describe that, which basically means "no backrefs".
-Before we perform any search of possible alternative sub-matches, we run
-the DFA to see if it thinks the proposed substring could possibly match.
-If not, we can reject the match immediately without iterating through many
-possibilities.
-
-As an example, consider the regex "(a[bc]+)\1".  The compiled
-representation will have a top-level concatenation subre node.  Its left
-child is a capture node, and the child of that is a plain DFA node for
-"a[bc]+".  The concatenation's right child is a backref node for \1.
-The DFA associated with the concatenation node will be "a[bc]+a[bc]+",
-where the backref has been replaced by a copy of the DFA for its referent
-expression.  When executed, the concatenation node will have to search for
-a possible division of the input string that allows its two child nodes to
-each match their part of the string (and although this specific case can
-only succeed when the division is at the middle, the code does not know
-that, nor would it be true in general).  However, we can first run the DFA
-and quickly reject any input that doesn't start with an "a" and contain
-one more "a" plus some number of b's and c's.  If the DFA doesn't match,
-there is no need to recurse to the two child nodes for each possible
-string division point.  In many cases, this prefiltering makes the search
-run much faster than a pure NFA engine could do.  It is this behavior that
-justifies using the phrase "hybrid DFA/NFA engine" to describe Spencer's
-library.
-
-
-Colors and colormapping
------------------------
-
-In many common regex patterns, there are large numbers of characters that
-can be treated alike by the execution engine.  A simple example is the
-pattern "[[:alpha:]][[:alnum:]]*" for an identifier.  Basically the engine
-only needs to care whether an input symbol is a letter, a digit, or other.
-We could build the NFA or DFA with a separate arc for each possible letter
-and digit, but that's very wasteful of space and not so cheap to execute
-either, especially when dealing with Unicode which can have thousands of
-letters.  Instead, the parser builds a "color map" that maps each possible
-input symbol to a "color", or equivalence class.  The NFA or DFA
-representation then has arcs labeled with colors, not specific input
-symbols.  At execution, the first thing the executor does with each input
-symbol is to look up its color in the color map, and then everything else
-works from the color only.
-
-To build the colormap, we start by assigning every possible input symbol
-the color WHITE, which means "other" (that is, at the end of parsing, the
-symbols that are still WHITE are those not explicitly referenced anywhere
-in the regex).  When we see a simple literal character or a bracket
-expression in the regex, we want to assign that character, or all the
-characters represented by the bracket expression, a unique new color that
-can be used to label the NFA arc corresponding to the state transition for
-matching this character or bracket expression.  The basic idea is:
-first, change the color assigned to a character to some new value;
-second, run through all the existing arcs in the partially-built NFA,
-and for each one referencing the character's old color, add a parallel
-arc referencing its new color (this keeps the reassignment from changing
-the semantics of what we already built); and third, add a new arc with
-the character's new color to the current pair of NFA states, denoting
-that seeing this character allows the state transition to be made.
-
-This is complicated a bit by not wanting to create more colors
-(equivalence classes) than absolutely necessary.  In particular, if a
-bracket expression mentions two characters that had the same color before,
-they should still share the same color after we process the bracket, since
-there is still not a need to distinguish them.  But we do need to
-distinguish them from other characters that previously had the same color
-yet are not listed in the bracket expression.  To mechanize this, the code
-has a concept of "parent colors" and "subcolors", where a color's subcolor
-is the new color that we are giving to any characters of that color while
-parsing the current atom.  (The word "parent" is a bit unfortunate here,
-because it suggests a long-lived relationship, but a subcolor link really
-only lasts for the duration of parsing a single atom.)  In other words,
-a subcolor link means that we are in process of splitting the parent color
-into two colors (equivalence classes), depending on whether or not each
-member character should be included by the current regex atom.
-
-As an example, suppose we have the regex "a\d\wx".  Initially all possible
-character codes are labeled WHITE (color 0).  To parse the atom "a", we
-create a new color (1), update "a"'s color map entry to 1, and create an
-arc labeled 1 between the first two states of the NFA.  Now we see \d,
-which is really a bracket expression containing the digits "0"-"9".
-First we process "0", which is currently WHITE, so we create a new color
-(2), update "0"'s color map entry to 2, and create an arc labeled 2
-between the second and third states of the NFA.  We also mark color WHITE
-as having the subcolor 2, which means that future relabelings of WHITE
-characters should also select 2 as the new color.  Thus, when we process
-"1", we won't create a new color but re-use 2.  We update "1"'s color map
-entry to 2, and then find that we don't need a new arc because there is
-already one labeled 2 between the second and third states of the NFA.
-Similarly for the other 8 digits, so there will be only one arc labeled 2
-between NFA states 2 and 3 for all members of this bracket expression.
-At completion of processing of the bracket expression, we call okcolors()
-which breaks all the existing parent/subcolor links; there is no longer a
-marker saying that WHITE characters should be relabeled 2.  (Note:
-actually, we did the same creation and clearing of a subcolor link for the
-primitive atom "a", but it didn't do anything very interesting.)  Now we
-come to the "\w" bracket expression, which for simplicity assume expands
-to just "[a-z0-9]".  We process "a", but observe that it is already the
-sole member of its color 1.  This means there is no need to subdivide that
-equivalence class more finely, so we do not create any new color.  We just
-make an arc labeled 1 between the third and fourth NFA states.  Next we
-process "b", which is WHITE and far from the only WHITE character, so we
-create a new color (3), link that as WHITE's subcolor, relabel "b" as
-color 3, and make an arc labeled 3.  As we process "c" through "z", each
-is relabeled from WHITE to 3, but no new arc is needed.  Now we come to
-"0", which is not the only member of its color 2, so we suppose that a new
-color is needed and create color 4.  We link 4 as subcolor of 2, relabel
-"0" as color 4 in the map, and add an arc for color 4.  Next "1" through
-"9" are similarly relabeled as color 4, with no additional arcs needed.
-Having finished the bracket expression, we call okcolors(), which breaks
-the subcolor links.  okcolors() further observes that we have removed
-every member of color 2 (the previous color of the digit characters).
-Therefore, it runs through the partial NFA built so far and relabels arcs
-labeled 2 to color 4; in particular the arc from NFA state 2 to state 3 is
-relabeled color 4.  Then it frees up color 2, since we have no more use
-for that color.  We now have an NFA in which transitions for digits are
-consistently labeled with color 4.  Last, we come to the atom "x".
-"x" is currently labeled with color 3, and it's not the only member of
-that color, so we realize that we now need to distinguish "x" from other
-letters when we did not before.  We create a new color, which might have
-been 5 but instead we recycle the unused color 2.  "x" is relabeled 2 in
-the color map and 2 is linked as the subcolor of 3, and we add an arc for
-2 between states 4 and 5 of the NFA.  Now we call okcolors(), which breaks
-the subcolor link between colors 3 and 2 and notices that both colors are
-nonempty.  Therefore, it also runs through the existing NFA arcs and adds
-an additional arc labeled 2 wherever there is an arc labeled 3; this
-action ensures that characters of color 2 (i.e., "x") will still be
-considered as allowing any transitions they did before.  We are now done
-parsing the regex, and we have these final color assignments:
-	color 1: "a"
-	color 2: "x"
-	color 3: other letters
-	color 4: digits
-and the NFA has these arcs:
-	states 1 -> 2 on color 1 (hence, "a" only)
-	states 2 -> 3 on color 4 (digits)
-	states 3 -> 4 on colors 1, 3, 4, and 2 (covering all \w characters)
-	states 4 -> 5 on color 2 ("x" only)
-which can be seen to be a correct representation of the regex.
-
-Given this summary, we can see we need the following operations for
-colors:
-
-* A fast way to look up the current color assignment for any character
-  code.  (This is needed during both parsing and execution, while the
-  remaining operations are needed only during parsing.)
-* A way to alter the color assignment for any given character code.
-* We must track the number of characters currently assigned to each
-  color, so that we can detect empty and singleton colors.
-* We must track all existing NFA arcs of a given color, so that we
-  can relabel them at need, or add parallel arcs of a new color when
-  an existing color has to be subdivided.
-
-The last two of these are handled with the "struct colordesc" array and
-the "colorchain" links in NFA arc structs.  The color map proper (that
-is, the per-character lookup array) is handled as a multi-level tree,
-with each tree level indexed by one byte of a character's value.  The
-code arranges to not have more than one copy of bottom-level tree pages
-that are all-the-same-color.
-
-Unfortunately, this design does not seem terribly efficient for common
-cases such as a tree in which all Unicode letters are colored the same,
-because there aren't that many places where we get a whole page all the
-same color, except at the end of the map.  (It also strikes me that given
-PG's current restrictions on the range of Unicode values, we could use a
-3-level rather than 4-level tree; but there's not provision for that in
-regguts.h at the moment.)
-
-A bigger problem is that it just doesn't seem very reasonable to have to
-consider each Unicode letter separately at regex parse time for a regex
-such as "\w"; more than likely, a huge percentage of those codes will
-never be seen at runtime.  We need to fix things so that locale-based
-character classes are somehow processed "symbolically" without making a
-full expansion of their contents at parse time.  This would mean that we'd
-have to be ready to call iswalpha() at runtime, but if that only happens
-for high-code-value characters, it shouldn't be a big performance hit.
-
-
-Detailed semantics of an NFA
-----------------------------
-
-When trying to read dumped-out NFAs, it's helpful to know these facts:
-
-State 0 (additionally marked with "@" in dumpnfa's output) is always the
-goal state, and state 1 (additionally marked with ">") is the start state.
-(The code refers to these as the post state and pre state respectively.)
-
-The possible arc types are:
-
-    PLAIN arcs, which specify matching of any character of a given "color"
-    (see above).  These are dumped as "[color_number]->to_state".
-
-    EMPTY arcs, which specify a no-op transition to another state.  These
-    are dumped as "->to_state".
-
-    AHEAD constraints, which represent a "next character must be of this
-    color" constraint.  AHEAD differs from a PLAIN arc in that the input
-    character is not consumed when crossing the arc.  These are dumped as
-    ">color_number>->to_state".
-
-    BEHIND constraints, which represent a "previous character must be of
-    this color" constraint, which likewise consumes no input.  These are
-    dumped as "<color_number<->to_state".
-
-    '^' arcs, which specify a beginning-of-input constraint.  These are
-    dumped as "^0->to_state" or "^1->to_state" for beginning-of-string and
-    beginning-of-line constraints respectively.
-
-    '$' arcs, which specify an end-of-input constraint.  These are dumped
-    as "$0->to_state" or "$1->to_state" for end-of-string and end-of-line
-    constraints respectively.
-
-    LACON constraints, which represent "(?=re)", "(?!re)", "(?<=re)", and
-    "(?<!re)" constraints, i.e. the input starting/ending at this point must
-    match (or not match) a given sub-RE, but the matching input is not
-    consumed.  These are dumped as ":subtree_number:->to_state".
-
-If you see anything else (especially any question marks) in the display of
-an arc, it's dumpnfa() trying to tell you that there's something fishy
-about the arc; see the source code.
-
-The regex executor can only handle PLAIN and LACON transitions.  The regex
-optimize() function is responsible for transforming the parser's output
-to get rid of all the other arc types.  In particular, ^ and $ arcs that
-are not dropped as impossible will always end up adjacent to the pre or
-post state respectively, and then will be converted into PLAIN arcs that
-mention the special "colors" for BOS, BOL, EOS, or EOL.
-
-To decide whether a thus-transformed NFA matches a given substring of the
-input string, the executor essentially follows these rules:
-1. Start the NFA "looking at" the character *before* the given substring,
-or if the substring is at the start of the input, prepend an imaginary BOS
-character instead.
-2. Run the NFA until it has consumed the character *after* the given
-substring, or an imaginary following EOS character if the substring is at
-the end of the input.
-3. If the NFA is (or can be) in the goal state at this point, it matches.
-
-So one can mentally execute an untransformed NFA by taking ^ and $ as
-ordinary constraints that match at start and end of input; but plain
-arcs out of the start state should be taken as matches for the character
-before the target substring, and similarly, plain arcs leading to the
-post state are matches for the character after the target substring.
-This definition is necessary to support regexes that begin or end with
-constraints such as \m and \M, which imply requirements on the adjacent
-character if any.  NFAs for simple unanchored patterns will usually have
-pre-state outarcs for all possible character colors as well as BOS and
-BOL, and post-state inarcs for all possible character colors as well as
-EOS and EOL, so that the executor's behavior will work.
diff --git a/src/backend/regex/re_syntax.n b/src/backend/regex/re_syntax.n
deleted file mode 100644
index 4621bfc..0000000
--- a/src/backend/regex/re_syntax.n
+++ /dev/null
@@ -1,979 +0,0 @@
-'\"
-'\" Copyright (c) 1998 Sun Microsystems, Inc.
-'\" Copyright (c) 1999 Scriptics Corporation
-'\"
-'\" This software is copyrighted by the Regents of the University of
-'\" California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
-'\" Corporation and other parties.  The following terms apply to all files
-'\" associated with the software unless explicitly disclaimed in
-'\" individual files.
-'\" 
-'\" The authors hereby grant permission to use, copy, modify, distribute,
-'\" and license this software and its documentation for any purpose, provided
-'\" that existing copyright notices are retained in all copies and that this
-'\" notice is included verbatim in any distributions. No written agreement,
-'\" license, or royalty fee is required for any of the authorized uses.
-'\" Modifications to this software may be copyrighted by their authors
-'\" and need not follow the licensing terms described here, provided that
-'\" the new terms are clearly indicated on the first page of each file where
-'\" they apply.
-'\" 
-'\" IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
-'\" FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
-'\" ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
-'\" DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
-'\" POSSIBILITY OF SUCH DAMAGE.
-'\" 
-'\" THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
-'\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
-'\" FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
-'\" IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
-'\" NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
-'\" MODIFICATIONS.
-'\" 
-'\" GOVERNMENT USE: If you are acquiring this software on behalf of the
-'\" U.S. government, the Government shall have only "Restricted Rights"
-'\" in the software and related documentation as defined in the Federal 
-'\" Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
-'\" are acquiring the software on behalf of the Department of Defense, the
-'\" software shall be classified as "Commercial Computer Software" and the
-'\" Government shall have only "Restricted Rights" as defined in Clause
-'\" 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
-'\" authors grant the U.S. Government and others acting in its behalf
-'\" permission to use and distribute the software in accordance with the
-'\" terms specified in this license. 
-'\" 
-'\" RCS: @(#) Id: re_syntax.n,v 1.3 1999/07/14 19:09:36 jpeek Exp 
-'\"
-.so man.macros
-.TH re_syntax n "8.1" Tcl "Tcl Built-In Commands"
-.BS
-.SH NAME
-re_syntax \- Syntax of Tcl regular expressions.
-.BE
-
-.SH DESCRIPTION
-.PP
-A \fIregular expression\fR describes strings of characters.
-It's a pattern that matches certain strings and doesn't match others.
-
-.SH "DIFFERENT FLAVORS OF REs"
-Regular expressions (``RE''s), as defined by POSIX, come in two
-flavors: \fIextended\fR REs (``EREs'') and \fIbasic\fR REs (``BREs'').
-EREs are roughly those of the traditional \fIegrep\fR, while BREs are
-roughly those of the traditional \fIed\fR.  This implementation adds
-a third flavor, \fIadvanced\fR REs (``AREs''), basically EREs with
-some significant extensions.
-.PP
-This manual page primarily describes AREs.  BREs mostly exist for
-backward compatibility in some old programs; they will be discussed at
-the end.  POSIX EREs are almost an exact subset of AREs.  Features of
-AREs that are not present in EREs will be indicated.
-
-.SH "REGULAR EXPRESSION SYNTAX"
-.PP
-Tcl regular expressions are implemented using the package written by
-Henry Spencer, based on the 1003.2 spec and some (not quite all) of
-the Perl5 extensions (thanks, Henry!).  Much of the description of
-regular expressions below is copied verbatim from his manual entry.
-.PP
-An ARE is one or more \fIbranches\fR,
-separated by `\fB|\fR',
-matching anything that matches any of the branches.
-.PP
-A branch is zero or more \fIconstraints\fR or \fIquantified atoms\fR,
-concatenated.
-It matches a match for the first, followed by a match for the second, etc;
-an empty branch matches the empty string.
-.PP
-A quantified atom is an \fIatom\fR possibly followed
-by a single \fIquantifier\fR.
-Without a quantifier, it matches a match for the atom.
-The quantifiers,
-and what a so-quantified atom matches, are:
-.RS 2
-.TP 6
-\fB*\fR
-a sequence of 0 or more matches of the atom
-.TP
-\fB+\fR
-a sequence of 1 or more matches of the atom
-.TP
-\fB?\fR
-a sequence of 0 or 1 matches of the atom
-.TP
-\fB{\fIm\fB}\fR
-a sequence of exactly \fIm\fR matches of the atom
-.TP
-\fB{\fIm\fB,}\fR
-a sequence of \fIm\fR or more matches of the atom
-.TP
-\fB{\fIm\fB,\fIn\fB}\fR
-a sequence of \fIm\fR through \fIn\fR (inclusive) matches of the atom;
-\fIm\fR may not exceed \fIn\fR
-.TP
-\fB*?  +?  ??  {\fIm\fB}?  {\fIm\fB,}?  {\fIm\fB,\fIn\fB}?\fR
-\fInon-greedy\fR quantifiers,
-which match the same possibilities,
-but prefer the smallest number rather than the largest number
-of matches (see MATCHING)
-.RE
-.PP
-The forms using
-\fB{\fR and \fB}\fR
-are known as \fIbound\fRs.
-The numbers
-\fIm\fR and \fIn\fR are unsigned decimal integers
-with permissible values from 0 to 255 inclusive.
-.PP
-An atom is one of:
-.RS 2
-.TP 6
-\fB(\fIre\fB)\fR
-(where \fIre\fR is any regular expression)
-matches a match for
-\fIre\fR, with the match noted for possible reporting
-.TP
-\fB(?:\fIre\fB)\fR
-as previous,
-but does no reporting
-(a ``non-capturing'' set of parentheses)
-.TP
-\fB()\fR
-matches an empty string,
-noted for possible reporting
-.TP
-\fB(?:)\fR
-matches an empty string,
-without reporting
-.TP
-\fB[\fIchars\fB]\fR
-a \fIbracket expression\fR,
-matching any one of the \fIchars\fR (see BRACKET EXPRESSIONS for more detail)
-.TP
- \fB.\fR
-matches any single character
-.TP
-\fB\e\fIk\fR
-(where \fIk\fR is a non-alphanumeric character)
-matches that character taken as an ordinary character,
-e.g. \e\e matches a backslash character
-.TP
-\fB\e\fIc\fR
-where \fIc\fR is alphanumeric
-(possibly followed by other characters),
-an \fIescape\fR (AREs only),
-see ESCAPES below
-.TP
-\fB{\fR
-when followed by a character other than a digit,
-matches the left-brace character `\fB{\fR';
-when followed by a digit, it is the beginning of a
-\fIbound\fR (see above)
-.TP
-\fIx\fR
-where \fIx\fR is
-a single character with no other significance, matches that character.
-.RE
-.PP
-A \fIconstraint\fR matches an empty string when specific conditions
-are met.
-A constraint may not be followed by a quantifier.
-The simple constraints are as follows; some more constraints are
-described later, under ESCAPES.
-.RS 2
-.TP 8
-\fB^\fR
-matches at the beginning of a line
-.TP
-\fB$\fR
-matches at the end of a line
-.TP
-\fB(?=\fIre\fB)\fR
-\fIpositive lookahead\fR (AREs only), matches at any point
-where a substring matching \fIre\fR begins
-.TP
-\fB(?!\fIre\fB)\fR
-\fInegative lookahead\fR (AREs only), matches at any point
-where no substring matching \fIre\fR begins
-.TP
-\fB(?<=\fIre\fB)\fR
-\fIpositive lookbehind\fR (AREs only), matches at any point
-where a substring matching \fIre\fR ends
-.TP
-\fB(?<!\fIre\fB)\fR
-\fInegative lookbehind\fR (AREs only), matches at any point
-where no substring matching \fIre\fR ends
-.RE
-.PP
-Lookahead and lookbehind constraints may not contain back references
-(see later), and all parentheses within them are considered non-capturing.
-.PP
-An RE may not end with `\fB\e\fR'.
-
-.SH "BRACKET EXPRESSIONS"
-A \fIbracket expression\fR is a list of characters enclosed in `\fB[\|]\fR'.
-It normally matches any single character from the list (but see below).
-If the list begins with `\fB^\fR',
-it matches any single character
-(but see below) \fInot\fR from the rest of the list.
-.PP
-If two characters in the list are separated by `\fB\-\fR',
-this is shorthand
-for the full \fIrange\fR of characters between those two (inclusive) in the
-collating sequence,
-e.g.
-\fB[0\-9]\fR
-in ASCII matches any decimal digit.
-Two ranges may not share an
-endpoint, so e.g.
-\fBa\-c\-e\fR
-is illegal.
-Ranges are very collating-sequence-dependent,
-and portable programs should avoid relying on them.
-.PP
-To include a literal
-\fB]\fR
-or
-\fB\-\fR
-in the list,
-the simplest method is to
-enclose it in
-\fB[.\fR and \fB.]\fR
-to make it a collating element (see below).
-Alternatively,
-make it the first character
-(following a possible `\fB^\fR'),
-or (AREs only) precede it with `\fB\e\fR'.
-Alternatively, for `\fB\-\fR',
-make it the last character,
-or the second endpoint of a range.
-To use a literal
-\fB\-\fR
-as the first endpoint of a range,
-make it a collating element
-or (AREs only) precede it with `\fB\e\fR'.
-With the exception of these, some combinations using
-\fB[\fR
-(see next
-paragraphs), and escapes,
-all other special characters lose their
-special significance within a bracket expression.
-.PP
-Within a bracket expression, a collating element (a character,
-a multi-character sequence that collates as if it were a single character,
-or a collating-sequence name for either)
-enclosed in
-\fB[.\fR and \fB.]\fR
-stands for the
-sequence of characters of that collating element.
-The sequence is a single element of the bracket expression's list.
-A bracket expression in a locale that has
-multi-character collating elements
-can thus match more than one character.
-.VS 8.2
-So (insidiously), a bracket expression that starts with \fB^\fR
-can match multi-character collating elements even if none of them
-appear in the bracket expression!
-(\fINote:\fR Tcl currently has no multi-character collating elements.
-This information is only for illustration.)
-.PP
-For example, assume the collating sequence includes a \fBch\fR
-multi-character collating element.
-Then the RE \fB[[.ch.]]*c\fR (zero or more \fBch\fP's followed by \fBc\fP)
-matches the first five characters of `\fBchchcc\fR'.
-Also, the RE \fB[^c]b\fR matches all of `\fBchb\fR'
-(because \fB[^c]\fR matches the multi-character \fBch\fR).
-.VE 8.2
-.PP
-Within a bracket expression, a collating element enclosed in
-\fB[=\fR
-and
-\fB=]\fR
-is an equivalence class, standing for the sequences of characters
-of all collating elements equivalent to that one, including itself.
-(If there are no other equivalent collating elements,
-the treatment is as if the enclosing delimiters were `\fB[.\fR'\&
-and `\fB.]\fR'.)
-For example, if
-\fBo\fR
-and
-\fB\o'o^'\fR
-are the members of an equivalence class,
-then `\fB[[=o=]]\fR', `\fB[[=\o'o^'=]]\fR',
-and `\fB[o\o'o^']\fR'\&
-are all synonymous.
-An equivalence class may not be an endpoint
-of a range.
-.VS 8.2
-(\fINote:\fR 
-Tcl currently implements only the Unicode locale.
-It doesn't define any equivalence classes.
-The examples above are just illustrations.)
-.VE 8.2
-.PP
-Within a bracket expression, the name of a \fIcharacter class\fR enclosed
-in
-\fB[:\fR
-and
-\fB:]\fR
-stands for the list of all characters
-(not all collating elements!)
-belonging to that
-class.
-Standard character classes are:
-.PP
-.RS
-.ne 5
-.nf
-.ta 3c
-\fBalpha\fR	A letter. 
-\fBupper\fR	An upper-case letter. 
-\fBlower\fR	A lower-case letter. 
-\fBdigit\fR	A decimal digit. 
-\fBxdigit\fR	A hexadecimal digit. 
-\fBalnum\fR	An alphanumeric (letter or digit). 
-\fBprint\fR	An alphanumeric (same as alnum).
-\fBblank\fR	A space or tab character.
-\fBspace\fR	A character producing white space in displayed text. 
-\fBpunct\fR	A punctuation character. 
-\fBgraph\fR	A character with a visible representation. 
-\fBcntrl\fR	A control character. 
-.fi
-.RE
-.PP
-A locale may provide others.
-.VS 8.2
-(Note that the current Tcl implementation has only one locale:
-the Unicode locale.)
-.VE 8.2
-A character class may not be used as an endpoint of a range.
-.PP
-There are two special cases of bracket expressions:
-the bracket expressions
-\fB[[:<:]]\fR
-and
-\fB[[:>:]]\fR
-are constraints, matching empty strings at
-the beginning and end of a word respectively.
-'\" note, discussion of escapes below references this definition of word
-A word is defined as a sequence of
-word characters
-that is neither preceded nor followed by
-word characters.
-A word character is an
-\fIalnum\fR
-character
-or an underscore
-(\fB_\fR).
-These special bracket expressions are deprecated;
-users of AREs should use constraint escapes instead (see below).
-.SH ESCAPES
-Escapes (AREs only), which begin with a
-\fB\e\fR
-followed by an alphanumeric character,
-come in several varieties:
-character entry, class shorthands, constraint escapes, and back references.
-A
-\fB\e\fR
-followed by an alphanumeric character but not constituting
-a valid escape is illegal in AREs.
-In EREs, there are no escapes:
-outside a bracket expression,
-a
-\fB\e\fR
-followed by an alphanumeric character merely stands for that
-character as an ordinary character,
-and inside a bracket expression,
-\fB\e\fR
-is an ordinary character.
-(The latter is the one actual incompatibility between EREs and AREs.)
-.PP
-Character-entry escapes (AREs only) exist to make it easier to specify
-non-printing and otherwise inconvenient characters in REs:
-.RS 2
-.TP 5
-\fB\ea\fR
-alert (bell) character, as in C
-.TP
-\fB\eb\fR
-backspace, as in C
-.TP
-\fB\eB\fR
-synonym for
-\fB\e\fR
-to help reduce backslash doubling in some
-applications where there are multiple levels of backslash processing
-.TP
-\fB\ec\fIX\fR
-(where X is any character) the character whose
-low-order 5 bits are the same as those of
-\fIX\fR,
-and whose other bits are all zero
-.TP
-\fB\ee\fR
-the character whose collating-sequence name
-is `\fBESC\fR',
-or failing that, the character with octal value 033
-.TP
-\fB\ef\fR
-formfeed, as in C
-.TP
-\fB\en\fR
-newline, as in C
-.TP
-\fB\er\fR
-carriage return, as in C
-.TP
-\fB\et\fR
-horizontal tab, as in C
-.TP
-\fB\eu\fIwxyz\fR
-(where
-\fIwxyz\fR
-is exactly four hexadecimal digits)
-the Unicode character
-\fBU+\fIwxyz\fR
-in the local byte ordering
-.TP
-\fB\eU\fIstuvwxyz\fR
-(where
-\fIstuvwxyz\fR
-is exactly eight hexadecimal digits)
-reserved for a somewhat-hypothetical Unicode extension to 32 bits
-.TP
-\fB\ev\fR
-vertical tab, as in C
-are all available.
-.TP
-\fB\ex\fIhhh\fR
-(where
-\fIhhh\fR
-is any sequence of hexadecimal digits)
-the character whose hexadecimal value is
-\fB0x\fIhhh\fR
-(a single character no matter how many hexadecimal digits are used).
-.TP
-\fB\e0\fR
-the character whose value is
-\fB0\fR
-.TP
-\fB\e\fIxy\fR
-(where
-\fIxy\fR
-is exactly two octal digits,
-and is not a
-\fIback reference\fR (see below))
-the character whose octal value is
-\fB0\fIxy\fR
-.TP
-\fB\e\fIxyz\fR
-(where
-\fIxyz\fR
-is exactly three octal digits,
-and is not a
-back reference (see below))
-the character whose octal value is
-\fB0\fIxyz\fR
-.RE
-.PP
-Hexadecimal digits are `\fB0\fR'-`\fB9\fR', `\fBa\fR'-`\fBf\fR',
-and `\fBA\fR'-`\fBF\fR'.
-Octal digits are `\fB0\fR'-`\fB7\fR'.
-.PP
-The character-entry escapes are always taken as ordinary characters.
-For example,
-\fB\e135\fR
-is
-\fB]\fR
-in ASCII,
-but
-\fB\e135\fR
-does not terminate a bracket expression.
-Beware, however, that some applications (e.g., C compilers) interpret 
-such sequences themselves before the regular-expression package
-gets to see them, which may require doubling (quadrupling, etc.) the `\fB\e\fR'.
-.PP
-Class-shorthand escapes (AREs only) provide shorthands for certain commonly-used
-character classes:
-.RS 2
-.TP 10
-\fB\ed\fR
-\fB[[:digit:]]\fR
-.TP
-\fB\es\fR
-\fB[[:space:]]\fR
-.TP
-\fB\ew\fR
-\fB[[:alnum:]_]\fR
-(note underscore)
-.TP
-\fB\eD\fR
-\fB[^[:digit:]]\fR
-.TP
-\fB\eS\fR
-\fB[^[:space:]]\fR
-.TP
-\fB\eW\fR
-\fB[^[:alnum:]_]\fR
-(note underscore)
-.RE
-.PP
-Within bracket expressions, `\fB\ed\fR', `\fB\es\fR',
-and `\fB\ew\fR'\&
-lose their outer brackets,
-and `\fB\eD\fR', `\fB\eS\fR',
-and `\fB\eW\fR'\&
-are illegal.
-.VS 8.2
-(So, for example, \fB[a-c\ed]\fR is equivalent to \fB[a-c[:digit:]]\fR.
-Also, \fB[a-c\eD]\fR, which is equivalent to \fB[a-c^[:digit:]]\fR, is illegal.)
-.VE 8.2
-.PP
-A constraint escape (AREs only) is a constraint,
-matching the empty string if specific conditions are met,
-written as an escape:
-.RS 2
-.TP 6
-\fB\eA\fR
-matches only at the beginning of the string
-(see MATCHING, below, for how this differs from `\fB^\fR')
-.TP
-\fB\em\fR
-matches only at the beginning of a word
-.TP
-\fB\eM\fR
-matches only at the end of a word
-.TP
-\fB\ey\fR
-matches only at the beginning or end of a word
-.TP
-\fB\eY\fR
-matches only at a point that is not the beginning or end of a word
-.TP
-\fB\eZ\fR
-matches only at the end of the string
-(see MATCHING, below, for how this differs from `\fB$\fR')
-.TP
-\fB\e\fIm\fR
-(where
-\fIm\fR
-is a nonzero digit) a \fIback reference\fR, see below
-.TP
-\fB\e\fImnn\fR
-(where
-\fIm\fR
-is a nonzero digit, and
-\fInn\fR
-is some more digits,
-and the decimal value
-\fImnn\fR
-is not greater than the number of closing capturing parentheses seen so far)
-a \fIback reference\fR, see below
-.RE
-.PP
-A word is defined as in the specification of
-\fB[[:<:]]\fR
-and
-\fB[[:>:]]\fR
-above.
-Constraint escapes are illegal within bracket expressions.
-.PP
-A back reference (AREs only) matches the same string matched by the parenthesized
-subexpression specified by the number,
-so that (e.g.)
-\fB([bc])\e1\fR
-matches
-\fBbb\fR
-or
-\fBcc\fR
-but not `\fBbc\fR'.
-The subexpression must entirely precede the back reference in the RE.
-Subexpressions are numbered in the order of their leading parentheses.
-Non-capturing parentheses do not define subexpressions.
-.PP
-There is an inherent historical ambiguity between octal character-entry 
-escapes and back references, which is resolved by heuristics,
-as hinted at above.
-A leading zero always indicates an octal escape.
-A single non-zero digit, not followed by another digit,
-is always taken as a back reference.
-A multi-digit sequence not starting with a zero is taken as a back 
-reference if it comes after a suitable subexpression
-(i.e. the number is in the legal range for a back reference),
-and otherwise is taken as octal.
-.SH "METASYNTAX"
-In addition to the main syntax described above, there are some special
-forms and miscellaneous syntactic facilities available.
-.PP
-Normally the flavor of RE being used is specified by
-application-dependent means.
-However, this can be overridden by a \fIdirector\fR.
-If an RE of any flavor begins with `\fB***:\fR',
-the rest of the RE is an ARE.
-If an RE of any flavor begins with `\fB***=\fR',
-the rest of the RE is taken to be a literal string,
-with all characters considered ordinary characters.
-.PP
-An ARE may begin with \fIembedded options\fR:
-a sequence
-\fB(?\fIxyz\fB)\fR
-(where
-\fIxyz\fR
-is one or more alphabetic characters)
-specifies options affecting the rest of the RE.
-These supplement, and can override,
-any options specified by the application.
-The available option letters are:
-.RS 2
-.TP 3
-\fBb\fR
-rest of RE is a BRE
-.TP 3
-\fBc\fR
-case-sensitive matching (usual default)
-.TP 3
-\fBe\fR
-rest of RE is an ERE
-.TP 3
-\fBi\fR
-case-insensitive matching (see MATCHING, below)
-.TP 3
-\fBm\fR
-historical synonym for
-\fBn\fR
-.TP 3
-\fBn\fR
-newline-sensitive matching (see MATCHING, below)
-.TP 3
-\fBp\fR
-partial newline-sensitive matching (see MATCHING, below)
-.TP 3
-\fBq\fR
-rest of RE is a literal (``quoted'') string, all ordinary characters
-.TP 3
-\fBs\fR
-non-newline-sensitive matching (usual default)
-.TP 3
-\fBt\fR
-tight syntax (usual default; see below)
-.TP 3
-\fBw\fR
-inverse partial newline-sensitive (``weird'') matching (see MATCHING, below)
-.TP 3
-\fBx\fR
-expanded syntax (see below)
-.RE
-.PP
-Embedded options take effect at the
-\fB)\fR
-terminating the sequence.
-They are available only at the start of an ARE,
-and may not be used later within it.
-.PP
-In addition to the usual (\fItight\fR) RE syntax, in which all characters are
-significant, there is an \fIexpanded\fR syntax,
-available in all flavors of RE
-with the \fB-expanded\fR switch, or in AREs with the embedded x option.
-In the expanded syntax,
-white-space characters are ignored
-and all characters between a
-\fB#\fR
-and the following newline (or the end of the RE) are ignored,
-permitting paragraphing and commenting a complex RE.
-There are three exceptions to that basic rule:
-.RS 2
-.PP
-a white-space character or `\fB#\fR' preceded by `\fB\e\fR' is retained
-.PP
-white space or `\fB#\fR' within a bracket expression is retained
-.PP
-white space and comments are illegal within multi-character symbols
-like the ARE `\fB(?:\fR' or the BRE `\fB\e(\fR'
-.RE
-.PP
-Expanded-syntax white-space characters are blank, tab, newline, and
-.VS 8.2
-any character that belongs to the \fIspace\fR character class.
-.VE 8.2
-.PP
-Finally, in an ARE,
-outside bracket expressions, the sequence `\fB(?#\fIttt\fB)\fR'
-(where
-\fIttt\fR
-is any text not containing a `\fB)\fR')
-is a comment,
-completely ignored.
-Again, this is not allowed between the characters of
-multi-character symbols like `\fB(?:\fR'.
-Such comments are more a historical artifact than a useful facility,
-and their use is deprecated;
-use the expanded syntax instead.
-.PP
-\fINone\fR of these metasyntax extensions is available if the application
-(or an initial
-\fB***=\fR
-director)
-has specified that the user's input be treated as a literal string
-rather than as an RE.
-.SH MATCHING
-In the event that an RE could match more than one substring of a given
-string,
-the RE matches the one starting earliest in the string.
-If the RE could match more than one substring starting at that point,
-its choice is determined by its \fIpreference\fR:
-either the longest substring, or the shortest.
-.PP
-Most atoms, and all constraints, have no preference.
-A parenthesized RE has the same preference (possibly none) as the RE.
-A quantified atom with quantifier
-\fB{\fIm\fB}\fR
-or
-\fB{\fIm\fB}?\fR
-has the same preference (possibly none) as the atom itself.
-A quantified atom with other normal quantifiers (including
-\fB{\fIm\fB,\fIn\fB}\fR
-with
-\fIm\fR
-equal to
-\fIn\fR)
-prefers longest match.
-A quantified atom with other non-greedy quantifiers (including
-\fB{\fIm\fB,\fIn\fB}?\fR
-with
-\fIm\fR
-equal to
-\fIn\fR)
-prefers shortest match.
-A branch has the same preference as the first quantified atom in it
-which has a preference.
-An RE consisting of two or more branches connected by the
-\fB|\fR
-operator prefers longest match.
-.PP
-Subject to the constraints imposed by the rules for matching the whole RE,
-subexpressions also match the longest or shortest possible substrings,
-based on their preferences,
-with subexpressions starting earlier in the RE taking priority over
-ones starting later.
-Note that outer subexpressions thus take priority over
-their component subexpressions.
-.PP
-Note that the quantifiers
-\fB{1,1}\fR
-and
-\fB{1,1}?\fR
-can be used to force longest and shortest preference, respectively,
-on a subexpression or a whole RE.
-.PP
-Match lengths are measured in characters, not collating elements.
-An empty string is considered longer than no match at all.
-For example,
-\fBbb*\fR
-matches the three middle characters of `\fBabbbc\fR',
-\fB(week|wee)(night|knights)\fR
-matches all ten characters of `\fBweeknights\fR',
-when
-\fB(.*).*\fR
-is matched against
-\fBabc\fR
-the parenthesized subexpression
-matches all three characters, and
-when
-\fB(a*)*\fR
-is matched against
-\fBbc\fR
-both the whole RE and the parenthesized
-subexpression match an empty string.
-.PP
-If case-independent matching is specified,
-the effect is much as if all case distinctions had vanished from the
-alphabet.
-When an alphabetic that exists in multiple cases appears as an
-ordinary character outside a bracket expression, it is effectively
-transformed into a bracket expression containing both cases,
-so that
-\fBx\fR
-becomes `\fB[xX]\fR'.
-When it appears inside a bracket expression, all case counterparts
-of it are added to the bracket expression, so that
-\fB[x]\fR
-becomes
-\fB[xX]\fR
-and
-\fB[^x]\fR
-becomes `\fB[^xX]\fR'.
-.PP
-If newline-sensitive matching is specified, \fB.\fR
-and bracket expressions using
-\fB^\fR
-will never match the newline character
-(so that matches will never cross newlines unless the RE
-explicitly arranges it)
-and
-\fB^\fR
-and
-\fB$\fR
-will match the empty string after and before a newline
-respectively, in addition to matching at beginning and end of string
-respectively.
-ARE
-\fB\eA\fR
-and
-\fB\eZ\fR
-continue to match beginning or end of string \fIonly\fR.
-.PP
-If partial newline-sensitive matching is specified,
-this affects \fB.\fR
-and bracket expressions
-as with newline-sensitive matching, but not
-\fB^\fR
-and `\fB$\fR'.
-.PP
-If inverse partial newline-sensitive matching is specified,
-this affects
-\fB^\fR
-and
-\fB$\fR
-as with
-newline-sensitive matching,
-but not \fB.\fR
-and bracket expressions.
-This isn't very useful but is provided for symmetry.
-.SH "LIMITS AND COMPATIBILITY"
-No particular limit is imposed on the length of REs.
-Programs intended to be highly portable should not employ REs longer
-than 256 bytes,
-as a POSIX-compliant implementation can refuse to accept such REs.
-.PP
-The only feature of AREs that is actually incompatible with
-POSIX EREs is that
-\fB\e\fR
-does not lose its special
-significance inside bracket expressions.
-All other ARE features use syntax which is illegal or has
-undefined or unspecified effects in POSIX EREs;
-the
-\fB***\fR
-syntax of directors likewise is outside the POSIX
-syntax for both BREs and EREs.
-.PP
-Many of the ARE extensions are borrowed from Perl, but some have
-been changed to clean them up, and a few Perl extensions are not present.
-Incompatibilities of note include `\fB\eb\fR', `\fB\eB\fR',
-the lack of special treatment for a trailing newline,
-the addition of complemented bracket expressions to the things
-affected by newline-sensitive matching,
-the restrictions on parentheses and back references in lookahead/lookbehind
-constraints,
-and the longest/shortest-match (rather than first-match) matching semantics.
-.PP
-The matching rules for REs containing both normal and non-greedy quantifiers
-have changed since early beta-test versions of this package.
-(The new rules are much simpler and cleaner,
-but don't work as hard at guessing the user's real intentions.)
-.PP
-Henry Spencer's original 1986 \fIregexp\fR package,
-still in widespread use (e.g., in pre-8.1 releases of Tcl),
-implemented an early version of today's EREs.
-There are four incompatibilities between \fIregexp\fR's near-EREs
-(`RREs' for short) and AREs.
-In roughly increasing order of significance:
-.PP
-.RS
-In AREs,
-\fB\e\fR
-followed by an alphanumeric character is either an
-escape or an error,
-while in RREs, it was just another way of writing the 
-alphanumeric.
-This should not be a problem because there was no reason to write
-such a sequence in RREs.
-.PP
-\fB{\fR
-followed by a digit in an ARE is the beginning of a bound,
-while in RREs,
-\fB{\fR
-was always an ordinary character.
-Such sequences should be rare,
-and will often result in an error because following characters
-will not look like a valid bound.
-.PP
-In AREs,
-\fB\e\fR
-remains a special character within `\fB[\|]\fR',
-so a literal
-\fB\e\fR
-within
-\fB[\|]\fR
-must be written `\fB\e\e\fR'.
-\fB\e\e\fR
-also gives a literal
-\fB\e\fR
-within
-\fB[\|]\fR
-in RREs,
-but only truly paranoid programmers routinely doubled the backslash.
-.PP
-AREs report the longest/shortest match for the RE,
-rather than the first found in a specified search order.
-This may affect some RREs which were written in the expectation that
-the first match would be reported.
-(The careful crafting of RREs to optimize the search order for fast
-matching is obsolete (AREs examine all possible matches
-in parallel, and their performance is largely insensitive to their
-complexity) but cases where the search order was exploited to deliberately 
-find a match which was \fInot\fR the longest/shortest will need rewriting.)
-.RE
-
-.SH "BASIC REGULAR EXPRESSIONS"
-BREs differ from EREs in several respects.  `\fB|\fR', `\fB+\fR',
-and
-\fB?\fR
-are ordinary characters and there is no equivalent
-for their functionality.
-The delimiters for bounds are
-\fB\e{\fR
-and `\fB\e}\fR',
-with
-\fB{\fR
-and
-\fB}\fR
-by themselves ordinary characters.
-The parentheses for nested subexpressions are
-\fB\e(\fR
-and `\fB\e)\fR',
-with
-\fB(\fR
-and
-\fB)\fR
-by themselves ordinary characters.
-\fB^\fR
-is an ordinary character except at the beginning of the
-RE or the beginning of a parenthesized subexpression,
-\fB$\fR
-is an ordinary character except at the end of the
-RE or the end of a parenthesized subexpression,
-and
-\fB*\fR
-is an ordinary character if it appears at the beginning of the
-RE or the beginning of a parenthesized subexpression
-(after a possible leading `\fB^\fR').
-Finally,
-single-digit back references are available,
-and
-\fB\e<\fR
-and
-\fB\e>\fR
-are synonyms for
-\fB[[:<:]]\fR
-and
-\fB[[:>:]]\fR
-respectively;
-no other escapes are available.
-
-.SH "SEE ALSO"
-RegExp(3), regexp(n), regsub(n), lsearch(n), switch(n), text(n)
-
-.SH KEYWORDS
-match, regular expression, string
diff --git a/src/backend/regex/regc_color.c b/src/backend/regex/regc_color.c
deleted file mode 100644
index c495cee..0000000
--- a/src/backend/regex/regc_color.c
+++ /dev/null
@@ -1,790 +0,0 @@
-/*
- * colorings of characters
- * This file is #included by regcomp.c.
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regc_color.c
- *
- *
- * Note that there are some incestuous relationships between this code and
- * NFA arc maintenance, which perhaps ought to be cleaned up sometime.
- */
-
-
-
-#define CISERR()	VISERR(cm->v)
-#define CERR(e)		VERR(cm->v, (e))
-
-
-
-/*
- * initcm - set up new colormap
- */
-static void
-initcm(struct vars * v,
-	   struct colormap * cm)
-{
-	int			i;
-	int			j;
-	union tree *t;
-	union tree *nextt;
-	struct colordesc *cd;
-
-	cm->magic = CMMAGIC;
-	cm->v = v;
-
-	cm->ncds = NINLINECDS;
-	cm->cd = cm->cdspace;
-	cm->max = 0;
-	cm->free = 0;
-
-	cd = cm->cd;				/* cm->cd[WHITE] */
-	cd->sub = NOSUB;
-	cd->arcs = NULL;
-	cd->firstchr = CHR_MIN;
-	cd->nchrs = CHR_MAX - CHR_MIN + 1;
-	cd->flags = 0;
-
-	/* upper levels of tree */
-	for (t = &cm->tree[0], j = NBYTS - 1; j > 0; t = nextt, j--)
-	{
-		nextt = t + 1;
-		for (i = BYTTAB - 1; i >= 0; i--)
-			t->tptr[i] = nextt;
-	}
-	/* bottom level is solid white */
-	t = &cm->tree[NBYTS - 1];
-	for (i = BYTTAB - 1; i >= 0; i--)
-		t->tcolor[i] = WHITE;
-	cd->block = t;
-}
-
-/*
- * freecm - free dynamically-allocated things in a colormap
- */
-static void
-freecm(struct colormap * cm)
-{
-	size_t		i;
-	union tree *cb;
-
-	cm->magic = 0;
-	if (NBYTS > 1)
-		cmtreefree(cm, cm->tree, 0);
-	for (i = 1; i <= cm->max; i++)		/* skip WHITE */
-		if (!UNUSEDCOLOR(&cm->cd[i]))
-		{
-			cb = cm->cd[i].block;
-			if (cb != NULL)
-				FREE(cb);
-		}
-	if (cm->cd != cm->cdspace)
-		FREE(cm->cd);
-}
-
-/*
- * cmtreefree - free a non-terminal part of a colormap tree
- */
-static void
-cmtreefree(struct colormap * cm,
-		   union tree * tree,
-		   int level)			/* level number (top == 0) of this block */
-{
-	int			i;
-	union tree *t;
-	union tree *fillt = &cm->tree[level + 1];
-	union tree *cb;
-
-	assert(level < NBYTS - 1);	/* this level has pointers */
-	for (i = BYTTAB - 1; i >= 0; i--)
-	{
-		t = tree->tptr[i];
-		assert(t != NULL);
-		if (t != fillt)
-		{
-			if (level < NBYTS - 2)
-			{					/* more pointer blocks below */
-				cmtreefree(cm, t, level + 1);
-				FREE(t);
-			}
-			else
-			{					/* color block below */
-				cb = cm->cd[t->tcolor[0]].block;
-				if (t != cb)	/* not a solid block */
-					FREE(t);
-			}
-		}
-	}
-}
-
-/*
- * setcolor - set the color of a character in a colormap
- */
-static color					/* previous color */
-setcolor(struct colormap * cm,
-		 chr c,
-		 pcolor co)
-{
-	uchr		uc = c;
-	int			shift;
-	int			level;
-	int			b;
-	int			bottom;
-	union tree *t;
-	union tree *newt;
-	union tree *fillt;
-	union tree *lastt;
-	union tree *cb;
-	color		prev;
-
-	assert(cm->magic == CMMAGIC);
-	if (CISERR() || co == COLORLESS)
-		return COLORLESS;
-
-	t = cm->tree;
-	for (level = 0, shift = BYTBITS * (NBYTS - 1); shift > 0;
-		 level++, shift -= BYTBITS)
-	{
-		b = (uc >> shift) & BYTMASK;
-		lastt = t;
-		t = lastt->tptr[b];
-		assert(t != NULL);
-		fillt = &cm->tree[level + 1];
-		bottom = (shift <= BYTBITS) ? 1 : 0;
-		cb = (bottom) ? cm->cd[t->tcolor[0]].block : fillt;
-		if (t == fillt || t == cb)
-		{						/* must allocate a new block */
-			newt = (union tree *) MALLOC((bottom) ?
-								sizeof(struct colors) : sizeof(struct ptrs));
-			if (newt == NULL)
-			{
-				CERR(REG_ESPACE);
-				return COLORLESS;
-			}
-			if (bottom)
-				memcpy(VS(newt->tcolor), VS(t->tcolor),
-					   BYTTAB * sizeof(color));
-			else
-				memcpy(VS(newt->tptr), VS(t->tptr),
-					   BYTTAB * sizeof(union tree *));
-			t = newt;
-			lastt->tptr[b] = t;
-		}
-	}
-
-	b = uc & BYTMASK;
-	prev = t->tcolor[b];
-	t->tcolor[b] = (color) co;
-	return prev;
-}
-
-/*
- * maxcolor - report largest color number in use
- */
-static color
-maxcolor(struct colormap * cm)
-{
-	if (CISERR())
-		return COLORLESS;
-
-	return (color) cm->max;
-}
-
-/*
- * newcolor - find a new color (must be subject of setcolor at once)
- * Beware:	may relocate the colordescs.
- */
-static color					/* COLORLESS for error */
-newcolor(struct colormap * cm)
-{
-	struct colordesc *cd;
-	size_t		n;
-
-	if (CISERR())
-		return COLORLESS;
-
-	if (cm->free != 0)
-	{
-		assert(cm->free > 0);
-		assert((size_t) cm->free < cm->ncds);
-		cd = &cm->cd[cm->free];
-		assert(UNUSEDCOLOR(cd));
-		assert(cd->arcs == NULL);
-		cm->free = cd->sub;
-	}
-	else if (cm->max < cm->ncds - 1)
-	{
-		cm->max++;
-		cd = &cm->cd[cm->max];
-	}
-	else
-	{
-		/* oops, must allocate more */
-		struct colordesc *newCd;
-
-		if (cm->max == MAX_COLOR)
-		{
-			CERR(REG_ECOLORS);
-			return COLORLESS;	/* too many colors */
-		}
-
-		n = cm->ncds * 2;
-		if (n > MAX_COLOR + 1)
-			n = MAX_COLOR + 1;
-		if (cm->cd == cm->cdspace)
-		{
-			newCd = (struct colordesc *) MALLOC(n * sizeof(struct colordesc));
-			if (newCd != NULL)
-				memcpy(VS(newCd), VS(cm->cdspace), cm->ncds *
-					   sizeof(struct colordesc));
-		}
-		else
-			newCd = (struct colordesc *)
-				REALLOC(cm->cd, n * sizeof(struct colordesc));
-		if (newCd == NULL)
-		{
-			CERR(REG_ESPACE);
-			return COLORLESS;
-		}
-		cm->cd = newCd;
-		cm->ncds = n;
-		assert(cm->max < cm->ncds - 1);
-		cm->max++;
-		cd = &cm->cd[cm->max];
-	}
-
-	cd->nchrs = 0;
-	cd->sub = NOSUB;
-	cd->arcs = NULL;
-	cd->firstchr = CHR_MIN;		/* in case never set otherwise */
-	cd->flags = 0;
-	cd->block = NULL;
-
-	return (color) (cd - cm->cd);
-}
-
-/*
- * freecolor - free a color (must have no arcs or subcolor)
- */
-static void
-freecolor(struct colormap * cm,
-		  pcolor co)
-{
-	struct colordesc *cd = &cm->cd[co];
-	color		pco,
-				nco;			/* for freelist scan */
-
-	assert(co >= 0);
-	if (co == WHITE)
-		return;
-
-	assert(cd->arcs == NULL);
-	assert(cd->sub == NOSUB);
-	assert(cd->nchrs == 0);
-	cd->flags = FREECOL;
-	if (cd->block != NULL)
-	{
-		FREE(cd->block);
-		cd->block = NULL;		/* just paranoia */
-	}
-
-	if ((size_t) co == cm->max)
-	{
-		while (cm->max > WHITE && UNUSEDCOLOR(&cm->cd[cm->max]))
-			cm->max--;
-		assert(cm->free >= 0);
-		while ((size_t) cm->free > cm->max)
-			cm->free = cm->cd[cm->free].sub;
-		if (cm->free > 0)
-		{
-			assert(cm->free < cm->max);
-			pco = cm->free;
-			nco = cm->cd[pco].sub;
-			while (nco > 0)
-				if ((size_t) nco > cm->max)
-				{
-					/* take this one out of freelist */
-					nco = cm->cd[nco].sub;
-					cm->cd[pco].sub = nco;
-				}
-				else
-				{
-					assert(nco < cm->max);
-					pco = nco;
-					nco = cm->cd[pco].sub;
-				}
-		}
-	}
-	else
-	{
-		cd->sub = cm->free;
-		cm->free = (color) (cd - cm->cd);
-	}
-}
-
-/*
- * pseudocolor - allocate a false color, to be managed by other means
- */
-static color
-pseudocolor(struct colormap * cm)
-{
-	color		co;
-
-	co = newcolor(cm);
-	if (CISERR())
-		return COLORLESS;
-	cm->cd[co].nchrs = 1;
-	cm->cd[co].flags = PSEUDO;
-	return co;
-}
-
-/*
- * subcolor - allocate a new subcolor (if necessary) to this chr
- */
-static color
-subcolor(struct colormap * cm, chr c)
-{
-	color		co;				/* current color of c */
-	color		sco;			/* new subcolor */
-
-	co = GETCOLOR(cm, c);
-	sco = newsub(cm, co);
-	if (CISERR())
-		return COLORLESS;
-	assert(sco != COLORLESS);
-
-	if (co == sco)				/* already in an open subcolor */
-		return co;				/* rest is redundant */
-	cm->cd[co].nchrs--;
-	if (cm->cd[sco].nchrs == 0)
-		cm->cd[sco].firstchr = c;
-	cm->cd[sco].nchrs++;
-	setcolor(cm, c, sco);
-	return sco;
-}
-
-/*
- * newsub - allocate a new subcolor (if necessary) for a color
- */
-static color
-newsub(struct colormap * cm,
-	   pcolor co)
-{
-	color		sco;			/* new subcolor */
-
-	sco = cm->cd[co].sub;
-	if (sco == NOSUB)
-	{							/* color has no open subcolor */
-		if (cm->cd[co].nchrs == 1)		/* optimization */
-			return co;
-		sco = newcolor(cm);		/* must create subcolor */
-		if (sco == COLORLESS)
-		{
-			assert(CISERR());
-			return COLORLESS;
-		}
-		cm->cd[co].sub = sco;
-		cm->cd[sco].sub = sco;	/* open subcolor points to self */
-	}
-	assert(sco != NOSUB);
-
-	return sco;
-}
-
-/*
- * subrange - allocate new subcolors to this range of chrs, fill in arcs
- */
-static void
-subrange(struct vars * v,
-		 chr from,
-		 chr to,
-		 struct state * lp,
-		 struct state * rp)
-{
-	uchr		uf;
-	int			i;
-
-	assert(from <= to);
-
-	/* first, align "from" on a tree-block boundary */
-	uf = (uchr) from;
-	i = (int) (((uf + BYTTAB - 1) & (uchr) ~BYTMASK) - uf);
-	for (; from <= to && i > 0; i--, from++)
-		newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp);
-	if (from > to)				/* didn't reach a boundary */
-		return;
-
-	/* deal with whole blocks */
-	for (; to - from >= BYTTAB; from += BYTTAB)
-		subblock(v, from, lp, rp);
-
-	/* clean up any remaining partial table */
-	for (; from <= to; from++)
-		newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp);
-}
-
-/*
- * subblock - allocate new subcolors for one tree block of chrs, fill in arcs
- *
- * Note: subcolors that are created during execution of this function
- * will not be given a useful value of firstchr; it'll be left as CHR_MIN.
- * For the current usage of firstchr in pg_regprefix, this does not matter
- * because such subcolors won't occur in the common prefix of a regex.
- */
-static void
-subblock(struct vars * v,
-		 chr start,				/* first of BYTTAB chrs */
-		 struct state * lp,
-		 struct state * rp)
-{
-	uchr		uc = start;
-	struct colormap *cm = v->cm;
-	int			shift;
-	int			level;
-	int			i;
-	int			b;
-	union tree *t;
-	union tree *cb;
-	union tree *fillt;
-	union tree *lastt;
-	int			previ;
-	int			ndone;
-	color		co;
-	color		sco;
-
-	assert((uc % BYTTAB) == 0);
-
-	/* find its color block, making new pointer blocks as needed */
-	t = cm->tree;
-	fillt = NULL;
-	for (level = 0, shift = BYTBITS * (NBYTS - 1); shift > 0;
-		 level++, shift -= BYTBITS)
-	{
-		b = (uc >> shift) & BYTMASK;
-		lastt = t;
-		t = lastt->tptr[b];
-		assert(t != NULL);
-		fillt = &cm->tree[level + 1];
-		if (t == fillt && shift > BYTBITS)
-		{						/* need new ptr block */
-			t = (union tree *) MALLOC(sizeof(struct ptrs));
-			if (t == NULL)
-			{
-				CERR(REG_ESPACE);
-				return;
-			}
-			memcpy(VS(t->tptr), VS(fillt->tptr),
-				   BYTTAB * sizeof(union tree *));
-			lastt->tptr[b] = t;
-		}
-	}
-
-	/* special cases:  fill block or solid block */
-	co = t->tcolor[0];
-	cb = cm->cd[co].block;
-	if (t == fillt || t == cb)
-	{
-		/* either way, we want a subcolor solid block */
-		sco = newsub(cm, co);
-		t = cm->cd[sco].block;
-		if (t == NULL)
-		{						/* must set it up */
-			t = (union tree *) MALLOC(sizeof(struct colors));
-			if (t == NULL)
-			{
-				CERR(REG_ESPACE);
-				return;
-			}
-			for (i = 0; i < BYTTAB; i++)
-				t->tcolor[i] = sco;
-			cm->cd[sco].block = t;
-		}
-		/* find loop must have run at least once */
-		lastt->tptr[b] = t;
-		newarc(v->nfa, PLAIN, sco, lp, rp);
-		cm->cd[co].nchrs -= BYTTAB;
-		cm->cd[sco].nchrs += BYTTAB;
-		return;
-	}
-
-	/* general case, a mixed block to be altered */
-	i = 0;
-	while (i < BYTTAB)
-	{
-		co = t->tcolor[i];
-		sco = newsub(cm, co);
-		newarc(v->nfa, PLAIN, sco, lp, rp);
-		previ = i;
-		do
-		{
-			t->tcolor[i++] = sco;
-		} while (i < BYTTAB && t->tcolor[i] == co);
-		ndone = i - previ;
-		cm->cd[co].nchrs -= ndone;
-		cm->cd[sco].nchrs += ndone;
-	}
-}
-
-/*
- * okcolors - promote subcolors to full colors
- */
-static void
-okcolors(struct nfa * nfa,
-		 struct colormap * cm)
-{
-	struct colordesc *cd;
-	struct colordesc *end = CDEND(cm);
-	struct colordesc *scd;
-	struct arc *a;
-	color		co;
-	color		sco;
-
-	for (cd = cm->cd, co = 0; cd < end; cd++, co++)
-	{
-		sco = cd->sub;
-		if (UNUSEDCOLOR(cd) || sco == NOSUB)
-		{
-			/* has no subcolor, no further action */
-		}
-		else if (sco == co)
-		{
-			/* is subcolor, let parent deal with it */
-		}
-		else if (cd->nchrs == 0)
-		{
-			/* parent empty, its arcs change color to subcolor */
-			cd->sub = NOSUB;
-			scd = &cm->cd[sco];
-			assert(scd->nchrs > 0);
-			assert(scd->sub == sco);
-			scd->sub = NOSUB;
-			while ((a = cd->arcs) != NULL)
-			{
-				assert(a->co == co);
-				uncolorchain(cm, a);
-				a->co = sco;
-				colorchain(cm, a);
-			}
-			freecolor(cm, co);
-		}
-		else
-		{
-			/* parent's arcs must gain parallel subcolor arcs */
-			cd->sub = NOSUB;
-			scd = &cm->cd[sco];
-			assert(scd->nchrs > 0);
-			assert(scd->sub == sco);
-			scd->sub = NOSUB;
-			for (a = cd->arcs; a != NULL; a = a->colorchain)
-			{
-				assert(a->co == co);
-				newarc(nfa, a->type, sco, a->from, a->to);
-			}
-		}
-	}
-}
-
-/*
- * colorchain - add this arc to the color chain of its color
- */
-static void
-colorchain(struct colormap * cm,
-		   struct arc * a)
-{
-	struct colordesc *cd = &cm->cd[a->co];
-
-	if (cd->arcs != NULL)
-		cd->arcs->colorchainRev = a;
-	a->colorchain = cd->arcs;
-	a->colorchainRev = NULL;
-	cd->arcs = a;
-}
-
-/*
- * uncolorchain - delete this arc from the color chain of its color
- */
-static void
-uncolorchain(struct colormap * cm,
-			 struct arc * a)
-{
-	struct colordesc *cd = &cm->cd[a->co];
-	struct arc *aa = a->colorchainRev;
-
-	if (aa == NULL)
-	{
-		assert(cd->arcs == a);
-		cd->arcs = a->colorchain;
-	}
-	else
-	{
-		assert(aa->colorchain == a);
-		aa->colorchain = a->colorchain;
-	}
-	if (a->colorchain != NULL)
-		a->colorchain->colorchainRev = aa;
-	a->colorchain = NULL;		/* paranoia */
-	a->colorchainRev = NULL;
-}
-
-/*
- * rainbow - add arcs of all full colors (but one) between specified states
- */
-static void
-rainbow(struct nfa * nfa,
-		struct colormap * cm,
-		int type,
-		pcolor but,				/* COLORLESS if no exceptions */
-		struct state * from,
-		struct state * to)
-{
-	struct colordesc *cd;
-	struct colordesc *end = CDEND(cm);
-	color		co;
-
-	for (cd = cm->cd, co = 0; cd < end && !CISERR(); cd++, co++)
-		if (!UNUSEDCOLOR(cd) && cd->sub != co && co != but &&
-			!(cd->flags & PSEUDO))
-			newarc(nfa, type, co, from, to);
-}
-
-/*
- * colorcomplement - add arcs of complementary colors
- *
- * The calling sequence ought to be reconciled with cloneouts().
- */
-static void
-colorcomplement(struct nfa * nfa,
-				struct colormap * cm,
-				int type,
-				struct state * of,		/* complements of this guy's PLAIN
-										 * outarcs */
-				struct state * from,
-				struct state * to)
-{
-	struct colordesc *cd;
-	struct colordesc *end = CDEND(cm);
-	color		co;
-
-	assert(of != from);
-	for (cd = cm->cd, co = 0; cd < end && !CISERR(); cd++, co++)
-		if (!UNUSEDCOLOR(cd) && !(cd->flags & PSEUDO))
-			if (findarc(of, PLAIN, co) == NULL)
-				newarc(nfa, type, co, from, to);
-}
-
-
-#ifdef REG_DEBUG
-
-/*
- * dumpcolors - debugging output
- */
-static void
-dumpcolors(struct colormap * cm,
-		   FILE *f)
-{
-	struct colordesc *cd;
-	struct colordesc *end;
-	color		co;
-	chr			c;
-	char	   *has;
-
-	fprintf(f, "max %ld\n", (long) cm->max);
-	if (NBYTS > 1)
-		fillcheck(cm, cm->tree, 0, f);
-	end = CDEND(cm);
-	for (cd = cm->cd + 1, co = 1; cd < end; cd++, co++) /* skip 0 */
-		if (!UNUSEDCOLOR(cd))
-		{
-			assert(cd->nchrs > 0);
-			has = (cd->block != NULL) ? "#" : "";
-			if (cd->flags & PSEUDO)
-				fprintf(f, "#%2ld%s(ps): ", (long) co, has);
-			else
-				fprintf(f, "#%2ld%s(%2d): ", (long) co,
-						has, cd->nchrs);
-
-			/*
-			 * Unfortunately, it's hard to do this next bit more efficiently.
-			 *
-			 * Spencer's original coding has the loop iterating from CHR_MIN
-			 * to CHR_MAX, but that's utterly unusable for 32-bit chr. For
-			 * debugging purposes it seems fine to print only chr codes up to
-			 * 1000 or so.
-			 */
-			for (c = CHR_MIN; c < 1000; c++)
-				if (GETCOLOR(cm, c) == co)
-					dumpchr(c, f);
-			fprintf(f, "\n");
-		}
-}
-
-/*
- * fillcheck - check proper filling of a tree
- */
-static void
-fillcheck(struct colormap * cm,
-		  union tree * tree,
-		  int level,			/* level number (top == 0) of this block */
-		  FILE *f)
-{
-	int			i;
-	union tree *t;
-	union tree *fillt = &cm->tree[level + 1];
-
-	assert(level < NBYTS - 1);	/* this level has pointers */
-	for (i = BYTTAB - 1; i >= 0; i--)
-	{
-		t = tree->tptr[i];
-		if (t == NULL)
-			fprintf(f, "NULL found in filled tree!\n");
-		else if (t == fillt)
-		{
-		}
-		else if (level < NBYTS - 2)		/* more pointer blocks below */
-			fillcheck(cm, t, level + 1, f);
-	}
-}
-
-/*
- * dumpchr - print a chr
- *
- * Kind of char-centric but works well enough for debug use.
- */
-static void
-dumpchr(chr c,
-		FILE *f)
-{
-	if (c == '\\')
-		fprintf(f, "\\\\");
-	else if (c > ' ' && c <= '~')
-		putc((char) c, f);
-	else
-		fprintf(f, "\\u%04lx", (long) c);
-}
-
-#endif   /* REG_DEBUG */
diff --git a/src/backend/regex/regc_cvec.c b/src/backend/regex/regc_cvec.c
deleted file mode 100644
index 921a7d7..0000000
--- a/src/backend/regex/regc_cvec.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Utility functions for handling cvecs
- * This file is #included by regcomp.c.
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regc_cvec.c
- *
- */
-
-/*
- * Notes:
- * Only (selected) functions in _this_ file should treat chr* as non-constant.
- */
-
-/*
- * newcvec - allocate a new cvec
- */
-static struct cvec *
-newcvec(int nchrs,				/* to hold this many chrs... */
-		int nranges)			/* ... and this many ranges */
-{
-	size_t		nc = (size_t) nchrs + (size_t) nranges * 2;
-	size_t		n = sizeof(struct cvec) + nc * sizeof(chr);
-	struct cvec *cv = (struct cvec *) MALLOC(n);
-
-	if (cv == NULL)
-		return NULL;
-	cv->chrspace = nchrs;
-	cv->chrs = (chr *) (((char *) cv) + sizeof(struct cvec));
-	cv->ranges = cv->chrs + nchrs;
-	cv->rangespace = nranges;
-	return clearcvec(cv);
-}
-
-/*
- * clearcvec - clear a possibly-new cvec
- * Returns pointer as convenience.
- */
-static struct cvec *
-clearcvec(struct cvec * cv)
-{
-	assert(cv != NULL);
-	cv->nchrs = 0;
-	cv->nranges = 0;
-	return cv;
-}
-
-/*
- * addchr - add a chr to a cvec
- */
-static void
-addchr(struct cvec * cv,		/* character vector */
-	   chr c)					/* character to add */
-{
-	assert(cv->nchrs < cv->chrspace);
-	cv->chrs[cv->nchrs++] = (chr) c;
-}
-
-/*
- * addrange - add a range to a cvec
- */
-static void
-addrange(struct cvec * cv,		/* character vector */
-		 chr from,				/* first character of range */
-		 chr to)				/* last character of range */
-{
-	assert(cv->nranges < cv->rangespace);
-	cv->ranges[cv->nranges * 2] = (chr) from;
-	cv->ranges[cv->nranges * 2 + 1] = (chr) to;
-	cv->nranges++;
-}
-
-/*
- * getcvec - get a transient cvec, initialized to empty
- *
- * The returned cvec is valid only until the next call of getcvec, which
- * typically will recycle the space.  Callers should *not* free the cvec
- * explicitly; it will be cleaned up when the struct vars is destroyed.
- *
- * This is typically used while interpreting bracket expressions.  In that
- * usage the cvec is only needed momentarily until we build arcs from it,
- * so transientness is a convenient behavior.
- */
-static struct cvec *
-getcvec(struct vars * v,		/* context */
-		int nchrs,				/* to hold this many chrs... */
-		int nranges)			/* ... and this many ranges */
-{
-	/* recycle existing transient cvec if large enough */
-	if (v->cv != NULL && nchrs <= v->cv->chrspace &&
-		nranges <= v->cv->rangespace)
-		return clearcvec(v->cv);
-
-	/* nope, make a new one */
-	if (v->cv != NULL)
-		freecvec(v->cv);
-	v->cv = newcvec(nchrs, nranges);
-	if (v->cv == NULL)
-		ERR(REG_ESPACE);
-
-	return v->cv;
-}
-
-/*
- * freecvec - free a cvec
- */
-static void
-freecvec(struct cvec * cv)
-{
-	FREE(cv);
-}
diff --git a/src/backend/regex/regc_lex.c b/src/backend/regex/regc_lex.c
deleted file mode 100644
index bfd9dcd..0000000
--- a/src/backend/regex/regc_lex.c
+++ /dev/null
@@ -1,1171 +0,0 @@
-/*
- * lexical analyzer
- * This file is #included by regcomp.c.
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regc_lex.c
- *
- */
-
-/* scanning macros (know about v) */
-#define ATEOS()		(v->now >= v->stop)
-#define HAVE(n)		(v->stop - v->now >= (n))
-#define NEXT1(c)	(!ATEOS() && *v->now == CHR(c))
-#define NEXT2(a,b)	(HAVE(2) && *v->now == CHR(a) && *(v->now+1) == CHR(b))
-#define NEXT3(a,b,c)	(HAVE(3) && *v->now == CHR(a) && \
-						*(v->now+1) == CHR(b) && \
-						*(v->now+2) == CHR(c))
-#define SET(c)		(v->nexttype = (c))
-#define SETV(c, n)	(v->nexttype = (c), v->nextvalue = (n))
-#define RET(c)		return (SET(c), 1)
-#define RETV(c, n)	return (SETV(c, n), 1)
-#define FAILW(e)	return (ERR(e), 0)	/* ERR does SET(EOS) */
-#define LASTTYPE(t) (v->lasttype == (t))
-
-/* lexical contexts */
-#define L_ERE	1				/* mainline ERE/ARE */
-#define L_BRE	2				/* mainline BRE */
-#define L_Q 3					/* REG_QUOTE */
-#define L_EBND	4				/* ERE/ARE bound */
-#define L_BBND	5				/* BRE bound */
-#define L_BRACK 6				/* brackets */
-#define L_CEL	7				/* collating element */
-#define L_ECL	8				/* equivalence class */
-#define L_CCL	9				/* character class */
-#define INTOCON(c)	(v->lexcon = (c))
-#define INCON(con)	(v->lexcon == (con))
-
-/* construct pointer past end of chr array */
-#define ENDOF(array)	((array) + sizeof(array)/sizeof(chr))
-
-/*
- * lexstart - set up lexical stuff, scan leading options
- */
-static void
-lexstart(struct vars * v)
-{
-	prefixes(v);				/* may turn on new type bits etc. */
-	NOERR();
-
-	if (v->cflags & REG_QUOTE)
-	{
-		assert(!(v->cflags & (REG_ADVANCED | REG_EXPANDED | REG_NEWLINE)));
-		INTOCON(L_Q);
-	}
-	else if (v->cflags & REG_EXTENDED)
-	{
-		assert(!(v->cflags & REG_QUOTE));
-		INTOCON(L_ERE);
-	}
-	else
-	{
-		assert(!(v->cflags & (REG_QUOTE | REG_ADVF)));
-		INTOCON(L_BRE);
-	}
-
-	v->nexttype = EMPTY;		/* remember we were at the start */
-	next(v);					/* set up the first token */
-}
-
-/*
- * prefixes - implement various special prefixes
- */
-static void
-prefixes(struct vars * v)
-{
-	/* literal string doesn't get any of this stuff */
-	if (v->cflags & REG_QUOTE)
-		return;
-
-	/* initial "***" gets special things */
-	if (HAVE(4) && NEXT3('*', '*', '*'))
-		switch (*(v->now + 3))
-		{
-			case CHR('?'):		/* "***?" error, msg shows version */
-				ERR(REG_BADPAT);
-				return;			/* proceed no further */
-				break;
-			case CHR('='):		/* "***=" shifts to literal string */
-				NOTE(REG_UNONPOSIX);
-				v->cflags |= REG_QUOTE;
-				v->cflags &= ~(REG_ADVANCED | REG_EXPANDED | REG_NEWLINE);
-				v->now += 4;
-				return;			/* and there can be no more prefixes */
-				break;
-			case CHR(':'):		/* "***:" shifts to AREs */
-				NOTE(REG_UNONPOSIX);
-				v->cflags |= REG_ADVANCED;
-				v->now += 4;
-				break;
-			default:			/* otherwise *** is just an error */
-				ERR(REG_BADRPT);
-				return;
-				break;
-		}
-
-	/* BREs and EREs don't get embedded options */
-	if ((v->cflags & REG_ADVANCED) != REG_ADVANCED)
-		return;
-
-	/* embedded options (AREs only) */
-	if (HAVE(3) && NEXT2('(', '?') && iscalpha(*(v->now + 2)))
-	{
-		NOTE(REG_UNONPOSIX);
-		v->now += 2;
-		for (; !ATEOS() && iscalpha(*v->now); v->now++)
-			switch (*v->now)
-			{
-				case CHR('b'):	/* BREs (but why???) */
-					v->cflags &= ~(REG_ADVANCED | REG_QUOTE);
-					break;
-				case CHR('c'):	/* case sensitive */
-					v->cflags &= ~REG_ICASE;
-					break;
-				case CHR('e'):	/* plain EREs */
-					v->cflags |= REG_EXTENDED;
-					v->cflags &= ~(REG_ADVF | REG_QUOTE);
-					break;
-				case CHR('i'):	/* case insensitive */
-					v->cflags |= REG_ICASE;
-					break;
-				case CHR('m'):	/* Perloid synonym for n */
-				case CHR('n'):	/* \n affects ^ $ . [^ */
-					v->cflags |= REG_NEWLINE;
-					break;
-				case CHR('p'):	/* ~Perl, \n affects . [^ */
-					v->cflags |= REG_NLSTOP;
-					v->cflags &= ~REG_NLANCH;
-					break;
-				case CHR('q'):	/* literal string */
-					v->cflags |= REG_QUOTE;
-					v->cflags &= ~REG_ADVANCED;
-					break;
-				case CHR('s'):	/* single line, \n ordinary */
-					v->cflags &= ~REG_NEWLINE;
-					break;
-				case CHR('t'):	/* tight syntax */
-					v->cflags &= ~REG_EXPANDED;
-					break;
-				case CHR('w'):	/* weird, \n affects ^ $ only */
-					v->cflags &= ~REG_NLSTOP;
-					v->cflags |= REG_NLANCH;
-					break;
-				case CHR('x'):	/* expanded syntax */
-					v->cflags |= REG_EXPANDED;
-					break;
-				default:
-					ERR(REG_BADOPT);
-					return;
-			}
-		if (!NEXT1(')'))
-		{
-			ERR(REG_BADOPT);
-			return;
-		}
-		v->now++;
-		if (v->cflags & REG_QUOTE)
-			v->cflags &= ~(REG_EXPANDED | REG_NEWLINE);
-	}
-}
-
-/*
- * lexnest - "call a subroutine", interpolating string at the lexical level
- *
- * Note, this is not a very general facility.  There are a number of
- * implicit assumptions about what sorts of strings can be subroutines.
- */
-static void
-lexnest(struct vars * v,
-		const chr *beginp,		/* start of interpolation */
-		const chr *endp)		/* one past end of interpolation */
-{
-	assert(v->savenow == NULL); /* only one level of nesting */
-	v->savenow = v->now;
-	v->savestop = v->stop;
-	v->now = beginp;
-	v->stop = endp;
-}
-
-/*
- * string constants to interpolate as expansions of things like \d
- */
-static const chr backd[] = {	/* \d */
-	CHR('['), CHR('['), CHR(':'),
-	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
-	CHR(':'), CHR(']'), CHR(']')
-};
-static const chr backD[] = {	/* \D */
-	CHR('['), CHR('^'), CHR('['), CHR(':'),
-	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
-	CHR(':'), CHR(']'), CHR(']')
-};
-static const chr brbackd[] = {	/* \d within brackets */
-	CHR('['), CHR(':'),
-	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
-	CHR(':'), CHR(']')
-};
-static const chr backs[] = {	/* \s */
-	CHR('['), CHR('['), CHR(':'),
-	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
-	CHR(':'), CHR(']'), CHR(']')
-};
-static const chr backS[] = {	/* \S */
-	CHR('['), CHR('^'), CHR('['), CHR(':'),
-	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
-	CHR(':'), CHR(']'), CHR(']')
-};
-static const chr brbacks[] = {	/* \s within brackets */
-	CHR('['), CHR(':'),
-	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
-	CHR(':'), CHR(']')
-};
-static const chr backw[] = {	/* \w */
-	CHR('['), CHR('['), CHR(':'),
-	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
-	CHR(':'), CHR(']'), CHR('_'), CHR(']')
-};
-static const chr backW[] = {	/* \W */
-	CHR('['), CHR('^'), CHR('['), CHR(':'),
-	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
-	CHR(':'), CHR(']'), CHR('_'), CHR(']')
-};
-static const chr brbackw[] = {	/* \w within brackets */
-	CHR('['), CHR(':'),
-	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
-	CHR(':'), CHR(']'), CHR('_')
-};
-
-/*
- * lexword - interpolate a bracket expression for word characters
- * Possibly ought to inquire whether there is a "word" character class.
- */
-static void
-lexword(struct vars * v)
-{
-	lexnest(v, backw, ENDOF(backw));
-}
-
-/*
- * next - get next token
- */
-static int						/* 1 normal, 0 failure */
-next(struct vars * v)
-{
-	chr			c;
-
-	/* errors yield an infinite sequence of failures */
-	if (ISERR())
-		return 0;				/* the error has set nexttype to EOS */
-
-	/* remember flavor of last token */
-	v->lasttype = v->nexttype;
-
-	/* REG_BOSONLY */
-	if (v->nexttype == EMPTY && (v->cflags & REG_BOSONLY))
-	{
-		/* at start of a REG_BOSONLY RE */
-		RETV(SBEGIN, 0);		/* same as \A */
-	}
-
-	/* if we're nested and we've hit end, return to outer level */
-	if (v->savenow != NULL && ATEOS())
-	{
-		v->now = v->savenow;
-		v->stop = v->savestop;
-		v->savenow = v->savestop = NULL;
-	}
-
-	/* skip white space etc. if appropriate (not in literal or []) */
-	if (v->cflags & REG_EXPANDED)
-		switch (v->lexcon)
-		{
-			case L_ERE:
-			case L_BRE:
-			case L_EBND:
-			case L_BBND:
-				skip(v);
-				break;
-		}
-
-	/* handle EOS, depending on context */
-	if (ATEOS())
-	{
-		switch (v->lexcon)
-		{
-			case L_ERE:
-			case L_BRE:
-			case L_Q:
-				RET(EOS);
-				break;
-			case L_EBND:
-			case L_BBND:
-				FAILW(REG_EBRACE);
-				break;
-			case L_BRACK:
-			case L_CEL:
-			case L_ECL:
-			case L_CCL:
-				FAILW(REG_EBRACK);
-				break;
-		}
-		assert(NOTREACHED);
-	}
-
-	/* okay, time to actually get a character */
-	c = *v->now++;
-
-	/* deal with the easy contexts, punt EREs to code below */
-	switch (v->lexcon)
-	{
-		case L_BRE:				/* punt BREs to separate function */
-			return brenext(v, c);
-			break;
-		case L_ERE:				/* see below */
-			break;
-		case L_Q:				/* literal strings are easy */
-			RETV(PLAIN, c);
-			break;
-		case L_BBND:			/* bounds are fairly simple */
-		case L_EBND:
-			switch (c)
-			{
-				case CHR('0'):
-				case CHR('1'):
-				case CHR('2'):
-				case CHR('3'):
-				case CHR('4'):
-				case CHR('5'):
-				case CHR('6'):
-				case CHR('7'):
-				case CHR('8'):
-				case CHR('9'):
-					RETV(DIGIT, (chr) DIGITVAL(c));
-					break;
-				case CHR(','):
-					RET(',');
-					break;
-				case CHR('}'):	/* ERE bound ends with } */
-					if (INCON(L_EBND))
-					{
-						INTOCON(L_ERE);
-						if ((v->cflags & REG_ADVF) && NEXT1('?'))
-						{
-							v->now++;
-							NOTE(REG_UNONPOSIX);
-							RETV('}', 0);
-						}
-						RETV('}', 1);
-					}
-					else
-						FAILW(REG_BADBR);
-					break;
-				case CHR('\\'):	/* BRE bound ends with \} */
-					if (INCON(L_BBND) && NEXT1('}'))
-					{
-						v->now++;
-						INTOCON(L_BRE);
-						RET('}');
-					}
-					else
-						FAILW(REG_BADBR);
-					break;
-				default:
-					FAILW(REG_BADBR);
-					break;
-			}
-			assert(NOTREACHED);
-			break;
-		case L_BRACK:			/* brackets are not too hard */
-			switch (c)
-			{
-				case CHR(']'):
-					if (LASTTYPE('['))
-						RETV(PLAIN, c);
-					else
-					{
-						INTOCON((v->cflags & REG_EXTENDED) ?
-								L_ERE : L_BRE);
-						RET(']');
-					}
-					break;
-				case CHR('\\'):
-					NOTE(REG_UBBS);
-					if (!(v->cflags & REG_ADVF))
-						RETV(PLAIN, c);
-					NOTE(REG_UNONPOSIX);
-					if (ATEOS())
-						FAILW(REG_EESCAPE);
-					(DISCARD) lexescape(v);
-					switch (v->nexttype)
-					{			/* not all escapes okay here */
-						case PLAIN:
-							return 1;
-							break;
-						case CCLASS:
-							switch (v->nextvalue)
-							{
-								case 'd':
-									lexnest(v, brbackd, ENDOF(brbackd));
-									break;
-								case 's':
-									lexnest(v, brbacks, ENDOF(brbacks));
-									break;
-								case 'w':
-									lexnest(v, brbackw, ENDOF(brbackw));
-									break;
-								default:
-									FAILW(REG_EESCAPE);
-									break;
-							}
-							/* lexnest done, back up and try again */
-							v->nexttype = v->lasttype;
-							return next(v);
-							break;
-					}
-					/* not one of the acceptable escapes */
-					FAILW(REG_EESCAPE);
-					break;
-				case CHR('-'):
-					if (LASTTYPE('[') || NEXT1(']'))
-						RETV(PLAIN, c);
-					else
-						RETV(RANGE, c);
-					break;
-				case CHR('['):
-					if (ATEOS())
-						FAILW(REG_EBRACK);
-					switch (*v->now++)
-					{
-						case CHR('.'):
-							INTOCON(L_CEL);
-							/* might or might not be locale-specific */
-							RET(COLLEL);
-							break;
-						case CHR('='):
-							INTOCON(L_ECL);
-							NOTE(REG_ULOCALE);
-							RET(ECLASS);
-							break;
-						case CHR(':'):
-							INTOCON(L_CCL);
-							NOTE(REG_ULOCALE);
-							RET(CCLASS);
-							break;
-						default:		/* oops */
-							v->now--;
-							RETV(PLAIN, c);
-							break;
-					}
-					assert(NOTREACHED);
-					break;
-				default:
-					RETV(PLAIN, c);
-					break;
-			}
-			assert(NOTREACHED);
-			break;
-		case L_CEL:				/* collating elements are easy */
-			if (c == CHR('.') && NEXT1(']'))
-			{
-				v->now++;
-				INTOCON(L_BRACK);
-				RETV(END, '.');
-			}
-			else
-				RETV(PLAIN, c);
-			break;
-		case L_ECL:				/* ditto equivalence classes */
-			if (c == CHR('=') && NEXT1(']'))
-			{
-				v->now++;
-				INTOCON(L_BRACK);
-				RETV(END, '=');
-			}
-			else
-				RETV(PLAIN, c);
-			break;
-		case L_CCL:				/* ditto character classes */
-			if (c == CHR(':') && NEXT1(']'))
-			{
-				v->now++;
-				INTOCON(L_BRACK);
-				RETV(END, ':');
-			}
-			else
-				RETV(PLAIN, c);
-			break;
-		default:
-			assert(NOTREACHED);
-			break;
-	}
-
-	/* that got rid of everything except EREs and AREs */
-	assert(INCON(L_ERE));
-
-	/* deal with EREs and AREs, except for backslashes */
-	switch (c)
-	{
-		case CHR('|'):
-			RET('|');
-			break;
-		case CHR('*'):
-			if ((v->cflags & REG_ADVF) && NEXT1('?'))
-			{
-				v->now++;
-				NOTE(REG_UNONPOSIX);
-				RETV('*', 0);
-			}
-			RETV('*', 1);
-			break;
-		case CHR('+'):
-			if ((v->cflags & REG_ADVF) && NEXT1('?'))
-			{
-				v->now++;
-				NOTE(REG_UNONPOSIX);
-				RETV('+', 0);
-			}
-			RETV('+', 1);
-			break;
-		case CHR('?'):
-			if ((v->cflags & REG_ADVF) && NEXT1('?'))
-			{
-				v->now++;
-				NOTE(REG_UNONPOSIX);
-				RETV('?', 0);
-			}
-			RETV('?', 1);
-			break;
-		case CHR('{'):			/* bounds start or plain character */
-			if (v->cflags & REG_EXPANDED)
-				skip(v);
-			if (ATEOS() || !iscdigit(*v->now))
-			{
-				NOTE(REG_UBRACES);
-				NOTE(REG_UUNSPEC);
-				RETV(PLAIN, c);
-			}
-			else
-			{
-				NOTE(REG_UBOUNDS);
-				INTOCON(L_EBND);
-				RET('{');
-			}
-			assert(NOTREACHED);
-			break;
-		case CHR('('):			/* parenthesis, or advanced extension */
-			if ((v->cflags & REG_ADVF) && NEXT1('?'))
-			{
-				NOTE(REG_UNONPOSIX);
-				v->now++;
-				if (ATEOS())
-					FAILW(REG_BADRPT);
-				switch (*v->now++)
-				{
-					case CHR(':'):		/* non-capturing paren */
-						RETV('(', 0);
-						break;
-					case CHR('#'):		/* comment */
-						while (!ATEOS() && *v->now != CHR(')'))
-							v->now++;
-						if (!ATEOS())
-							v->now++;
-						assert(v->nexttype == v->lasttype);
-						return next(v);
-						break;
-					case CHR('='):		/* positive lookahead */
-						NOTE(REG_ULOOKAROUND);
-						RETV(LACON, LATYPE_AHEAD_POS);
-						break;
-					case CHR('!'):		/* negative lookahead */
-						NOTE(REG_ULOOKAROUND);
-						RETV(LACON, LATYPE_AHEAD_NEG);
-						break;
-					case CHR('<'):
-						if (ATEOS())
-							FAILW(REG_BADRPT);
-						switch (*v->now++)
-						{
-							case CHR('='):		/* positive lookbehind */
-								NOTE(REG_ULOOKAROUND);
-								RETV(LACON, LATYPE_BEHIND_POS);
-								break;
-							case CHR('!'):		/* negative lookbehind */
-								NOTE(REG_ULOOKAROUND);
-								RETV(LACON, LATYPE_BEHIND_NEG);
-								break;
-							default:
-								FAILW(REG_BADRPT);
-								break;
-						}
-						assert(NOTREACHED);
-						break;
-					default:
-						FAILW(REG_BADRPT);
-						break;
-				}
-				assert(NOTREACHED);
-			}
-			if (v->cflags & REG_NOSUB)
-				RETV('(', 0);	/* all parens non-capturing */
-			else
-				RETV('(', 1);
-			break;
-		case CHR(')'):
-			if (LASTTYPE('('))
-				NOTE(REG_UUNSPEC);
-			RETV(')', c);
-			break;
-		case CHR('['):			/* easy except for [[:<:]] and [[:>:]] */
-			if (HAVE(6) && *(v->now + 0) == CHR('[') &&
-				*(v->now + 1) == CHR(':') &&
-				(*(v->now + 2) == CHR('<') ||
-				 *(v->now + 2) == CHR('>')) &&
-				*(v->now + 3) == CHR(':') &&
-				*(v->now + 4) == CHR(']') &&
-				*(v->now + 5) == CHR(']'))
-			{
-				c = *(v->now + 2);
-				v->now += 6;
-				NOTE(REG_UNONPOSIX);
-				RET((c == CHR('<')) ? '<' : '>');
-			}
-			INTOCON(L_BRACK);
-			if (NEXT1('^'))
-			{
-				v->now++;
-				RETV('[', 0);
-			}
-			RETV('[', 1);
-			break;
-		case CHR('.'):
-			RET('.');
-			break;
-		case CHR('^'):
-			RET('^');
-			break;
-		case CHR('$'):
-			RET('$');
-			break;
-		case CHR('\\'): /* mostly punt backslashes to code below */
-			if (ATEOS())
-				FAILW(REG_EESCAPE);
-			break;
-		default:				/* ordinary character */
-			RETV(PLAIN, c);
-			break;
-	}
-
-	/* ERE/ARE backslash handling; backslash already eaten */
-	assert(!ATEOS());
-	if (!(v->cflags & REG_ADVF))
-	{							/* only AREs have non-trivial escapes */
-		if (iscalnum(*v->now))
-		{
-			NOTE(REG_UBSALNUM);
-			NOTE(REG_UUNSPEC);
-		}
-		RETV(PLAIN, *v->now++);
-	}
-	(DISCARD) lexescape(v);
-	if (ISERR())
-		FAILW(REG_EESCAPE);
-	if (v->nexttype == CCLASS)
-	{							/* fudge at lexical level */
-		switch (v->nextvalue)
-		{
-			case 'd':
-				lexnest(v, backd, ENDOF(backd));
-				break;
-			case 'D':
-				lexnest(v, backD, ENDOF(backD));
-				break;
-			case 's':
-				lexnest(v, backs, ENDOF(backs));
-				break;
-			case 'S':
-				lexnest(v, backS, ENDOF(backS));
-				break;
-			case 'w':
-				lexnest(v, backw, ENDOF(backw));
-				break;
-			case 'W':
-				lexnest(v, backW, ENDOF(backW));
-				break;
-			default:
-				assert(NOTREACHED);
-				FAILW(REG_ASSERT);
-				break;
-		}
-		/* lexnest done, back up and try again */
-		v->nexttype = v->lasttype;
-		return next(v);
-	}
-	/* otherwise, lexescape has already done the work */
-	return !ISERR();
-}
-
-/*
- * lexescape - parse an ARE backslash escape (backslash already eaten)
- * Note slightly nonstandard use of the CCLASS type code.
- */
-static int						/* not actually used, but convenient for RETV */
-lexescape(struct vars * v)
-{
-	chr			c;
-	static const chr alert[] = {
-		CHR('a'), CHR('l'), CHR('e'), CHR('r'), CHR('t')
-	};
-	static const chr esc[] = {
-		CHR('E'), CHR('S'), CHR('C')
-	};
-	const chr  *save;
-
-	assert(v->cflags & REG_ADVF);
-
-	assert(!ATEOS());
-	c = *v->now++;
-	if (!iscalnum(c))
-		RETV(PLAIN, c);
-
-	NOTE(REG_UNONPOSIX);
-	switch (c)
-	{
-		case CHR('a'):
-			RETV(PLAIN, chrnamed(v, alert, ENDOF(alert), CHR('\007')));
-			break;
-		case CHR('A'):
-			RETV(SBEGIN, 0);
-			break;
-		case CHR('b'):
-			RETV(PLAIN, CHR('\b'));
-			break;
-		case CHR('B'):
-			RETV(PLAIN, CHR('\\'));
-			break;
-		case CHR('c'):
-			NOTE(REG_UUNPORT);
-			if (ATEOS())
-				FAILW(REG_EESCAPE);
-			RETV(PLAIN, (chr) (*v->now++ & 037));
-			break;
-		case CHR('d'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 'd');
-			break;
-		case CHR('D'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 'D');
-			break;
-		case CHR('e'):
-			NOTE(REG_UUNPORT);
-			RETV(PLAIN, chrnamed(v, esc, ENDOF(esc), CHR('\033')));
-			break;
-		case CHR('f'):
-			RETV(PLAIN, CHR('\f'));
-			break;
-		case CHR('m'):
-			RET('<');
-			break;
-		case CHR('M'):
-			RET('>');
-			break;
-		case CHR('n'):
-			RETV(PLAIN, CHR('\n'));
-			break;
-		case CHR('r'):
-			RETV(PLAIN, CHR('\r'));
-			break;
-		case CHR('s'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 's');
-			break;
-		case CHR('S'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 'S');
-			break;
-		case CHR('t'):
-			RETV(PLAIN, CHR('\t'));
-			break;
-		case CHR('u'):
-			c = lexdigits(v, 16, 4, 4);
-			if (ISERR())
-				FAILW(REG_EESCAPE);
-			RETV(PLAIN, c);
-			break;
-		case CHR('U'):
-			c = lexdigits(v, 16, 8, 8);
-			if (ISERR())
-				FAILW(REG_EESCAPE);
-			RETV(PLAIN, c);
-			break;
-		case CHR('v'):
-			RETV(PLAIN, CHR('\v'));
-			break;
-		case CHR('w'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 'w');
-			break;
-		case CHR('W'):
-			NOTE(REG_ULOCALE);
-			RETV(CCLASS, 'W');
-			break;
-		case CHR('x'):
-			NOTE(REG_UUNPORT);
-			c = lexdigits(v, 16, 1, 255);		/* REs >255 long outside spec */
-			if (ISERR())
-				FAILW(REG_EESCAPE);
-			RETV(PLAIN, c);
-			break;
-		case CHR('y'):
-			NOTE(REG_ULOCALE);
-			RETV(WBDRY, 0);
-			break;
-		case CHR('Y'):
-			NOTE(REG_ULOCALE);
-			RETV(NWBDRY, 0);
-			break;
-		case CHR('Z'):
-			RETV(SEND, 0);
-			break;
-		case CHR('1'):
-		case CHR('2'):
-		case CHR('3'):
-		case CHR('4'):
-		case CHR('5'):
-		case CHR('6'):
-		case CHR('7'):
-		case CHR('8'):
-		case CHR('9'):
-			save = v->now;
-			v->now--;			/* put first digit back */
-			c = lexdigits(v, 10, 1, 255);		/* REs >255 long outside spec */
-			if (ISERR())
-				FAILW(REG_EESCAPE);
-			/* ugly heuristic (first test is "exactly 1 digit?") */
-			if (v->now == save || ((int) c > 0 && (int) c <= v->nsubexp))
-			{
-				NOTE(REG_UBACKREF);
-				RETV(BACKREF, (chr) c);
-			}
-			/* oops, doesn't look like it's a backref after all... */
-			v->now = save;
-			/* and fall through into octal number */
-		case CHR('0'):
-			NOTE(REG_UUNPORT);
-			v->now--;			/* put first digit back */
-			c = lexdigits(v, 8, 1, 3);
-			if (ISERR())
-				FAILW(REG_EESCAPE);
-			if (c > 0xff)
-			{
-				/* out of range, so we handled one digit too much */
-				v->now--;
-				c >>= 3;
-			}
-			RETV(PLAIN, c);
-			break;
-		default:
-			assert(iscalpha(c));
-			FAILW(REG_EESCAPE); /* unknown alphabetic escape */
-			break;
-	}
-	assert(NOTREACHED);
-}
-
-/*
- * lexdigits - slurp up digits and return chr value
- */
-static chr						/* chr value; errors signalled via ERR */
-lexdigits(struct vars * v,
-		  int base,
-		  int minlen,
-		  int maxlen)
-{
-	uchr		n;				/* unsigned to avoid overflow misbehavior */
-	int			len;
-	chr			c;
-	int			d;
-	const uchr	ub = (uchr) base;
-
-	n = 0;
-	for (len = 0; len < maxlen && !ATEOS(); len++)
-	{
-		c = *v->now++;
-		switch (c)
-		{
-			case CHR('0'):
-			case CHR('1'):
-			case CHR('2'):
-			case CHR('3'):
-			case CHR('4'):
-			case CHR('5'):
-			case CHR('6'):
-			case CHR('7'):
-			case CHR('8'):
-			case CHR('9'):
-				d = DIGITVAL(c);
-				break;
-			case CHR('a'):
-			case CHR('A'):
-				d = 10;
-				break;
-			case CHR('b'):
-			case CHR('B'):
-				d = 11;
-				break;
-			case CHR('c'):
-			case CHR('C'):
-				d = 12;
-				break;
-			case CHR('d'):
-			case CHR('D'):
-				d = 13;
-				break;
-			case CHR('e'):
-			case CHR('E'):
-				d = 14;
-				break;
-			case CHR('f'):
-			case CHR('F'):
-				d = 15;
-				break;
-			default:
-				v->now--;		/* oops, not a digit at all */
-				d = -1;
-				break;
-		}
-
-		if (d >= base)
-		{						/* not a plausible digit */
-			v->now--;
-			d = -1;
-		}
-		if (d < 0)
-			break;				/* NOTE BREAK OUT */
-		n = n * ub + (uchr) d;
-	}
-	if (len < minlen)
-		ERR(REG_EESCAPE);
-
-	return (chr) n;
-}
-
-/*
- * brenext - get next BRE token
- *
- * This is much like EREs except for all the stupid backslashes and the
- * context-dependency of some things.
- */
-static int						/* 1 normal, 0 failure */
-brenext(struct vars * v,
-		chr pc)
-{
-	chr			c = (chr) pc;
-
-	switch (c)
-	{
-		case CHR('*'):
-			if (LASTTYPE(EMPTY) || LASTTYPE('(') || LASTTYPE('^'))
-				RETV(PLAIN, c);
-			RET('*');
-			break;
-		case CHR('['):
-			if (HAVE(6) && *(v->now + 0) == CHR('[') &&
-				*(v->now + 1) == CHR(':') &&
-				(*(v->now + 2) == CHR('<') ||
-				 *(v->now + 2) == CHR('>')) &&
-				*(v->now + 3) == CHR(':') &&
-				*(v->now + 4) == CHR(']') &&
-				*(v->now + 5) == CHR(']'))
-			{
-				c = *(v->now + 2);
-				v->now += 6;
-				NOTE(REG_UNONPOSIX);
-				RET((c == CHR('<')) ? '<' : '>');
-			}
-			INTOCON(L_BRACK);
-			if (NEXT1('^'))
-			{
-				v->now++;
-				RETV('[', 0);
-			}
-			RETV('[', 1);
-			break;
-		case CHR('.'):
-			RET('.');
-			break;
-		case CHR('^'):
-			if (LASTTYPE(EMPTY))
-				RET('^');
-			if (LASTTYPE('('))
-			{
-				NOTE(REG_UUNSPEC);
-				RET('^');
-			}
-			RETV(PLAIN, c);
-			break;
-		case CHR('$'):
-			if (v->cflags & REG_EXPANDED)
-				skip(v);
-			if (ATEOS())
-				RET('$');
-			if (NEXT2('\\', ')'))
-			{
-				NOTE(REG_UUNSPEC);
-				RET('$');
-			}
-			RETV(PLAIN, c);
-			break;
-		case CHR('\\'):
-			break;				/* see below */
-		default:
-			RETV(PLAIN, c);
-			break;
-	}
-
-	assert(c == CHR('\\'));
-
-	if (ATEOS())
-		FAILW(REG_EESCAPE);
-
-	c = *v->now++;
-	switch (c)
-	{
-		case CHR('{'):
-			INTOCON(L_BBND);
-			NOTE(REG_UBOUNDS);
-			RET('{');
-			break;
-		case CHR('('):
-			RETV('(', 1);
-			break;
-		case CHR(')'):
-			RETV(')', c);
-			break;
-		case CHR('<'):
-			NOTE(REG_UNONPOSIX);
-			RET('<');
-			break;
-		case CHR('>'):
-			NOTE(REG_UNONPOSIX);
-			RET('>');
-			break;
-		case CHR('1'):
-		case CHR('2'):
-		case CHR('3'):
-		case CHR('4'):
-		case CHR('5'):
-		case CHR('6'):
-		case CHR('7'):
-		case CHR('8'):
-		case CHR('9'):
-			NOTE(REG_UBACKREF);
-			RETV(BACKREF, (chr) DIGITVAL(c));
-			break;
-		default:
-			if (iscalnum(c))
-			{
-				NOTE(REG_UBSALNUM);
-				NOTE(REG_UUNSPEC);
-			}
-			RETV(PLAIN, c);
-			break;
-	}
-
-	assert(NOTREACHED);
-	return 0;
-}
-
-/*
- * skip - skip white space and comments in expanded form
- */
-static void
-skip(struct vars * v)
-{
-	const chr  *start = v->now;
-
-	assert(v->cflags & REG_EXPANDED);
-
-	for (;;)
-	{
-		while (!ATEOS() && iscspace(*v->now))
-			v->now++;
-		if (ATEOS() || *v->now != CHR('#'))
-			break;				/* NOTE BREAK OUT */
-		assert(NEXT1('#'));
-		while (!ATEOS() && *v->now != CHR('\n'))
-			v->now++;
-		/* leave the newline to be picked up by the iscspace loop */
-	}
-
-	if (v->now != start)
-		NOTE(REG_UNONPOSIX);
-}
-
-/*
- * newline - return the chr for a newline
- *
- * This helps confine use of CHR to this source file.
- */
-static chr
-newline(void)
-{
-	return CHR('\n');
-}
-
-/*
- * chrnamed - return the chr known by a given (chr string) name
- *
- * The code is a bit clumsy, but this routine gets only such specialized
- * use that it hardly matters.
- */
-static chr
-chrnamed(struct vars * v,
-		 const chr *startp,		/* start of name */
-		 const chr *endp,		/* just past end of name */
-		 chr lastresort)		/* what to return if name lookup fails */
-{
-	celt		c;
-	int			errsave;
-	int			e;
-	struct cvec *cv;
-
-	errsave = v->err;
-	v->err = 0;
-	c = element(v, startp, endp);
-	e = v->err;
-	v->err = errsave;
-
-	if (e != 0)
-		return (chr) lastresort;
-
-	cv = range(v, c, c, 0);
-	if (cv->nchrs == 0)
-		return (chr) lastresort;
-	return cv->chrs[0];
-}
diff --git a/src/backend/regex/regc_locale.c b/src/backend/regex/regc_locale.c
deleted file mode 100644
index e7bbb50..0000000
--- a/src/backend/regex/regc_locale.c
+++ /dev/null
@@ -1,700 +0,0 @@
-/*
- * regc_locale.c --
- *
- *	This file contains locale-specific regexp routines.
- *	This file is #included by regcomp.c.
- *
- * Copyright (c) 1998 by Scriptics Corporation.
- *
- * This software is copyrighted by the Regents of the University of
- * California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
- * Corporation and other parties.  The following terms apply to all files
- * associated with the software unless explicitly disclaimed in
- * individual files.
- *
- * The authors hereby grant permission to use, copy, modify, distribute,
- * and license this software and its documentation for any purpose, provided
- * that existing copyright notices are retained in all copies and that this
- * notice is included verbatim in any distributions. No written agreement,
- * license, or royalty fee is required for any of the authorized uses.
- * Modifications to this software may be copyrighted by their authors
- * and need not follow the licensing terms described here, provided that
- * the new terms are clearly indicated on the first page of each file where
- * they apply.
- *
- * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
- * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
- * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
- * DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
- * IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
- * NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
- * MODIFICATIONS.
- *
- * GOVERNMENT USE: If you are acquiring this software on behalf of the
- * U.S. government, the Government shall have only "Restricted Rights"
- * in the software and related documentation as defined in the Federal
- * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
- * are acquiring the software on behalf of the Department of Defense, the
- * software shall be classified as "Commercial Computer Software" and the
- * Government shall have only "Restricted Rights" as defined in Clause
- * 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
- * authors grant the U.S. Government and others acting in its behalf
- * permission to use and distribute the software in accordance with the
- * terms specified in this license.
- *
- * src/backend/regex/regc_locale.c
- */
-
-/* ASCII character-name table */
-
-static const struct cname
-{
-	const char *name;
-	const char	code;
-}	cnames[] =
-
-{
-	{
-		"NUL", '\0'
-	},
-	{
-		"SOH", '\001'
-	},
-	{
-		"STX", '\002'
-	},
-	{
-		"ETX", '\003'
-	},
-	{
-		"EOT", '\004'
-	},
-	{
-		"ENQ", '\005'
-	},
-	{
-		"ACK", '\006'
-	},
-	{
-		"BEL", '\007'
-	},
-	{
-		"alert", '\007'
-	},
-	{
-		"BS", '\010'
-	},
-	{
-		"backspace", '\b'
-	},
-	{
-		"HT", '\011'
-	},
-	{
-		"tab", '\t'
-	},
-	{
-		"LF", '\012'
-	},
-	{
-		"newline", '\n'
-	},
-	{
-		"VT", '\013'
-	},
-	{
-		"vertical-tab", '\v'
-	},
-	{
-		"FF", '\014'
-	},
-	{
-		"form-feed", '\f'
-	},
-	{
-		"CR", '\015'
-	},
-	{
-		"carriage-return", '\r'
-	},
-	{
-		"SO", '\016'
-	},
-	{
-		"SI", '\017'
-	},
-	{
-		"DLE", '\020'
-	},
-	{
-		"DC1", '\021'
-	},
-	{
-		"DC2", '\022'
-	},
-	{
-		"DC3", '\023'
-	},
-	{
-		"DC4", '\024'
-	},
-	{
-		"NAK", '\025'
-	},
-	{
-		"SYN", '\026'
-	},
-	{
-		"ETB", '\027'
-	},
-	{
-		"CAN", '\030'
-	},
-	{
-		"EM", '\031'
-	},
-	{
-		"SUB", '\032'
-	},
-	{
-		"ESC", '\033'
-	},
-	{
-		"IS4", '\034'
-	},
-	{
-		"FS", '\034'
-	},
-	{
-		"IS3", '\035'
-	},
-	{
-		"GS", '\035'
-	},
-	{
-		"IS2", '\036'
-	},
-	{
-		"RS", '\036'
-	},
-	{
-		"IS1", '\037'
-	},
-	{
-		"US", '\037'
-	},
-	{
-		"space", ' '
-	},
-	{
-		"exclamation-mark", '!'
-	},
-	{
-		"quotation-mark", '"'
-	},
-	{
-		"number-sign", '#'
-	},
-	{
-		"dollar-sign", '$'
-	},
-	{
-		"percent-sign", '%'
-	},
-	{
-		"ampersand", '&'
-	},
-	{
-		"apostrophe", '\''
-	},
-	{
-		"left-parenthesis", '('
-	},
-	{
-		"right-parenthesis", ')'
-	},
-	{
-		"asterisk", '*'
-	},
-	{
-		"plus-sign", '+'
-	},
-	{
-		"comma", ','
-	},
-	{
-		"hyphen", '-'
-	},
-	{
-		"hyphen-minus", '-'
-	},
-	{
-		"period", '.'
-	},
-	{
-		"full-stop", '.'
-	},
-	{
-		"slash", '/'
-	},
-	{
-		"solidus", '/'
-	},
-	{
-		"zero", '0'
-	},
-	{
-		"one", '1'
-	},
-	{
-		"two", '2'
-	},
-	{
-		"three", '3'
-	},
-	{
-		"four", '4'
-	},
-	{
-		"five", '5'
-	},
-	{
-		"six", '6'
-	},
-	{
-		"seven", '7'
-	},
-	{
-		"eight", '8'
-	},
-	{
-		"nine", '9'
-	},
-	{
-		"colon", ':'
-	},
-	{
-		"semicolon", ';'
-	},
-	{
-		"less-than-sign", '<'
-	},
-	{
-		"equals-sign", '='
-	},
-	{
-		"greater-than-sign", '>'
-	},
-	{
-		"question-mark", '?'
-	},
-	{
-		"commercial-at", '@'
-	},
-	{
-		"left-square-bracket", '['
-	},
-	{
-		"backslash", '\\'
-	},
-	{
-		"reverse-solidus", '\\'
-	},
-	{
-		"right-square-bracket", ']'
-	},
-	{
-		"circumflex", '^'
-	},
-	{
-		"circumflex-accent", '^'
-	},
-	{
-		"underscore", '_'
-	},
-	{
-		"low-line", '_'
-	},
-	{
-		"grave-accent", '`'
-	},
-	{
-		"left-brace", '{'
-	},
-	{
-		"left-curly-bracket", '{'
-	},
-	{
-		"vertical-line", '|'
-	},
-	{
-		"right-brace", '}'
-	},
-	{
-		"right-curly-bracket", '}'
-	},
-	{
-		"tilde", '~'
-	},
-	{
-		"DEL", '\177'
-	},
-	{
-		NULL, 0
-	}
-};
-
-
-/*
- * We do not use the hard-wired Unicode classification tables that Tcl does.
- * This is because (a) we need to deal with other encodings besides Unicode,
- * and (b) we want to track the behavior of the libc locale routines as
- * closely as possible.  For example, it wouldn't be unreasonable for a
- * locale to not consider every Unicode letter as a letter.  So we build
- * character classification cvecs by asking libc, even for Unicode.
- */
-
-
-/*
- * element - map collating-element name to celt
- */
-static celt
-element(struct vars * v,		/* context */
-		const chr *startp,		/* points to start of name */
-		const chr *endp)		/* points just past end of name */
-{
-	const struct cname *cn;
-	size_t		len;
-
-	/* generic:  one-chr names stand for themselves */
-	assert(startp < endp);
-	len = endp - startp;
-	if (len == 1)
-		return *startp;
-
-	NOTE(REG_ULOCALE);
-
-	/* search table */
-	for (cn = cnames; cn->name != NULL; cn++)
-	{
-		if (strlen(cn->name) == len &&
-			pg_char_and_wchar_strncmp(cn->name, startp, len) == 0)
-		{
-			break;				/* NOTE BREAK OUT */
-		}
-	}
-	if (cn->name != NULL)
-		return CHR(cn->code);
-
-	/* couldn't find it */
-	ERR(REG_ECOLLATE);
-	return 0;
-}
-
-/*
- * range - supply cvec for a range, including legality check
- */
-static struct cvec *
-range(struct vars * v,			/* context */
-	  celt a,					/* range start */
-	  celt b,					/* range end, might equal a */
-	  int cases)				/* case-independent? */
-{
-	int			nchrs;
-	struct cvec *cv;
-	celt		c,
-				lc,
-				uc;
-
-	if (a != b && !before(a, b))
-	{
-		ERR(REG_ERANGE);
-		return NULL;
-	}
-
-	if (!cases)
-	{							/* easy version */
-		cv = getcvec(v, 0, 1);
-		NOERRN();
-		addrange(cv, a, b);
-		return cv;
-	}
-
-	/*
-	 * When case-independent, it's hard to decide when cvec ranges are usable,
-	 * so for now at least, we won't try.  We allocate enough space for two
-	 * case variants plus a little extra for the two title case variants.
-	 */
-
-	nchrs = (b - a + 1) * 2 + 4;
-
-	cv = getcvec(v, nchrs, 0);
-	NOERRN();
-
-	for (c = a; c <= b; c++)
-	{
-		addchr(cv, c);
-		lc = pg_wc_tolower((chr) c);
-		if (c != lc)
-			addchr(cv, lc);
-		uc = pg_wc_toupper((chr) c);
-		if (c != uc)
-			addchr(cv, uc);
-	}
-
-	return cv;
-}
-
-/*
- * before - is celt x before celt y, for purposes of range legality?
- */
-static int						/* predicate */
-before(celt x, celt y)
-{
-	if (x < y)
-		return 1;
-	return 0;
-}
-
-/*
- * eclass - supply cvec for an equivalence class
- * Must include case counterparts on request.
- */
-static struct cvec *
-eclass(struct vars * v,			/* context */
-	   celt c,					/* Collating element representing the
-								 * equivalence class. */
-	   int cases)				/* all cases? */
-{
-	struct cvec *cv;
-
-	/* crude fake equivalence class for testing */
-	if ((v->cflags & REG_FAKE) && c == 'x')
-	{
-		cv = getcvec(v, 4, 0);
-		addchr(cv, (chr) 'x');
-		addchr(cv, (chr) 'y');
-		if (cases)
-		{
-			addchr(cv, (chr) 'X');
-			addchr(cv, (chr) 'Y');
-		}
-		return cv;
-	}
-
-	/* otherwise, none */
-	if (cases)
-		return allcases(v, c);
-	cv = getcvec(v, 1, 0);
-	assert(cv != NULL);
-	addchr(cv, (chr) c);
-	return cv;
-}
-
-/*
- * cclass - supply cvec for a character class
- *
- * Must include case counterparts if "cases" is true.
- *
- * The returned cvec might be either a transient cvec gotten from getcvec(),
- * or a permanently cached one from pg_ctype_get_cache().  This is okay
- * because callers are not supposed to explicitly free the result either way.
- */
-static struct cvec *
-cclass(struct vars * v,			/* context */
-	   const chr *startp,		/* where the name starts */
-	   const chr *endp,			/* just past the end of the name */
-	   int cases)				/* case-independent? */
-{
-	size_t		len;
-	struct cvec *cv = NULL;
-	const char *const * namePtr;
-	int			i,
-				index;
-
-	/*
-	 * The following arrays define the valid character class names.
-	 */
-
-	static const char *const classNames[] = {
-		"alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
-		"lower", "print", "punct", "space", "upper", "xdigit", NULL
-	};
-
-	enum classes
-	{
-		CC_ALNUM, CC_ALPHA, CC_ASCII, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH,
-		CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_XDIGIT
-	};
-
-	/*
-	 * Map the name to the corresponding enumerated value.
-	 */
-	len = endp - startp;
-	index = -1;
-	for (namePtr = classNames, i = 0; *namePtr != NULL; namePtr++, i++)
-	{
-		if (strlen(*namePtr) == len &&
-			pg_char_and_wchar_strncmp(*namePtr, startp, len) == 0)
-		{
-			index = i;
-			break;
-		}
-	}
-	if (index == -1)
-	{
-		ERR(REG_ECTYPE);
-		return NULL;
-	}
-
-	/*
-	 * Remap lower and upper to alpha if the match is case insensitive.
-	 */
-
-	if (cases &&
-		((enum classes) index == CC_LOWER ||
-		 (enum classes) index == CC_UPPER))
-		index = (int) CC_ALPHA;
-
-	/*
-	 * Now compute the character class contents.  For classes that are based
-	 * on the behavior of a <wctype.h> or <ctype.h> function, we use
-	 * pg_ctype_get_cache so that we can cache the results.  Other classes
-	 * have definitions that are hard-wired here, and for those we just
-	 * construct a transient cvec on the fly.
-	 */
-
-	switch ((enum classes) index)
-	{
-		case CC_PRINT:
-			cv = pg_ctype_get_cache(pg_wc_isprint);
-			break;
-		case CC_ALNUM:
-			cv = pg_ctype_get_cache(pg_wc_isalnum);
-			break;
-		case CC_ALPHA:
-			cv = pg_ctype_get_cache(pg_wc_isalpha);
-			break;
-		case CC_ASCII:
-			/* hard-wired meaning */
-			cv = getcvec(v, 0, 1);
-			if (cv)
-				addrange(cv, 0, 0x7f);
-			break;
-		case CC_BLANK:
-			/* hard-wired meaning */
-			cv = getcvec(v, 2, 0);
-			addchr(cv, '\t');
-			addchr(cv, ' ');
-			break;
-		case CC_CNTRL:
-			/* hard-wired meaning */
-			cv = getcvec(v, 0, 2);
-			addrange(cv, 0x0, 0x1f);
-			addrange(cv, 0x7f, 0x9f);
-			break;
-		case CC_DIGIT:
-			cv = pg_ctype_get_cache(pg_wc_isdigit);
-			break;
-		case CC_PUNCT:
-			cv = pg_ctype_get_cache(pg_wc_ispunct);
-			break;
-		case CC_XDIGIT:
-
-			/*
-			 * It's not clear how to define this in non-western locales, and
-			 * even less clear that there's any particular use in trying. So
-			 * just hard-wire the meaning.
-			 */
-			cv = getcvec(v, 0, 3);
-			if (cv)
-			{
-				addrange(cv, '0', '9');
-				addrange(cv, 'a', 'f');
-				addrange(cv, 'A', 'F');
-			}
-			break;
-		case CC_SPACE:
-			cv = pg_ctype_get_cache(pg_wc_isspace);
-			break;
-		case CC_LOWER:
-			cv = pg_ctype_get_cache(pg_wc_islower);
-			break;
-		case CC_UPPER:
-			cv = pg_ctype_get_cache(pg_wc_isupper);
-			break;
-		case CC_GRAPH:
-			cv = pg_ctype_get_cache(pg_wc_isgraph);
-			break;
-	}
-
-	/* If cv is NULL now, the reason must be "out of memory" */
-	if (cv == NULL)
-		ERR(REG_ESPACE);
-	return cv;
-}
-
-/*
- * allcases - supply cvec for all case counterparts of a chr (including itself)
- *
- * This is a shortcut, preferably an efficient one, for simple characters;
- * messy cases are done via range().
- */
-static struct cvec *
-allcases(struct vars * v,		/* context */
-		 chr pc)				/* character to get case equivs of */
-{
-	struct cvec *cv;
-	chr			c = (chr) pc;
-	chr			lc,
-				uc;
-
-	lc = pg_wc_tolower((chr) c);
-	uc = pg_wc_toupper((chr) c);
-
-	cv = getcvec(v, 2, 0);
-	addchr(cv, lc);
-	if (lc != uc)
-		addchr(cv, uc);
-	return cv;
-}
-
-/*
- * cmp - chr-substring compare
- *
- * Backrefs need this.  It should preferably be efficient.
- * Note that it does not need to report anything except equal/unequal.
- * Note also that the length is exact, and the comparison should not
- * stop at embedded NULs!
- */
-static int						/* 0 for equal, nonzero for unequal */
-cmp(const chr *x, const chr *y, /* strings to compare */
-	size_t len)					/* exact length of comparison */
-{
-	return memcmp(VS(x), VS(y), len * sizeof(chr));
-}
-
-/*
- * casecmp - case-independent chr-substring compare
- *
- * REG_ICASE backrefs need this.  It should preferably be efficient.
- * Note that it does not need to report anything except equal/unequal.
- * Note also that the length is exact, and the comparison should not
- * stop at embedded NULs!
- */
-static int						/* 0 for equal, nonzero for unequal */
-casecmp(const chr *x, const chr *y,		/* strings to compare */
-		size_t len)				/* exact length of comparison */
-{
-	for (; len > 0; len--, x++, y++)
-	{
-		if ((*x != *y) && (pg_wc_tolower(*x) != pg_wc_tolower(*y)))
-			return 1;
-	}
-	return 0;
-}
diff --git a/src/backend/regex/regc_nfa.c b/src/backend/regex/regc_nfa.c
deleted file mode 100644
index cd9a323..0000000
--- a/src/backend/regex/regc_nfa.c
+++ /dev/null
@@ -1,3181 +0,0 @@
-/*
- * NFA utilities.
- * This file is #included by regcomp.c.
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regc_nfa.c
- *
- *
- * One or two things that technically ought to be in here
- * are actually in color.c, thanks to some incestuous relationships in
- * the color chains.
- */
-
-#define NISERR()	VISERR(nfa->v)
-#define NERR(e)		VERR(nfa->v, (e))
-
-
-/*
- * newnfa - set up an NFA
- */
-static struct nfa *				/* the NFA, or NULL */
-newnfa(struct vars * v,
-	   struct colormap * cm,
-	   struct nfa * parent)		/* NULL if primary NFA */
-{
-	struct nfa *nfa;
-
-	nfa = (struct nfa *) MALLOC(sizeof(struct nfa));
-	if (nfa == NULL)
-	{
-		ERR(REG_ESPACE);
-		return NULL;
-	}
-
-	nfa->states = NULL;
-	nfa->slast = NULL;
-	nfa->free = NULL;
-	nfa->nstates = 0;
-	nfa->cm = cm;
-	nfa->v = v;
-	nfa->bos[0] = nfa->bos[1] = COLORLESS;
-	nfa->eos[0] = nfa->eos[1] = COLORLESS;
-	nfa->parent = parent;		/* Precedes newfstate so parent is valid. */
-	nfa->post = newfstate(nfa, '@');	/* number 0 */
-	nfa->pre = newfstate(nfa, '>');		/* number 1 */
-
-	nfa->init = newstate(nfa);	/* may become invalid later */
-	nfa->final = newstate(nfa);
-	if (ISERR())
-	{
-		freenfa(nfa);
-		return NULL;
-	}
-	rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->pre, nfa->init);
-	newarc(nfa, '^', 1, nfa->pre, nfa->init);
-	newarc(nfa, '^', 0, nfa->pre, nfa->init);
-	rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->final, nfa->post);
-	newarc(nfa, '$', 1, nfa->final, nfa->post);
-	newarc(nfa, '$', 0, nfa->final, nfa->post);
-
-	if (ISERR())
-	{
-		freenfa(nfa);
-		return NULL;
-	}
-	return nfa;
-}
-
-/*
- * freenfa - free an entire NFA
- */
-static void
-freenfa(struct nfa * nfa)
-{
-	struct state *s;
-
-	while ((s = nfa->states) != NULL)
-	{
-		s->nins = s->nouts = 0; /* don't worry about arcs */
-		freestate(nfa, s);
-	}
-	while ((s = nfa->free) != NULL)
-	{
-		nfa->free = s->next;
-		destroystate(nfa, s);
-	}
-
-	nfa->slast = NULL;
-	nfa->nstates = -1;
-	nfa->pre = NULL;
-	nfa->post = NULL;
-	FREE(nfa);
-}
-
-/*
- * newstate - allocate an NFA state, with zero flag value
- */
-static struct state *			/* NULL on error */
-newstate(struct nfa * nfa)
-{
-	struct state *s;
-
-	/*
-	 * This is a handy place to check for operation cancel during regex
-	 * compilation, since no code path will go very long without making a new
-	 * state or arc.
-	 */
-	if (CANCEL_REQUESTED(nfa->v->re))
-	{
-		NERR(REG_CANCEL);
-		return NULL;
-	}
-
-	if (nfa->free != NULL)
-	{
-		s = nfa->free;
-		nfa->free = s->next;
-	}
-	else
-	{
-		if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE)
-		{
-			NERR(REG_ETOOBIG);
-			return NULL;
-		}
-		s = (struct state *) MALLOC(sizeof(struct state));
-		if (s == NULL)
-		{
-			NERR(REG_ESPACE);
-			return NULL;
-		}
-		nfa->v->spaceused += sizeof(struct state);
-		s->oas.next = NULL;
-		s->free = NULL;
-		s->noas = 0;
-	}
-
-	assert(nfa->nstates >= 0);
-	s->no = nfa->nstates++;
-	s->flag = 0;
-	if (nfa->states == NULL)
-		nfa->states = s;
-	s->nins = 0;
-	s->ins = NULL;
-	s->nouts = 0;
-	s->outs = NULL;
-	s->tmp = NULL;
-	s->next = NULL;
-	if (nfa->slast != NULL)
-	{
-		assert(nfa->slast->next == NULL);
-		nfa->slast->next = s;
-	}
-	s->prev = nfa->slast;
-	nfa->slast = s;
-	return s;
-}
-
-/*
- * newfstate - allocate an NFA state with a specified flag value
- */
-static struct state *			/* NULL on error */
-newfstate(struct nfa * nfa, int flag)
-{
-	struct state *s;
-
-	s = newstate(nfa);
-	if (s != NULL)
-		s->flag = (char) flag;
-	return s;
-}
-
-/*
- * dropstate - delete a state's inarcs and outarcs and free it
- */
-static void
-dropstate(struct nfa * nfa,
-		  struct state * s)
-{
-	struct arc *a;
-
-	while ((a = s->ins) != NULL)
-		freearc(nfa, a);
-	while ((a = s->outs) != NULL)
-		freearc(nfa, a);
-	freestate(nfa, s);
-}
-
-/*
- * freestate - free a state, which has no in-arcs or out-arcs
- */
-static void
-freestate(struct nfa * nfa,
-		  struct state * s)
-{
-	assert(s != NULL);
-	assert(s->nins == 0 && s->nouts == 0);
-
-	s->no = FREESTATE;
-	s->flag = 0;
-	if (s->next != NULL)
-		s->next->prev = s->prev;
-	else
-	{
-		assert(s == nfa->slast);
-		nfa->slast = s->prev;
-	}
-	if (s->prev != NULL)
-		s->prev->next = s->next;
-	else
-	{
-		assert(s == nfa->states);
-		nfa->states = s->next;
-	}
-	s->prev = NULL;
-	s->next = nfa->free;		/* don't delete it, put it on the free list */
-	nfa->free = s;
-}
-
-/*
- * destroystate - really get rid of an already-freed state
- */
-static void
-destroystate(struct nfa * nfa,
-			 struct state * s)
-{
-	struct arcbatch *ab;
-	struct arcbatch *abnext;
-
-	assert(s->no == FREESTATE);
-	for (ab = s->oas.next; ab != NULL; ab = abnext)
-	{
-		abnext = ab->next;
-		FREE(ab);
-		nfa->v->spaceused -= sizeof(struct arcbatch);
-	}
-	s->ins = NULL;
-	s->outs = NULL;
-	s->next = NULL;
-	FREE(s);
-	nfa->v->spaceused -= sizeof(struct state);
-}
-
-/*
- * newarc - set up a new arc within an NFA
- *
- * This function checks to make sure that no duplicate arcs are created.
- * In general we never want duplicates.
- */
-static void
-newarc(struct nfa * nfa,
-	   int t,
-	   pcolor co,
-	   struct state * from,
-	   struct state * to)
-{
-	struct arc *a;
-
-	assert(from != NULL && to != NULL);
-
-	/*
-	 * This is a handy place to check for operation cancel during regex
-	 * compilation, since no code path will go very long without making a new
-	 * state or arc.
-	 */
-	if (CANCEL_REQUESTED(nfa->v->re))
-	{
-		NERR(REG_CANCEL);
-		return;
-	}
-
-	/* check for duplicate arc, using whichever chain is shorter */
-	if (from->nouts <= to->nins)
-	{
-		for (a = from->outs; a != NULL; a = a->outchain)
-			if (a->to == to && a->co == co && a->type == t)
-				return;
-	}
-	else
-	{
-		for (a = to->ins; a != NULL; a = a->inchain)
-			if (a->from == from && a->co == co && a->type == t)
-				return;
-	}
-
-	/* no dup, so create the arc */
-	createarc(nfa, t, co, from, to);
-}
-
-/*
- * createarc - create a new arc within an NFA
- *
- * This function must *only* be used after verifying that there is no existing
- * identical arc (same type/color/from/to).
- */
-static void
-createarc(struct nfa * nfa,
-		  int t,
-		  pcolor co,
-		  struct state * from,
-		  struct state * to)
-{
-	struct arc *a;
-
-	/* the arc is physically allocated within its from-state */
-	a = allocarc(nfa, from);
-	if (NISERR())
-		return;
-	assert(a != NULL);
-
-	a->type = t;
-	a->co = (color) co;
-	a->to = to;
-	a->from = from;
-
-	/*
-	 * Put the new arc on the beginning, not the end, of the chains; it's
-	 * simpler here, and freearc() is the same cost either way.  See also the
-	 * logic in moveins() and its cohorts, as well as fixempties().
-	 */
-	a->inchain = to->ins;
-	a->inchainRev = NULL;
-	if (to->ins)
-		to->ins->inchainRev = a;
-	to->ins = a;
-	a->outchain = from->outs;
-	a->outchainRev = NULL;
-	if (from->outs)
-		from->outs->outchainRev = a;
-	from->outs = a;
-
-	from->nouts++;
-	to->nins++;
-
-	if (COLORED(a) && nfa->parent == NULL)
-		colorchain(nfa->cm, a);
-}
-
-/*
- * allocarc - allocate a new out-arc within a state
- */
-static struct arc *				/* NULL for failure */
-allocarc(struct nfa * nfa,
-		 struct state * s)
-{
-	struct arc *a;
-
-	/* shortcut */
-	if (s->free == NULL && s->noas < ABSIZE)
-	{
-		a = &s->oas.a[s->noas];
-		s->noas++;
-		return a;
-	}
-
-	/* if none at hand, get more */
-	if (s->free == NULL)
-	{
-		struct arcbatch *newAb;
-		int			i;
-
-		if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE)
-		{
-			NERR(REG_ETOOBIG);
-			return NULL;
-		}
-		newAb = (struct arcbatch *) MALLOC(sizeof(struct arcbatch));
-		if (newAb == NULL)
-		{
-			NERR(REG_ESPACE);
-			return NULL;
-		}
-		nfa->v->spaceused += sizeof(struct arcbatch);
-		newAb->next = s->oas.next;
-		s->oas.next = newAb;
-
-		for (i = 0; i < ABSIZE; i++)
-		{
-			newAb->a[i].type = 0;
-			newAb->a[i].freechain = &newAb->a[i + 1];
-		}
-		newAb->a[ABSIZE - 1].freechain = NULL;
-		s->free = &newAb->a[0];
-	}
-	assert(s->free != NULL);
-
-	a = s->free;
-	s->free = a->freechain;
-	return a;
-}
-
-/*
- * freearc - free an arc
- */
-static void
-freearc(struct nfa * nfa,
-		struct arc * victim)
-{
-	struct state *from = victim->from;
-	struct state *to = victim->to;
-	struct arc *predecessor;
-
-	assert(victim->type != 0);
-
-	/* take it off color chain if necessary */
-	if (COLORED(victim) && nfa->parent == NULL)
-		uncolorchain(nfa->cm, victim);
-
-	/* take it off source's out-chain */
-	assert(from != NULL);
-	predecessor = victim->outchainRev;
-	if (predecessor == NULL)
-	{
-		assert(from->outs == victim);
-		from->outs = victim->outchain;
-	}
-	else
-	{
-		assert(predecessor->outchain == victim);
-		predecessor->outchain = victim->outchain;
-	}
-	if (victim->outchain != NULL)
-	{
-		assert(victim->outchain->outchainRev == victim);
-		victim->outchain->outchainRev = predecessor;
-	}
-	from->nouts--;
-
-	/* take it off target's in-chain */
-	assert(to != NULL);
-	predecessor = victim->inchainRev;
-	if (predecessor == NULL)
-	{
-		assert(to->ins == victim);
-		to->ins = victim->inchain;
-	}
-	else
-	{
-		assert(predecessor->inchain == victim);
-		predecessor->inchain = victim->inchain;
-	}
-	if (victim->inchain != NULL)
-	{
-		assert(victim->inchain->inchainRev == victim);
-		victim->inchain->inchainRev = predecessor;
-	}
-	to->nins--;
-
-	/* clean up and place on from-state's free list */
-	victim->type = 0;
-	victim->from = NULL;		/* precautions... */
-	victim->to = NULL;
-	victim->inchain = NULL;
-	victim->inchainRev = NULL;
-	victim->outchain = NULL;
-	victim->outchainRev = NULL;
-	victim->freechain = from->free;
-	from->free = victim;
-}
-
-/*
- * changearctarget - flip an arc to have a different to state
- *
- * Caller must have verified that there is no pre-existing duplicate arc.
- *
- * Note that because we store arcs in their from state, we can't easily have
- * a similar changearcsource function.
- */
-static void
-changearctarget(struct arc * a, struct state * newto)
-{
-	struct state *oldto = a->to;
-	struct arc *predecessor;
-
-	assert(oldto != newto);
-
-	/* take it off old target's in-chain */
-	assert(oldto != NULL);
-	predecessor = a->inchainRev;
-	if (predecessor == NULL)
-	{
-		assert(oldto->ins == a);
-		oldto->ins = a->inchain;
-	}
-	else
-	{
-		assert(predecessor->inchain == a);
-		predecessor->inchain = a->inchain;
-	}
-	if (a->inchain != NULL)
-	{
-		assert(a->inchain->inchainRev == a);
-		a->inchain->inchainRev = predecessor;
-	}
-	oldto->nins--;
-
-	a->to = newto;
-
-	/* prepend it to new target's in-chain */
-	a->inchain = newto->ins;
-	a->inchainRev = NULL;
-	if (newto->ins)
-		newto->ins->inchainRev = a;
-	newto->ins = a;
-	newto->nins++;
-}
-
-/*
- * hasnonemptyout - Does state have a non-EMPTY out arc?
- */
-static int
-hasnonemptyout(struct state * s)
-{
-	struct arc *a;
-
-	for (a = s->outs; a != NULL; a = a->outchain)
-	{
-		if (a->type != EMPTY)
-			return 1;
-	}
-	return 0;
-}
-
-/*
- * findarc - find arc, if any, from given source with given type and color
- * If there is more than one such arc, the result is random.
- */
-static struct arc *
-findarc(struct state * s,
-		int type,
-		pcolor co)
-{
-	struct arc *a;
-
-	for (a = s->outs; a != NULL; a = a->outchain)
-		if (a->type == type && a->co == co)
-			return a;
-	return NULL;
-}
-
-/*
- * cparc - allocate a new arc within an NFA, copying details from old one
- */
-static void
-cparc(struct nfa * nfa,
-	  struct arc * oa,
-	  struct state * from,
-	  struct state * to)
-{
-	newarc(nfa, oa->type, oa->co, from, to);
-}
-
-/*
- * sortins - sort the in arcs of a state by from/color/type
- */
-static void
-sortins(struct nfa * nfa,
-		struct state * s)
-{
-	struct arc **sortarray;
-	struct arc *a;
-	int			n = s->nins;
-	int			i;
-
-	if (n <= 1)
-		return;					/* nothing to do */
-	/* make an array of arc pointers ... */
-	sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *));
-	if (sortarray == NULL)
-	{
-		NERR(REG_ESPACE);
-		return;
-	}
-	i = 0;
-	for (a = s->ins; a != NULL; a = a->inchain)
-		sortarray[i++] = a;
-	assert(i == n);
-	/* ... sort the array */
-	qsort(sortarray, n, sizeof(struct arc *), sortins_cmp);
-	/* ... and rebuild arc list in order */
-	/* it seems worth special-casing first and last items to simplify loop */
-	a = sortarray[0];
-	s->ins = a;
-	a->inchain = sortarray[1];
-	a->inchainRev = NULL;
-	for (i = 1; i < n - 1; i++)
-	{
-		a = sortarray[i];
-		a->inchain = sortarray[i + 1];
-		a->inchainRev = sortarray[i - 1];
-	}
-	a = sortarray[i];
-	a->inchain = NULL;
-	a->inchainRev = sortarray[i - 1];
-	FREE(sortarray);
-}
-
-static int
-sortins_cmp(const void *a, const void *b)
-{
-	const struct arc *aa = *((const struct arc * const *) a);
-	const struct arc *bb = *((const struct arc * const *) b);
-
-	/* we check the fields in the order they are most likely to be different */
-	if (aa->from->no < bb->from->no)
-		return -1;
-	if (aa->from->no > bb->from->no)
-		return 1;
-	if (aa->co < bb->co)
-		return -1;
-	if (aa->co > bb->co)
-		return 1;
-	if (aa->type < bb->type)
-		return -1;
-	if (aa->type > bb->type)
-		return 1;
-	return 0;
-}
-
-/*
- * sortouts - sort the out arcs of a state by to/color/type
- */
-static void
-sortouts(struct nfa * nfa,
-		 struct state * s)
-{
-	struct arc **sortarray;
-	struct arc *a;
-	int			n = s->nouts;
-	int			i;
-
-	if (n <= 1)
-		return;					/* nothing to do */
-	/* make an array of arc pointers ... */
-	sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *));
-	if (sortarray == NULL)
-	{
-		NERR(REG_ESPACE);
-		return;
-	}
-	i = 0;
-	for (a = s->outs; a != NULL; a = a->outchain)
-		sortarray[i++] = a;
-	assert(i == n);
-	/* ... sort the array */
-	qsort(sortarray, n, sizeof(struct arc *), sortouts_cmp);
-	/* ... and rebuild arc list in order */
-	/* it seems worth special-casing first and last items to simplify loop */
-	a = sortarray[0];
-	s->outs = a;
-	a->outchain = sortarray[1];
-	a->outchainRev = NULL;
-	for (i = 1; i < n - 1; i++)
-	{
-		a = sortarray[i];
-		a->outchain = sortarray[i + 1];
-		a->outchainRev = sortarray[i - 1];
-	}
-	a = sortarray[i];
-	a->outchain = NULL;
-	a->outchainRev = sortarray[i - 1];
-	FREE(sortarray);
-}
-
-static int
-sortouts_cmp(const void *a, const void *b)
-{
-	const struct arc *aa = *((const struct arc * const *) a);
-	const struct arc *bb = *((const struct arc * const *) b);
-
-	/* we check the fields in the order they are most likely to be different */
-	if (aa->to->no < bb->to->no)
-		return -1;
-	if (aa->to->no > bb->to->no)
-		return 1;
-	if (aa->co < bb->co)
-		return -1;
-	if (aa->co > bb->co)
-		return 1;
-	if (aa->type < bb->type)
-		return -1;
-	if (aa->type > bb->type)
-		return 1;
-	return 0;
-}
-
-/*
- * Common decision logic about whether to use arc-by-arc operations or
- * sort/merge.  If there's just a few source arcs we cannot recoup the
- * cost of sorting the destination arc list, no matter how large it is.
- * Otherwise, limit the number of arc-by-arc comparisons to about 1000
- * (a somewhat arbitrary choice, but the breakeven point would probably
- * be machine dependent anyway).
- */
-#define BULK_ARC_OP_USE_SORT(nsrcarcs, ndestarcs) \
-	((nsrcarcs) < 4 ? 0 : ((nsrcarcs) > 32 || (ndestarcs) > 32))
-
-/*
- * moveins - move all in arcs of a state to another state
- *
- * You might think this could be done better by just updating the
- * existing arcs, and you would be right if it weren't for the need
- * for duplicate suppression, which makes it easier to just make new
- * ones to exploit the suppression built into newarc.
- *
- * However, if we have a whole lot of arcs to deal with, retail duplicate
- * checks become too slow.  In that case we proceed by sorting and merging
- * the arc lists, and then we can indeed just update the arcs in-place.
- */
-static void
-moveins(struct nfa * nfa,
-		struct state * oldState,
-		struct state * newState)
-{
-	assert(oldState != newState);
-
-	if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins))
-	{
-		/* With not too many arcs, just do them one at a time */
-		struct arc *a;
-
-		while ((a = oldState->ins) != NULL)
-		{
-			cparc(nfa, a, a->from, newState);
-			freearc(nfa, a);
-		}
-	}
-	else
-	{
-		/*
-		 * With many arcs, use a sort-merge approach.  Note changearctarget()
-		 * will put the arc onto the front of newState's chain, so it does not
-		 * break our walk through the sorted part of the chain.
-		 */
-		struct arc *oa;
-		struct arc *na;
-
-		/*
-		 * Because we bypass newarc() in this code path, we'd better include a
-		 * cancel check.
-		 */
-		if (CANCEL_REQUESTED(nfa->v->re))
-		{
-			NERR(REG_CANCEL);
-			return;
-		}
-
-		sortins(nfa, oldState);
-		sortins(nfa, newState);
-		if (NISERR())
-			return;				/* might have failed to sort */
-		oa = oldState->ins;
-		na = newState->ins;
-		while (oa != NULL && na != NULL)
-		{
-			struct arc *a = oa;
-
-			switch (sortins_cmp(&oa, &na))
-			{
-				case -1:
-					/* newState does not have anything matching oa */
-					oa = oa->inchain;
-
-					/*
-					 * Rather than doing createarc+freearc, we can just unlink
-					 * and relink the existing arc struct.
-					 */
-					changearctarget(a, newState);
-					break;
-				case 0:
-					/* match, advance in both lists */
-					oa = oa->inchain;
-					na = na->inchain;
-					/* ... and drop duplicate arc from oldState */
-					freearc(nfa, a);
-					break;
-				case +1:
-					/* advance only na; oa might have a match later */
-					na = na->inchain;
-					break;
-				default:
-					assert(NOTREACHED);
-			}
-		}
-		while (oa != NULL)
-		{
-			/* newState does not have anything matching oa */
-			struct arc *a = oa;
-
-			oa = oa->inchain;
-			changearctarget(a, newState);
-		}
-	}
-
-	assert(oldState->nins == 0);
-	assert(oldState->ins == NULL);
-}
-
-/*
- * copyins - copy in arcs of a state to another state
- */
-static void
-copyins(struct nfa * nfa,
-		struct state * oldState,
-		struct state * newState)
-{
-	assert(oldState != newState);
-
-	if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins))
-	{
-		/* With not too many arcs, just do them one at a time */
-		struct arc *a;
-
-		for (a = oldState->ins; a != NULL; a = a->inchain)
-			cparc(nfa, a, a->from, newState);
-	}
-	else
-	{
-		/*
-		 * With many arcs, use a sort-merge approach.  Note that createarc()
-		 * will put new arcs onto the front of newState's chain, so it does
-		 * not break our walk through the sorted part of the chain.
-		 */
-		struct arc *oa;
-		struct arc *na;
-
-		/*
-		 * Because we bypass newarc() in this code path, we'd better include a
-		 * cancel check.
-		 */
-		if (CANCEL_REQUESTED(nfa->v->re))
-		{
-			NERR(REG_CANCEL);
-			return;
-		}
-
-		sortins(nfa, oldState);
-		sortins(nfa, newState);
-		if (NISERR())
-			return;				/* might have failed to sort */
-		oa = oldState->ins;
-		na = newState->ins;
-		while (oa != NULL && na != NULL)
-		{
-			struct arc *a = oa;
-
-			switch (sortins_cmp(&oa, &na))
-			{
-				case -1:
-					/* newState does not have anything matching oa */
-					oa = oa->inchain;
-					createarc(nfa, a->type, a->co, a->from, newState);
-					break;
-				case 0:
-					/* match, advance in both lists */
-					oa = oa->inchain;
-					na = na->inchain;
-					break;
-				case +1:
-					/* advance only na; oa might have a match later */
-					na = na->inchain;
-					break;
-				default:
-					assert(NOTREACHED);
-			}
-		}
-		while (oa != NULL)
-		{
-			/* newState does not have anything matching oa */
-			struct arc *a = oa;
-
-			oa = oa->inchain;
-			createarc(nfa, a->type, a->co, a->from, newState);
-		}
-	}
-}
-
-/*
- * mergeins - merge a list of inarcs into a state
- *
- * This is much like copyins, but the source arcs are listed in an array,
- * and are not guaranteed unique.  It's okay to clobber the array contents.
- */
-static void
-mergeins(struct nfa * nfa,
-		 struct state * s,
-		 struct arc ** arcarray,
-		 int arccount)
-{
-	struct arc *na;
-	int			i;
-	int			j;
-
-	if (arccount <= 0)
-		return;
-
-	/*
-	 * Because we bypass newarc() in this code path, we'd better include a
-	 * cancel check.
-	 */
-	if (CANCEL_REQUESTED(nfa->v->re))
-	{
-		NERR(REG_CANCEL);
-		return;
-	}
-
-	/* Sort existing inarcs as well as proposed new ones */
-	sortins(nfa, s);
-	if (NISERR())
-		return;					/* might have failed to sort */
-
-	qsort(arcarray, arccount, sizeof(struct arc *), sortins_cmp);
-
-	/*
-	 * arcarray very likely includes dups, so we must eliminate them.  (This
-	 * could be folded into the next loop, but it's not worth the trouble.)
-	 */
-	j = 0;
-	for (i = 1; i < arccount; i++)
-	{
-		switch (sortins_cmp(&arcarray[j], &arcarray[i]))
-		{
-			case -1:
-				/* non-dup */
-				arcarray[++j] = arcarray[i];
-				break;
-			case 0:
-				/* dup */
-				break;
-			default:
-				/* trouble */
-				assert(NOTREACHED);
-		}
-	}
-	arccount = j + 1;
-
-	/*
-	 * Now merge into s' inchain.  Note that createarc() will put new arcs
-	 * onto the front of s's chain, so it does not break our walk through the
-	 * sorted part of the chain.
-	 */
-	i = 0;
-	na = s->ins;
-	while (i < arccount && na != NULL)
-	{
-		struct arc *a = arcarray[i];
-
-		switch (sortins_cmp(&a, &na))
-		{
-			case -1:
-				/* s does not have anything matching a */
-				createarc(nfa, a->type, a->co, a->from, s);
-				i++;
-				break;
-			case 0:
-				/* match, advance in both lists */
-				i++;
-				na = na->inchain;
-				break;
-			case +1:
-				/* advance only na; array might have a match later */
-				na = na->inchain;
-				break;
-			default:
-				assert(NOTREACHED);
-		}
-	}
-	while (i < arccount)
-	{
-		/* s does not have anything matching a */
-		struct arc *a = arcarray[i];
-
-		createarc(nfa, a->type, a->co, a->from, s);
-		i++;
-	}
-}
-
-/*
- * moveouts - move all out arcs of a state to another state
- */
-static void
-moveouts(struct nfa * nfa,
-		 struct state * oldState,
-		 struct state * newState)
-{
-	assert(oldState != newState);
-
-	if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts))
-	{
-		/* With not too many arcs, just do them one at a time */
-		struct arc *a;
-
-		while ((a = oldState->outs) != NULL)
-		{
-			cparc(nfa, a, newState, a->to);
-			freearc(nfa, a);
-		}
-	}
-	else
-	{
-		/*
-		 * With many arcs, use a sort-merge approach.  Note that createarc()
-		 * will put new arcs onto the front of newState's chain, so it does
-		 * not break our walk through the sorted part of the chain.
-		 */
-		struct arc *oa;
-		struct arc *na;
-
-		/*
-		 * Because we bypass newarc() in this code path, we'd better include a
-		 * cancel check.
-		 */
-		if (CANCEL_REQUESTED(nfa->v->re))
-		{
-			NERR(REG_CANCEL);
-			return;
-		}
-
-		sortouts(nfa, oldState);
-		sortouts(nfa, newState);
-		if (NISERR())
-			return;				/* might have failed to sort */
-		oa = oldState->outs;
-		na = newState->outs;
-		while (oa != NULL && na != NULL)
-		{
-			struct arc *a = oa;
-
-			switch (sortouts_cmp(&oa, &na))
-			{
-				case -1:
-					/* newState does not have anything matching oa */
-					oa = oa->outchain;
-					createarc(nfa, a->type, a->co, newState, a->to);
-					freearc(nfa, a);
-					break;
-				case 0:
-					/* match, advance in both lists */
-					oa = oa->outchain;
-					na = na->outchain;
-					/* ... and drop duplicate arc from oldState */
-					freearc(nfa, a);
-					break;
-				case +1:
-					/* advance only na; oa might have a match later */
-					na = na->outchain;
-					break;
-				default:
-					assert(NOTREACHED);
-			}
-		}
-		while (oa != NULL)
-		{
-			/* newState does not have anything matching oa */
-			struct arc *a = oa;
-
-			oa = oa->outchain;
-			createarc(nfa, a->type, a->co, newState, a->to);
-			freearc(nfa, a);
-		}
-	}
-
-	assert(oldState->nouts == 0);
-	assert(oldState->outs == NULL);
-}
-
-/*
- * copyouts - copy out arcs of a state to another state
- */
-static void
-copyouts(struct nfa * nfa,
-		 struct state * oldState,
-		 struct state * newState)
-{
-	assert(oldState != newState);
-
-	if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts))
-	{
-		/* With not too many arcs, just do them one at a time */
-		struct arc *a;
-
-		for (a = oldState->outs; a != NULL; a = a->outchain)
-			cparc(nfa, a, newState, a->to);
-	}
-	else
-	{
-		/*
-		 * With many arcs, use a sort-merge approach.  Note that createarc()
-		 * will put new arcs onto the front of newState's chain, so it does
-		 * not break our walk through the sorted part of the chain.
-		 */
-		struct arc *oa;
-		struct arc *na;
-
-		/*
-		 * Because we bypass newarc() in this code path, we'd better include a
-		 * cancel check.
-		 */
-		if (CANCEL_REQUESTED(nfa->v->re))
-		{
-			NERR(REG_CANCEL);
-			return;
-		}
-
-		sortouts(nfa, oldState);
-		sortouts(nfa, newState);
-		if (NISERR())
-			return;				/* might have failed to sort */
-		oa = oldState->outs;
-		na = newState->outs;
-		while (oa != NULL && na != NULL)
-		{
-			struct arc *a = oa;
-
-			switch (sortouts_cmp(&oa, &na))
-			{
-				case -1:
-					/* newState does not have anything matching oa */
-					oa = oa->outchain;
-					createarc(nfa, a->type, a->co, newState, a->to);
-					break;
-				case 0:
-					/* match, advance in both lists */
-					oa = oa->outchain;
-					na = na->outchain;
-					break;
-				case +1:
-					/* advance only na; oa might have a match later */
-					na = na->outchain;
-					break;
-				default:
-					assert(NOTREACHED);
-			}
-		}
-		while (oa != NULL)
-		{
-			/* newState does not have anything matching oa */
-			struct arc *a = oa;
-
-			oa = oa->outchain;
-			createarc(nfa, a->type, a->co, newState, a->to);
-		}
-	}
-}
-
-/*
- * cloneouts - copy out arcs of a state to another state pair, modifying type
- */
-static void
-cloneouts(struct nfa * nfa,
-		  struct state * old,
-		  struct state * from,
-		  struct state * to,
-		  int type)
-{
-	struct arc *a;
-
-	assert(old != from);
-
-	for (a = old->outs; a != NULL; a = a->outchain)
-		newarc(nfa, type, a->co, from, to);
-}
-
-/*
- * delsub - delete a sub-NFA, updating subre pointers if necessary
- *
- * This uses a recursive traversal of the sub-NFA, marking already-seen
- * states using their tmp pointer.
- */
-static void
-delsub(struct nfa * nfa,
-	   struct state * lp,		/* the sub-NFA goes from here... */
-	   struct state * rp)		/* ...to here, *not* inclusive */
-{
-	assert(lp != rp);
-
-	rp->tmp = rp;				/* mark end */
-
-	deltraverse(nfa, lp, lp);
-	if (NISERR())
-		return;					/* asserts might not hold after failure */
-	assert(lp->nouts == 0 && rp->nins == 0);	/* did the job */
-	assert(lp->no != FREESTATE && rp->no != FREESTATE); /* no more */
-
-	rp->tmp = NULL;				/* unmark end */
-	lp->tmp = NULL;				/* and begin, marked by deltraverse */
-}
-
-/*
- * deltraverse - the recursive heart of delsub
- * This routine's basic job is to destroy all out-arcs of the state.
- */
-static void
-deltraverse(struct nfa * nfa,
-			struct state * leftend,
-			struct state * s)
-{
-	struct arc *a;
-	struct state *to;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	if (s->nouts == 0)
-		return;					/* nothing to do */
-	if (s->tmp != NULL)
-		return;					/* already in progress */
-
-	s->tmp = s;					/* mark as in progress */
-
-	while ((a = s->outs) != NULL)
-	{
-		to = a->to;
-		deltraverse(nfa, leftend, to);
-		if (NISERR())
-			return;				/* asserts might not hold after failure */
-		assert(to->nouts == 0 || to->tmp != NULL);
-		freearc(nfa, a);
-		if (to->nins == 0 && to->tmp == NULL)
-		{
-			assert(to->nouts == 0);
-			freestate(nfa, to);
-		}
-	}
-
-	assert(s->no != FREESTATE); /* we're still here */
-	assert(s == leftend || s->nins != 0);		/* and still reachable */
-	assert(s->nouts == 0);		/* but have no outarcs */
-
-	s->tmp = NULL;				/* we're done here */
-}
-
-/*
- * dupnfa - duplicate sub-NFA
- *
- * Another recursive traversal, this time using tmp to point to duplicates
- * as well as mark already-seen states.  (You knew there was a reason why
- * it's a state pointer, didn't you? :-))
- */
-static void
-dupnfa(struct nfa * nfa,
-	   struct state * start,	/* duplicate of subNFA starting here */
-	   struct state * stop,		/* and stopping here */
-	   struct state * from,		/* stringing duplicate from here */
-	   struct state * to)		/* to here */
-{
-	if (start == stop)
-	{
-		newarc(nfa, EMPTY, 0, from, to);
-		return;
-	}
-
-	stop->tmp = to;
-	duptraverse(nfa, start, from);
-	/* done, except for clearing out the tmp pointers */
-
-	stop->tmp = NULL;
-	cleartraverse(nfa, start);
-}
-
-/*
- * duptraverse - recursive heart of dupnfa
- */
-static void
-duptraverse(struct nfa * nfa,
-			struct state * s,
-			struct state * stmp)	/* s's duplicate, or NULL */
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	if (s->tmp != NULL)
-		return;					/* already done */
-
-	s->tmp = (stmp == NULL) ? newstate(nfa) : stmp;
-	if (s->tmp == NULL)
-	{
-		assert(NISERR());
-		return;
-	}
-
-	for (a = s->outs; a != NULL && !NISERR(); a = a->outchain)
-	{
-		duptraverse(nfa, a->to, (struct state *) NULL);
-		if (NISERR())
-			break;
-		assert(a->to->tmp != NULL);
-		cparc(nfa, a, s->tmp, a->to->tmp);
-	}
-}
-
-/*
- * cleartraverse - recursive cleanup for algorithms that leave tmp ptrs set
- */
-static void
-cleartraverse(struct nfa * nfa,
-			  struct state * s)
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	if (s->tmp == NULL)
-		return;
-	s->tmp = NULL;
-
-	for (a = s->outs; a != NULL; a = a->outchain)
-		cleartraverse(nfa, a->to);
-}
-
-/*
- * single_color_transition - does getting from s1 to s2 cross one PLAIN arc?
- *
- * If traversing from s1 to s2 requires a single PLAIN match (possibly of any
- * of a set of colors), return a state whose outarc list contains only PLAIN
- * arcs of those color(s).  Otherwise return NULL.
- *
- * This is used before optimizing the NFA, so there may be EMPTY arcs, which
- * we should ignore; the possibility of an EMPTY is why the result state could
- * be different from s1.
- *
- * It's worth troubling to handle multiple parallel PLAIN arcs here because a
- * bracket construct such as [abc] might yield either one or several parallel
- * PLAIN arcs depending on earlier atoms in the expression.  We'd rather that
- * that implementation detail not create user-visible performance differences.
- */
-static struct state *
-single_color_transition(struct state * s1, struct state * s2)
-{
-	struct arc *a;
-
-	/* Ignore leading EMPTY arc, if any */
-	if (s1->nouts == 1 && s1->outs->type == EMPTY)
-		s1 = s1->outs->to;
-	/* Likewise for any trailing EMPTY arc */
-	if (s2->nins == 1 && s2->ins->type == EMPTY)
-		s2 = s2->ins->from;
-	/* Perhaps we could have a single-state loop in between, if so reject */
-	if (s1 == s2)
-		return NULL;
-	/* s1 must have at least one outarc... */
-	if (s1->outs == NULL)
-		return NULL;
-	/* ... and they must all be PLAIN arcs to s2 */
-	for (a = s1->outs; a != NULL; a = a->outchain)
-	{
-		if (a->type != PLAIN || a->to != s2)
-			return NULL;
-	}
-	/* OK, return s1 as the possessor of the relevant outarcs */
-	return s1;
-}
-
-/*
- * specialcolors - fill in special colors for an NFA
- */
-static void
-specialcolors(struct nfa * nfa)
-{
-	/* false colors for BOS, BOL, EOS, EOL */
-	if (nfa->parent == NULL)
-	{
-		nfa->bos[0] = pseudocolor(nfa->cm);
-		nfa->bos[1] = pseudocolor(nfa->cm);
-		nfa->eos[0] = pseudocolor(nfa->cm);
-		nfa->eos[1] = pseudocolor(nfa->cm);
-	}
-	else
-	{
-		assert(nfa->parent->bos[0] != COLORLESS);
-		nfa->bos[0] = nfa->parent->bos[0];
-		assert(nfa->parent->bos[1] != COLORLESS);
-		nfa->bos[1] = nfa->parent->bos[1];
-		assert(nfa->parent->eos[0] != COLORLESS);
-		nfa->eos[0] = nfa->parent->eos[0];
-		assert(nfa->parent->eos[1] != COLORLESS);
-		nfa->eos[1] = nfa->parent->eos[1];
-	}
-}
-
-/*
- * optimize - optimize an NFA
- *
- * The main goal of this function is not so much "optimization" (though it
- * does try to get rid of useless NFA states) as reducing the NFA to a form
- * the regex executor can handle.  The executor, and indeed the cNFA format
- * that is its input, can only handle PLAIN and LACON arcs.  The output of
- * the regex parser also includes EMPTY (do-nothing) arcs, as well as
- * ^, $, AHEAD, and BEHIND constraint arcs, which we must get rid of here.
- * We first get rid of EMPTY arcs and then deal with the constraint arcs.
- * The hardest part of either job is to get rid of circular loops of the
- * target arc type.  We would have to do that in any case, though, as such a
- * loop would otherwise allow the executor to cycle through the loop endlessly
- * without making any progress in the input string.
- */
-static long						/* re_info bits */
-optimize(struct nfa * nfa,
-		 FILE *f)				/* for debug output; NULL none */
-{
-#ifdef REG_DEBUG
-	int			verbose = (f != NULL) ? 1 : 0;
-
-	if (verbose)
-		fprintf(f, "\ninitial cleanup:\n");
-#endif
-	cleanup(nfa);				/* may simplify situation */
-#ifdef REG_DEBUG
-	if (verbose)
-		dumpnfa(nfa, f);
-	if (verbose)
-		fprintf(f, "\nempties:\n");
-#endif
-	fixempties(nfa, f);			/* get rid of EMPTY arcs */
-#ifdef REG_DEBUG
-	if (verbose)
-		fprintf(f, "\nconstraints:\n");
-#endif
-	fixconstraintloops(nfa, f); /* get rid of constraint loops */
-	pullback(nfa, f);			/* pull back constraints backward */
-	pushfwd(nfa, f);			/* push fwd constraints forward */
-#ifdef REG_DEBUG
-	if (verbose)
-		fprintf(f, "\nfinal cleanup:\n");
-#endif
-	cleanup(nfa);				/* final tidying */
-#ifdef REG_DEBUG
-	if (verbose)
-		dumpnfa(nfa, f);
-#endif
-	return analyze(nfa);		/* and analysis */
-}
-
-/*
- * pullback - pull back constraints backward to eliminate them
- */
-static void
-pullback(struct nfa * nfa,
-		 FILE *f)				/* for debug output; NULL none */
-{
-	struct state *s;
-	struct state *nexts;
-	struct arc *a;
-	struct arc *nexta;
-	struct state *intermediates;
-	int			progress;
-
-	/* find and pull until there are no more */
-	do
-	{
-		progress = 0;
-		for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-		{
-			nexts = s->next;
-			intermediates = NULL;
-			for (a = s->outs; a != NULL && !NISERR(); a = nexta)
-			{
-				nexta = a->outchain;
-				if (a->type == '^' || a->type == BEHIND)
-					if (pull(nfa, a, &intermediates))
-						progress = 1;
-			}
-			/* clear tmp fields of intermediate states created here */
-			while (intermediates != NULL)
-			{
-				struct state *ns = intermediates->tmp;
-
-				intermediates->tmp = NULL;
-				intermediates = ns;
-			}
-			/* if s is now useless, get rid of it */
-			if ((s->nins == 0 || s->nouts == 0) && !s->flag)
-				dropstate(nfa, s);
-		}
-		if (progress && f != NULL)
-			dumpnfa(nfa, f);
-	} while (progress && !NISERR());
-	if (NISERR())
-		return;
-
-	/*
-	 * Any ^ constraints we were able to pull to the start state can now be
-	 * replaced by PLAIN arcs referencing the BOS or BOL colors.  There should
-	 * be no other ^ or BEHIND arcs left in the NFA, though we do not check
-	 * that here (compact() will fail if so).
-	 */
-	for (a = nfa->pre->outs; a != NULL; a = nexta)
-	{
-		nexta = a->outchain;
-		if (a->type == '^')
-		{
-			assert(a->co == 0 || a->co == 1);
-			newarc(nfa, PLAIN, nfa->bos[a->co], a->from, a->to);
-			freearc(nfa, a);
-		}
-	}
-}
-
-/*
- * pull - pull a back constraint backward past its source state
- *
- * Returns 1 if successful (which it always is unless the source is the
- * start state or we have an internal error), 0 if nothing happened.
- *
- * A significant property of this function is that it deletes no pre-existing
- * states, and no outarcs of the constraint's from state other than the given
- * constraint arc.  This makes the loops in pullback() safe, at the cost that
- * we may leave useless states behind.  Therefore, we leave it to pullback()
- * to delete such states.
- *
- * If the from state has multiple back-constraint outarcs, and/or multiple
- * compatible constraint inarcs, we only need to create one new intermediate
- * state per combination of predecessor and successor states.  *intermediates
- * points to a list of such intermediate states for this from state (chained
- * through their tmp fields).
- */
-static int
-pull(struct nfa * nfa,
-	 struct arc * con,
-	 struct state ** intermediates)
-{
-	struct state *from = con->from;
-	struct state *to = con->to;
-	struct arc *a;
-	struct arc *nexta;
-	struct state *s;
-
-	assert(from != to);			/* should have gotten rid of this earlier */
-	if (from->flag)				/* can't pull back beyond start */
-		return 0;
-	if (from->nins == 0)
-	{							/* unreachable */
-		freearc(nfa, con);
-		return 1;
-	}
-
-	/*
-	 * First, clone from state if necessary to avoid other outarcs.  This may
-	 * seem wasteful, but it simplifies the logic, and we'll get rid of the
-	 * clone state again at the bottom.
-	 */
-	if (from->nouts > 1)
-	{
-		s = newstate(nfa);
-		if (NISERR())
-			return 0;
-		copyins(nfa, from, s);	/* duplicate inarcs */
-		cparc(nfa, con, s, to); /* move constraint arc */
-		freearc(nfa, con);
-		if (NISERR())
-			return 0;
-		from = s;
-		con = from->outs;
-	}
-	assert(from->nouts == 1);
-
-	/* propagate the constraint into the from state's inarcs */
-	for (a = from->ins; a != NULL && !NISERR(); a = nexta)
-	{
-		nexta = a->inchain;
-		switch (combine(con, a))
-		{
-			case INCOMPATIBLE:	/* destroy the arc */
-				freearc(nfa, a);
-				break;
-			case SATISFIED:		/* no action needed */
-				break;
-			case COMPATIBLE:	/* swap the two arcs, more or less */
-				/* need an intermediate state, but might have one already */
-				for (s = *intermediates; s != NULL; s = s->tmp)
-				{
-					assert(s->nins > 0 && s->nouts > 0);
-					if (s->ins->from == a->from && s->outs->to == to)
-						break;
-				}
-				if (s == NULL)
-				{
-					s = newstate(nfa);
-					if (NISERR())
-						return 0;
-					s->tmp = *intermediates;
-					*intermediates = s;
-				}
-				cparc(nfa, con, a->from, s);
-				cparc(nfa, a, s, to);
-				freearc(nfa, a);
-				break;
-			default:
-				assert(NOTREACHED);
-				break;
-		}
-	}
-
-	/* remaining inarcs, if any, incorporate the constraint */
-	moveins(nfa, from, to);
-	freearc(nfa, con);
-	/* from state is now useless, but we leave it to pullback() to clean up */
-	return 1;
-}
-
-/*
- * pushfwd - push forward constraints forward to eliminate them
- */
-static void
-pushfwd(struct nfa * nfa,
-		FILE *f)				/* for debug output; NULL none */
-{
-	struct state *s;
-	struct state *nexts;
-	struct arc *a;
-	struct arc *nexta;
-	struct state *intermediates;
-	int			progress;
-
-	/* find and push until there are no more */
-	do
-	{
-		progress = 0;
-		for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-		{
-			nexts = s->next;
-			intermediates = NULL;
-			for (a = s->ins; a != NULL && !NISERR(); a = nexta)
-			{
-				nexta = a->inchain;
-				if (a->type == '$' || a->type == AHEAD)
-					if (push(nfa, a, &intermediates))
-						progress = 1;
-			}
-			/* clear tmp fields of intermediate states created here */
-			while (intermediates != NULL)
-			{
-				struct state *ns = intermediates->tmp;
-
-				intermediates->tmp = NULL;
-				intermediates = ns;
-			}
-			/* if s is now useless, get rid of it */
-			if ((s->nins == 0 || s->nouts == 0) && !s->flag)
-				dropstate(nfa, s);
-		}
-		if (progress && f != NULL)
-			dumpnfa(nfa, f);
-	} while (progress && !NISERR());
-	if (NISERR())
-		return;
-
-	/*
-	 * Any $ constraints we were able to push to the post state can now be
-	 * replaced by PLAIN arcs referencing the EOS or EOL colors.  There should
-	 * be no other $ or AHEAD arcs left in the NFA, though we do not check
-	 * that here (compact() will fail if so).
-	 */
-	for (a = nfa->post->ins; a != NULL; a = nexta)
-	{
-		nexta = a->inchain;
-		if (a->type == '$')
-		{
-			assert(a->co == 0 || a->co == 1);
-			newarc(nfa, PLAIN, nfa->eos[a->co], a->from, a->to);
-			freearc(nfa, a);
-		}
-	}
-}
-
-/*
- * push - push a forward constraint forward past its destination state
- *
- * Returns 1 if successful (which it always is unless the destination is the
- * post state or we have an internal error), 0 if nothing happened.
- *
- * A significant property of this function is that it deletes no pre-existing
- * states, and no inarcs of the constraint's to state other than the given
- * constraint arc.  This makes the loops in pushfwd() safe, at the cost that
- * we may leave useless states behind.  Therefore, we leave it to pushfwd()
- * to delete such states.
- *
- * If the to state has multiple forward-constraint inarcs, and/or multiple
- * compatible constraint outarcs, we only need to create one new intermediate
- * state per combination of predecessor and successor states.  *intermediates
- * points to a list of such intermediate states for this to state (chained
- * through their tmp fields).
- */
-static int
-push(struct nfa * nfa,
-	 struct arc * con,
-	 struct state ** intermediates)
-{
-	struct state *from = con->from;
-	struct state *to = con->to;
-	struct arc *a;
-	struct arc *nexta;
-	struct state *s;
-
-	assert(to != from);			/* should have gotten rid of this earlier */
-	if (to->flag)				/* can't push forward beyond end */
-		return 0;
-	if (to->nouts == 0)
-	{							/* dead end */
-		freearc(nfa, con);
-		return 1;
-	}
-
-	/*
-	 * First, clone to state if necessary to avoid other inarcs.  This may
-	 * seem wasteful, but it simplifies the logic, and we'll get rid of the
-	 * clone state again at the bottom.
-	 */
-	if (to->nins > 1)
-	{
-		s = newstate(nfa);
-		if (NISERR())
-			return 0;
-		copyouts(nfa, to, s);	/* duplicate outarcs */
-		cparc(nfa, con, from, s);		/* move constraint arc */
-		freearc(nfa, con);
-		if (NISERR())
-			return 0;
-		to = s;
-		con = to->ins;
-	}
-	assert(to->nins == 1);
-
-	/* propagate the constraint into the to state's outarcs */
-	for (a = to->outs; a != NULL && !NISERR(); a = nexta)
-	{
-		nexta = a->outchain;
-		switch (combine(con, a))
-		{
-			case INCOMPATIBLE:	/* destroy the arc */
-				freearc(nfa, a);
-				break;
-			case SATISFIED:		/* no action needed */
-				break;
-			case COMPATIBLE:	/* swap the two arcs, more or less */
-				/* need an intermediate state, but might have one already */
-				for (s = *intermediates; s != NULL; s = s->tmp)
-				{
-					assert(s->nins > 0 && s->nouts > 0);
-					if (s->ins->from == from && s->outs->to == a->to)
-						break;
-				}
-				if (s == NULL)
-				{
-					s = newstate(nfa);
-					if (NISERR())
-						return 0;
-					s->tmp = *intermediates;
-					*intermediates = s;
-				}
-				cparc(nfa, con, s, a->to);
-				cparc(nfa, a, from, s);
-				freearc(nfa, a);
-				break;
-			default:
-				assert(NOTREACHED);
-				break;
-		}
-	}
-
-	/* remaining outarcs, if any, incorporate the constraint */
-	moveouts(nfa, to, from);
-	freearc(nfa, con);
-	/* to state is now useless, but we leave it to pushfwd() to clean up */
-	return 1;
-}
-
-/*
- * combine - constraint lands on an arc, what happens?
- *
- * #def INCOMPATIBLE	1	// destroys arc
- * #def SATISFIED		2	// constraint satisfied
- * #def COMPATIBLE		3	// compatible but not satisfied yet
- */
-static int
-combine(struct arc * con,
-		struct arc * a)
-{
-#define  CA(ct,at)	 (((ct)<<CHAR_BIT) | (at))
-
-	switch (CA(con->type, a->type))
-	{
-		case CA('^', PLAIN):	/* newlines are handled separately */
-		case CA('$', PLAIN):
-			return INCOMPATIBLE;
-			break;
-		case CA(AHEAD, PLAIN):	/* color constraints meet colors */
-		case CA(BEHIND, PLAIN):
-			if (con->co == a->co)
-				return SATISFIED;
-			return INCOMPATIBLE;
-			break;
-		case CA('^', '^'):		/* collision, similar constraints */
-		case CA('$', '$'):
-		case CA(AHEAD, AHEAD):
-		case CA(BEHIND, BEHIND):
-			if (con->co == a->co)		/* true duplication */
-				return SATISFIED;
-			return INCOMPATIBLE;
-			break;
-		case CA('^', BEHIND):	/* collision, dissimilar constraints */
-		case CA(BEHIND, '^'):
-		case CA('$', AHEAD):
-		case CA(AHEAD, '$'):
-			return INCOMPATIBLE;
-			break;
-		case CA('^', '$'):		/* constraints passing each other */
-		case CA('^', AHEAD):
-		case CA(BEHIND, '$'):
-		case CA(BEHIND, AHEAD):
-		case CA('$', '^'):
-		case CA('$', BEHIND):
-		case CA(AHEAD, '^'):
-		case CA(AHEAD, BEHIND):
-		case CA('^', LACON):
-		case CA(BEHIND, LACON):
-		case CA('$', LACON):
-		case CA(AHEAD, LACON):
-			return COMPATIBLE;
-			break;
-	}
-	assert(NOTREACHED);
-	return INCOMPATIBLE;		/* for benefit of blind compilers */
-}
-
-/*
- * fixempties - get rid of EMPTY arcs
- */
-static void
-fixempties(struct nfa * nfa,
-		   FILE *f)				/* for debug output; NULL none */
-{
-	struct state *s;
-	struct state *s2;
-	struct state *nexts;
-	struct arc *a;
-	struct arc *nexta;
-	int			totalinarcs;
-	struct arc **inarcsorig;
-	struct arc **arcarray;
-	int			arccount;
-	int			prevnins;
-	int			nskip;
-
-	/*
-	 * First, get rid of any states whose sole out-arc is an EMPTY, since
-	 * they're basically just aliases for their successor.  The parsing
-	 * algorithm creates enough of these that it's worth special-casing this.
-	 */
-	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-	{
-		nexts = s->next;
-		if (s->flag || s->nouts != 1)
-			continue;
-		a = s->outs;
-		assert(a != NULL && a->outchain == NULL);
-		if (a->type != EMPTY)
-			continue;
-		if (s != a->to)
-			moveins(nfa, s, a->to);
-		dropstate(nfa, s);
-	}
-
-	/*
-	 * Similarly, get rid of any state with a single EMPTY in-arc, by folding
-	 * it into its predecessor.
-	 */
-	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-	{
-		nexts = s->next;
-		/* while we're at it, ensure tmp fields are clear for next step */
-		assert(s->tmp == NULL);
-		if (s->flag || s->nins != 1)
-			continue;
-		a = s->ins;
-		assert(a != NULL && a->inchain == NULL);
-		if (a->type != EMPTY)
-			continue;
-		if (s != a->from)
-			moveouts(nfa, s, a->from);
-		dropstate(nfa, s);
-	}
-
-	if (NISERR())
-		return;
-
-	/*
-	 * For each remaining NFA state, find all other states from which it is
-	 * reachable by a chain of one or more EMPTY arcs.  Then generate new arcs
-	 * that eliminate the need for each such chain.
-	 *
-	 * We could replace a chain of EMPTY arcs that leads from a "from" state
-	 * to a "to" state either by pushing non-EMPTY arcs forward (linking
-	 * directly from "from"'s predecessors to "to") or by pulling them back
-	 * (linking directly from "from" to "to"'s successors).  We choose to
-	 * always do the former; this choice is somewhat arbitrary, but the
-	 * approach below requires that we uniformly do one or the other.
-	 *
-	 * Suppose we have a chain of N successive EMPTY arcs (where N can easily
-	 * approach the size of the NFA).  All of the intermediate states must
-	 * have additional inarcs and outarcs, else they'd have been removed by
-	 * the steps above.  Assuming their inarcs are mostly not empties, we will
-	 * add O(N^2) arcs to the NFA, since a non-EMPTY inarc leading to any one
-	 * state in the chain must be duplicated to lead to all its successor
-	 * states as well.  So there is no hope of doing less than O(N^2) work;
-	 * however, we should endeavor to keep the big-O cost from being even
-	 * worse than that, which it can easily become without care.  In
-	 * particular, suppose we were to copy all S1's inarcs forward to S2, and
-	 * then also to S3, and then later we consider pushing S2's inarcs forward
-	 * to S3.  If we include the arcs already copied from S1 in that, we'd be
-	 * doing O(N^3) work.  (The duplicate-arc elimination built into newarc()
-	 * and its cohorts would get rid of the extra arcs, but not without cost.)
-	 *
-	 * We can avoid this cost by treating only arcs that existed at the start
-	 * of this phase as candidates to be pushed forward.  To identify those,
-	 * we remember the first inarc each state had to start with.  We rely on
-	 * the fact that newarc() and friends put new arcs on the front of their
-	 * to-states' inchains, and that this phase never deletes arcs, so that
-	 * the original arcs must be the last arcs in their to-states' inchains.
-	 *
-	 * So the process here is that, for each state in the NFA, we gather up
-	 * all non-EMPTY inarcs of states that can reach the target state via
-	 * EMPTY arcs.  We then sort, de-duplicate, and merge these arcs into the
-	 * target state's inchain.  (We can safely use sort-merge for this as long
-	 * as we update each state's original-arcs pointer after we add arcs to
-	 * it; the sort step of mergeins probably changed the order of the old
-	 * arcs.)
-	 *
-	 * Another refinement worth making is that, because we only add non-EMPTY
-	 * arcs during this phase, and all added arcs have the same from-state as
-	 * the non-EMPTY arc they were cloned from, we know ahead of time that any
-	 * states having only EMPTY outarcs will be useless for lack of outarcs
-	 * after we drop the EMPTY arcs.  (They cannot gain non-EMPTY outarcs if
-	 * they had none to start with.)  So we need not bother to update the
-	 * inchains of such states at all.
-	 */
-
-	/* Remember the states' first original inarcs */
-	/* ... and while at it, count how many old inarcs there are altogether */
-	inarcsorig = (struct arc **) MALLOC(nfa->nstates * sizeof(struct arc *));
-	if (inarcsorig == NULL)
-	{
-		NERR(REG_ESPACE);
-		return;
-	}
-	totalinarcs = 0;
-	for (s = nfa->states; s != NULL; s = s->next)
-	{
-		inarcsorig[s->no] = s->ins;
-		totalinarcs += s->nins;
-	}
-
-	/*
-	 * Create a workspace for accumulating the inarcs to be added to the
-	 * current target state.  totalinarcs is probably a considerable
-	 * overestimate of the space needed, but the NFA is unlikely to be large
-	 * enough at this point to make it worth being smarter.
-	 */
-	arcarray = (struct arc **) MALLOC(totalinarcs * sizeof(struct arc *));
-	if (arcarray == NULL)
-	{
-		NERR(REG_ESPACE);
-		FREE(inarcsorig);
-		return;
-	}
-
-	/* And iterate over the target states */
-	for (s = nfa->states; s != NULL && !NISERR(); s = s->next)
-	{
-		/* Ignore target states without non-EMPTY outarcs, per note above */
-		if (!s->flag && !hasnonemptyout(s))
-			continue;
-
-		/* Find predecessor states and accumulate their original inarcs */
-		arccount = 0;
-		for (s2 = emptyreachable(nfa, s, s, inarcsorig); s2 != s; s2 = nexts)
-		{
-			/* Add s2's original inarcs to arcarray[], but ignore empties */
-			for (a = inarcsorig[s2->no]; a != NULL; a = a->inchain)
-			{
-				if (a->type != EMPTY)
-					arcarray[arccount++] = a;
-			}
-
-			/* Reset the tmp fields as we walk back */
-			nexts = s2->tmp;
-			s2->tmp = NULL;
-		}
-		s->tmp = NULL;
-		assert(arccount <= totalinarcs);
-
-		/* Remember how many original inarcs this state has */
-		prevnins = s->nins;
-
-		/* Add non-duplicate inarcs to target state */
-		mergeins(nfa, s, arcarray, arccount);
-
-		/* Now we must update the state's inarcsorig pointer */
-		nskip = s->nins - prevnins;
-		a = s->ins;
-		while (nskip-- > 0)
-			a = a->inchain;
-		inarcsorig[s->no] = a;
-	}
-
-	FREE(arcarray);
-	FREE(inarcsorig);
-
-	if (NISERR())
-		return;
-
-	/*
-	 * Now remove all the EMPTY arcs, since we don't need them anymore.
-	 */
-	for (s = nfa->states; s != NULL; s = s->next)
-	{
-		for (a = s->outs; a != NULL; a = nexta)
-		{
-			nexta = a->outchain;
-			if (a->type == EMPTY)
-				freearc(nfa, a);
-		}
-	}
-
-	/*
-	 * And remove any states that have become useless.  (This cleanup is not
-	 * very thorough, and would be even less so if we tried to combine it with
-	 * the previous step; but cleanup() will take care of anything we miss.)
-	 */
-	for (s = nfa->states; s != NULL; s = nexts)
-	{
-		nexts = s->next;
-		if ((s->nins == 0 || s->nouts == 0) && !s->flag)
-			dropstate(nfa, s);
-	}
-
-	if (f != NULL)
-		dumpnfa(nfa, f);
-}
-
-/*
- * emptyreachable - recursively find all states that can reach s by EMPTY arcs
- *
- * The return value is the last such state found.  Its tmp field links back
- * to the next-to-last such state, and so on back to s, so that all these
- * states can be located without searching the whole NFA.
- *
- * Since this is only used in fixempties(), we pass in the inarcsorig[] array
- * maintained by that function.  This lets us skip over all new inarcs, which
- * are certainly not EMPTY arcs.
- *
- * The maximum recursion depth here is equal to the length of the longest
- * loop-free chain of EMPTY arcs, which is surely no more than the size of
- * the NFA ... but that could still be enough to cause trouble.
- */
-static struct state *
-emptyreachable(struct nfa * nfa,
-			   struct state * s,
-			   struct state * lastfound,
-			   struct arc ** inarcsorig)
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return lastfound;
-	}
-
-	s->tmp = lastfound;
-	lastfound = s;
-	for (a = inarcsorig[s->no]; a != NULL; a = a->inchain)
-	{
-		if (a->type == EMPTY && a->from->tmp == NULL)
-			lastfound = emptyreachable(nfa, a->from, lastfound, inarcsorig);
-	}
-	return lastfound;
-}
-
-/*
- * isconstraintarc - detect whether an arc is of a constraint type
- */
-static inline int
-isconstraintarc(struct arc * a)
-{
-	switch (a->type)
-	{
-		case '^':
-		case '$':
-		case BEHIND:
-		case AHEAD:
-		case LACON:
-			return 1;
-	}
-	return 0;
-}
-
-/*
- * hasconstraintout - does state have a constraint out arc?
- */
-static int
-hasconstraintout(struct state * s)
-{
-	struct arc *a;
-
-	for (a = s->outs; a != NULL; a = a->outchain)
-	{
-		if (isconstraintarc(a))
-			return 1;
-	}
-	return 0;
-}
-
-/*
- * fixconstraintloops - get rid of loops containing only constraint arcs
- *
- * A loop of states that contains only constraint arcs is useless, since
- * passing around the loop represents no forward progress.  Moreover, it
- * would cause infinite looping in pullback/pushfwd, so we need to get rid
- * of such loops before doing that.
- */
-static void
-fixconstraintloops(struct nfa * nfa,
-				   FILE *f)		/* for debug output; NULL none */
-{
-	struct state *s;
-	struct state *nexts;
-	struct arc *a;
-	struct arc *nexta;
-	int			hasconstraints;
-
-	/*
-	 * In the trivial case of a state that loops to itself, we can just drop
-	 * the constraint arc altogether.  This is worth special-casing because
-	 * such loops are far more common than loops containing multiple states.
-	 * While we're at it, note whether any constraint arcs survive.
-	 */
-	hasconstraints = 0;
-	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-	{
-		nexts = s->next;
-		/* while we're at it, ensure tmp fields are clear for next step */
-		assert(s->tmp == NULL);
-		for (a = s->outs; a != NULL && !NISERR(); a = nexta)
-		{
-			nexta = a->outchain;
-			if (isconstraintarc(a))
-			{
-				if (a->to == s)
-					freearc(nfa, a);
-				else
-					hasconstraints = 1;
-			}
-		}
-		/* If we removed all the outarcs, the state is useless. */
-		if (s->nouts == 0 && !s->flag)
-			dropstate(nfa, s);
-	}
-
-	/* Nothing to do if no remaining constraint arcs */
-	if (NISERR() || !hasconstraints)
-		return;
-
-	/*
-	 * Starting from each remaining NFA state, search outwards for a
-	 * constraint loop.  If we find a loop, break the loop, then start the
-	 * search over.  (We could possibly retain some state from the first scan,
-	 * but it would complicate things greatly, and multi-state constraint
-	 * loops are rare enough that it's not worth optimizing the case.)
-	 */
-restart:
-	for (s = nfa->states; s != NULL && !NISERR(); s = s->next)
-	{
-		if (findconstraintloop(nfa, s))
-			goto restart;
-	}
-
-	if (NISERR())
-		return;
-
-	/*
-	 * Now remove any states that have become useless.  (This cleanup is not
-	 * very thorough, and would be even less so if we tried to combine it with
-	 * the previous step; but cleanup() will take care of anything we miss.)
-	 *
-	 * Because findconstraintloop intentionally doesn't reset all tmp fields,
-	 * we have to clear them after it's done.  This is a convenient place to
-	 * do that, too.
-	 */
-	for (s = nfa->states; s != NULL; s = nexts)
-	{
-		nexts = s->next;
-		s->tmp = NULL;
-		if ((s->nins == 0 || s->nouts == 0) && !s->flag)
-			dropstate(nfa, s);
-	}
-
-	if (f != NULL)
-		dumpnfa(nfa, f);
-}
-
-/*
- * findconstraintloop - recursively find a loop of constraint arcs
- *
- * If we find a loop, break it by calling breakconstraintloop(), then
- * return 1; otherwise return 0.
- *
- * State tmp fields are guaranteed all NULL on a success return, because
- * breakconstraintloop does that.  After a failure return, any state that
- * is known not to be part of a loop is marked with s->tmp == s; this allows
- * us not to have to re-prove that fact on later calls.  (This convention is
- * workable because we already eliminated single-state loops.)
- *
- * Note that the found loop doesn't necessarily include the first state we
- * are called on.  Any loop reachable from that state will do.
- *
- * The maximum recursion depth here is one more than the length of the longest
- * loop-free chain of constraint arcs, which is surely no more than the size
- * of the NFA ... but that could still be enough to cause trouble.
- */
-static int
-findconstraintloop(struct nfa * nfa, struct state * s)
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return 1;				/* to exit as quickly as possible */
-	}
-
-	if (s->tmp != NULL)
-	{
-		/* Already proven uninteresting? */
-		if (s->tmp == s)
-			return 0;
-		/* Found a loop involving s */
-		breakconstraintloop(nfa, s);
-		/* The tmp fields have been cleaned up by breakconstraintloop */
-		return 1;
-	}
-	for (a = s->outs; a != NULL; a = a->outchain)
-	{
-		if (isconstraintarc(a))
-		{
-			struct state *sto = a->to;
-
-			assert(sto != s);
-			s->tmp = sto;
-			if (findconstraintloop(nfa, sto))
-				return 1;
-		}
-	}
-
-	/*
-	 * If we get here, no constraint loop exists leading out from s.  Mark it
-	 * with s->tmp == s so we need not rediscover that fact again later.
-	 */
-	s->tmp = s;
-	return 0;
-}
-
-/*
- * breakconstraintloop - break a loop of constraint arcs
- *
- * sinitial is any one member state of the loop.  Each loop member's tmp
- * field links to its successor within the loop.  (Note that this function
- * will reset all the tmp fields to NULL.)
- *
- * We can break the loop by, for any one state S1 in the loop, cloning its
- * loop successor state S2 (and possibly following states), and then moving
- * all S1->S2 constraint arcs to point to the cloned S2.  The cloned S2 should
- * copy any non-constraint outarcs of S2.  Constraint outarcs should be
- * dropped if they point back to S1, else they need to be copied as arcs to
- * similarly cloned states S3, S4, etc.  In general, each cloned state copies
- * non-constraint outarcs, drops constraint outarcs that would lead to itself
- * or any earlier cloned state, and sends other constraint outarcs to newly
- * cloned states.  No cloned state will have any inarcs that aren't constraint
- * arcs or do not lead from S1 or earlier-cloned states.  It's okay to drop
- * constraint back-arcs since they would not take us to any state we've not
- * already been in; therefore, no new constraint loop is created.  In this way
- * we generate a modified NFA that can still represent every useful state
- * sequence, but not sequences that represent state loops with no consumption
- * of input data.  Note that the set of cloned states will certainly include
- * all of the loop member states other than S1, and it may also include
- * non-loop states that are reachable from S2 via constraint arcs.  This is
- * important because there is no guarantee that findconstraintloop found a
- * maximal loop (and searching for one would be NP-hard, so don't try).
- * Frequently the "non-loop states" are actually part of a larger loop that
- * we didn't notice, and indeed there may be several overlapping loops.
- * This technique ensures convergence in such cases, while considering only
- * the originally-found loop does not.
- *
- * If there is only one S1->S2 constraint arc, then that constraint is
- * certainly satisfied when we enter any of the clone states.  This means that
- * in the common case where many of the constraint arcs are identically
- * labeled, we can merge together clone states linked by a similarly-labeled
- * constraint: if we can get to the first one we can certainly get to the
- * second, so there's no need to distinguish.  This greatly reduces the number
- * of new states needed, so we preferentially break the given loop at a state
- * pair where this is true.
- *
- * Furthermore, it's fairly common to find that a cloned successor state has
- * no outarcs, especially if we're a bit aggressive about removing unnecessary
- * outarcs.  If that happens, then there is simply not any interesting state
- * that can be reached through the predecessor's loop arcs, which means we can
- * break the loop just by removing those loop arcs, with no new states added.
- */
-static void
-breakconstraintloop(struct nfa * nfa, struct state * sinitial)
-{
-	struct state *s;
-	struct state *shead;
-	struct state *stail;
-	struct state *sclone;
-	struct state *nexts;
-	struct arc *refarc;
-	struct arc *a;
-	struct arc *nexta;
-
-	/*
-	 * Start by identifying which loop step we want to break at.
-	 * Preferentially this is one with only one constraint arc.  (XXX are
-	 * there any other secondary heuristics we want to use here?)  Set refarc
-	 * to point to the selected lone constraint arc, if there is one.
-	 */
-	refarc = NULL;
-	s = sinitial;
-	do
-	{
-		nexts = s->tmp;
-		assert(nexts != s);		/* should not see any one-element loops */
-		if (refarc == NULL)
-		{
-			int			narcs = 0;
-
-			for (a = s->outs; a != NULL; a = a->outchain)
-			{
-				if (a->to == nexts && isconstraintarc(a))
-				{
-					refarc = a;
-					narcs++;
-				}
-			}
-			assert(narcs > 0);
-			if (narcs > 1)
-				refarc = NULL;	/* multiple constraint arcs here, no good */
-		}
-		s = nexts;
-	} while (s != sinitial);
-
-	if (refarc)
-	{
-		/* break at the refarc */
-		shead = refarc->from;
-		stail = refarc->to;
-		assert(stail == shead->tmp);
-	}
-	else
-	{
-		/* for lack of a better idea, break after sinitial */
-		shead = sinitial;
-		stail = sinitial->tmp;
-	}
-
-	/*
-	 * Reset the tmp fields so that we can use them for local storage in
-	 * clonesuccessorstates.  (findconstraintloop won't mind, since it's just
-	 * going to abandon its search anyway.)
-	 */
-	for (s = nfa->states; s != NULL; s = s->next)
-		s->tmp = NULL;
-
-	/*
-	 * Recursively build clone state(s) as needed.
-	 */
-	sclone = newstate(nfa);
-	if (sclone == NULL)
-	{
-		assert(NISERR());
-		return;
-	}
-
-	clonesuccessorstates(nfa, stail, sclone, shead, refarc,
-						 NULL, NULL, nfa->nstates);
-
-	if (NISERR())
-		return;
-
-	/*
-	 * It's possible that sclone has no outarcs at all, in which case it's
-	 * useless.  (We don't try extremely hard to get rid of useless states
-	 * here, but this is an easy and fairly common case.)
-	 */
-	if (sclone->nouts == 0)
-	{
-		freestate(nfa, sclone);
-		sclone = NULL;
-	}
-
-	/*
-	 * Move shead's constraint-loop arcs to point to sclone, or just drop them
-	 * if we discovered we don't need sclone.
-	 */
-	for (a = shead->outs; a != NULL; a = nexta)
-	{
-		nexta = a->outchain;
-		if (a->to == stail && isconstraintarc(a))
-		{
-			if (sclone)
-				cparc(nfa, a, shead, sclone);
-			freearc(nfa, a);
-			if (NISERR())
-				break;
-		}
-	}
-}
-
-/*
- * clonesuccessorstates - create a tree of constraint-arc successor states
- *
- * ssource is the state to be cloned, and sclone is the state to copy its
- * outarcs into.  sclone's inarcs, if any, should already be set up.
- *
- * spredecessor is the original predecessor state that we are trying to build
- * successors for (it may not be the immediate predecessor of ssource).
- * refarc, if not NULL, is the original constraint arc that is known to have
- * been traversed out of spredecessor to reach the successor(s).
- *
- * For each cloned successor state, we transiently create a "donemap" that is
- * a boolean array showing which source states we've already visited for this
- * clone state.  This prevents infinite recursion as well as useless repeat
- * visits to the same state subtree (which can add up fast, since typical NFAs
- * have multiple redundant arc pathways).  Each donemap is a char array
- * indexed by state number.  The donemaps are all of the same size "nstates",
- * which is nfa->nstates as of the start of the recursion.  This is enough to
- * have entries for all pre-existing states, but *not* entries for clone
- * states created during the recursion.  That's okay since we have no need to
- * mark those.
- *
- * curdonemap is NULL when recursing to a new sclone state, or sclone's
- * donemap when we are recursing without having created a new state (which we
- * do when we decide we can merge a successor state into the current clone
- * state).  outerdonemap is NULL at the top level and otherwise the parent
- * clone state's donemap.
- *
- * The successor states we create and fill here form a strict tree structure,
- * with each state having exactly one predecessor, except that the toplevel
- * state has no inarcs as yet (breakconstraintloop will add its inarcs from
- * spredecessor after we're done).  Thus, we can examine sclone's inarcs back
- * to the root, plus refarc if any, to identify the set of constraints already
- * known valid at the current point.  This allows us to avoid generating extra
- * successor states.
- */
-static void
-clonesuccessorstates(struct nfa * nfa,
-					 struct state * ssource,
-					 struct state * sclone,
-					 struct state * spredecessor,
-					 struct arc * refarc,
-					 char *curdonemap,
-					 char *outerdonemap,
-					 int nstates)
-{
-	char	   *donemap;
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	/* If this state hasn't already got a donemap, create one */
-	donemap = curdonemap;
-	if (donemap == NULL)
-	{
-		donemap = (char *) MALLOC(nstates * sizeof(char));
-		if (donemap == NULL)
-		{
-			NERR(REG_ESPACE);
-			return;
-		}
-
-		if (outerdonemap != NULL)
-		{
-			/*
-			 * Not at outermost recursion level, so copy the outer level's
-			 * donemap; this ensures that we see states in process of being
-			 * visited at outer levels, or already merged into predecessor
-			 * states, as ones we shouldn't traverse back to.
-			 */
-			memcpy(donemap, outerdonemap, nstates * sizeof(char));
-		}
-		else
-		{
-			/* At outermost level, only spredecessor is off-limits */
-			memset(donemap, 0, nstates * sizeof(char));
-			assert(spredecessor->no < nstates);
-			donemap[spredecessor->no] = 1;
-		}
-	}
-
-	/* Mark ssource as visited in the donemap */
-	assert(ssource->no < nstates);
-	assert(donemap[ssource->no] == 0);
-	donemap[ssource->no] = 1;
-
-	/*
-	 * We proceed by first cloning all of ssource's outarcs, creating new
-	 * clone states as needed but not doing more with them than that.  Then in
-	 * a second pass, recurse to process the child clone states.  This allows
-	 * us to have only one child clone state per reachable source state, even
-	 * when there are multiple outarcs leading to the same state.  Also, when
-	 * we do visit a child state, its set of inarcs is known exactly, which
-	 * makes it safe to apply the constraint-is-already-checked optimization.
-	 * Also, this ensures that we've merged all the states we can into the
-	 * current clone before we recurse to any children, thus possibly saving
-	 * them from making extra images of those states.
-	 *
-	 * While this function runs, child clone states of the current state are
-	 * marked by setting their tmp fields to point to the original state they
-	 * were cloned from.  This makes it possible to detect multiple outarcs
-	 * leading to the same state, and also makes it easy to distinguish clone
-	 * states from original states (which will have tmp == NULL).
-	 */
-	for (a = ssource->outs; a != NULL && !NISERR(); a = a->outchain)
-	{
-		struct state *sto = a->to;
-
-		/*
-		 * We do not consider cloning successor states that have no constraint
-		 * outarcs; just link to them as-is.  They cannot be part of a
-		 * constraint loop so there is no need to make copies.  In particular,
-		 * this rule keeps us from trying to clone the post state, which would
-		 * be a bad idea.
-		 */
-		if (isconstraintarc(a) && hasconstraintout(sto))
-		{
-			struct state *prevclone;
-			int			canmerge;
-			struct arc *a2;
-
-			/*
-			 * Back-link constraint arcs must not be followed.  Nor is there a
-			 * need to revisit states previously merged into this clone.
-			 */
-			assert(sto->no < nstates);
-			if (donemap[sto->no] != 0)
-				continue;
-
-			/*
-			 * Check whether we already have a child clone state for this
-			 * source state.
-			 */
-			prevclone = NULL;
-			for (a2 = sclone->outs; a2 != NULL; a2 = a2->outchain)
-			{
-				if (a2->to->tmp == sto)
-				{
-					prevclone = a2->to;
-					break;
-				}
-			}
-
-			/*
-			 * If this arc is labeled the same as refarc, or the same as any
-			 * arc we must have traversed to get to sclone, then no additional
-			 * constraints need to be met to get to sto, so we should just
-			 * merge its outarcs into sclone.
-			 */
-			if (refarc && a->type == refarc->type && a->co == refarc->co)
-				canmerge = 1;
-			else
-			{
-				struct state *s;
-
-				canmerge = 0;
-				for (s = sclone; s->ins; s = s->ins->from)
-				{
-					if (s->nins == 1 &&
-						a->type == s->ins->type && a->co == s->ins->co)
-					{
-						canmerge = 1;
-						break;
-					}
-				}
-			}
-
-			if (canmerge)
-			{
-				/*
-				 * We can merge into sclone.  If we previously made a child
-				 * clone state, drop it; there's no need to visit it.  (This
-				 * can happen if ssource has multiple pathways to sto, and we
-				 * only just now found one that is provably a no-op.)
-				 */
-				if (prevclone)
-					dropstate(nfa, prevclone);	/* kills our outarc, too */
-
-				/* Recurse to merge sto's outarcs into sclone */
-				clonesuccessorstates(nfa,
-									 sto,
-									 sclone,
-									 spredecessor,
-									 refarc,
-									 donemap,
-									 outerdonemap,
-									 nstates);
-				/* sto should now be marked as previously visited */
-				assert(NISERR() || donemap[sto->no] == 1);
-			}
-			else if (prevclone)
-			{
-				/*
-				 * We already have a clone state for this successor, so just
-				 * make another arc to it.
-				 */
-				cparc(nfa, a, sclone, prevclone);
-			}
-			else
-			{
-				/*
-				 * We need to create a new successor clone state.
-				 */
-				struct state *stoclone;
-
-				stoclone = newstate(nfa);
-				if (stoclone == NULL)
-				{
-					assert(NISERR());
-					break;
-				}
-				/* Mark it as to what it's a clone of */
-				stoclone->tmp = sto;
-				/* ... and add the outarc leading to it */
-				cparc(nfa, a, sclone, stoclone);
-			}
-		}
-		else
-		{
-			/*
-			 * Non-constraint outarcs just get copied to sclone, as do outarcs
-			 * leading to states with no constraint outarc.
-			 */
-			cparc(nfa, a, sclone, sto);
-		}
-	}
-
-	/*
-	 * If we are at outer level for this clone state, recurse to all its child
-	 * clone states, clearing their tmp fields as we go.  (If we're not
-	 * outermost for sclone, leave this to be done by the outer call level.)
-	 * Note that if we have multiple outarcs leading to the same clone state,
-	 * it will only be recursed-to once.
-	 */
-	if (curdonemap == NULL)
-	{
-		for (a = sclone->outs; a != NULL && !NISERR(); a = a->outchain)
-		{
-			struct state *stoclone = a->to;
-			struct state *sto = stoclone->tmp;
-
-			if (sto != NULL)
-			{
-				stoclone->tmp = NULL;
-				clonesuccessorstates(nfa,
-									 sto,
-									 stoclone,
-									 spredecessor,
-									 refarc,
-									 NULL,
-									 donemap,
-									 nstates);
-			}
-		}
-
-		/* Don't forget to free sclone's donemap when done with it */
-		FREE(donemap);
-	}
-}
-
-/*
- * cleanup - clean up NFA after optimizations
- */
-static void
-cleanup(struct nfa * nfa)
-{
-	struct state *s;
-	struct state *nexts;
-	int			n;
-
-	if (NISERR())
-		return;
-
-	/* clear out unreachable or dead-end states */
-	/* use pre to mark reachable, then post to mark can-reach-post */
-	markreachable(nfa, nfa->pre, (struct state *) NULL, nfa->pre);
-	markcanreach(nfa, nfa->post, nfa->pre, nfa->post);
-	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
-	{
-		nexts = s->next;
-		if (s->tmp != nfa->post && !s->flag)
-			dropstate(nfa, s);
-	}
-	assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == nfa->post);
-	cleartraverse(nfa, nfa->pre);
-	assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == NULL);
-	/* the nins==0 (final unreachable) case will be caught later */
-
-	/* renumber surviving states */
-	n = 0;
-	for (s = nfa->states; s != NULL; s = s->next)
-		s->no = n++;
-	nfa->nstates = n;
-}
-
-/*
- * markreachable - recursive marking of reachable states
- */
-static void
-markreachable(struct nfa * nfa,
-			  struct state * s,
-			  struct state * okay,		/* consider only states with this mark */
-			  struct state * mark)		/* the value to mark with */
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	if (s->tmp != okay)
-		return;
-	s->tmp = mark;
-
-	for (a = s->outs; a != NULL; a = a->outchain)
-		markreachable(nfa, a->to, okay, mark);
-}
-
-/*
- * markcanreach - recursive marking of states which can reach here
- */
-static void
-markcanreach(struct nfa * nfa,
-			 struct state * s,
-			 struct state * okay,		/* consider only states with this mark */
-			 struct state * mark)		/* the value to mark with */
-{
-	struct arc *a;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(nfa->v->re))
-	{
-		NERR(REG_ETOOBIG);
-		return;
-	}
-
-	if (s->tmp != okay)
-		return;
-	s->tmp = mark;
-
-	for (a = s->ins; a != NULL; a = a->inchain)
-		markcanreach(nfa, a->from, okay, mark);
-}
-
-/*
- * analyze - ascertain potentially-useful facts about an optimized NFA
- */
-static long						/* re_info bits to be ORed in */
-analyze(struct nfa * nfa)
-{
-	struct arc *a;
-	struct arc *aa;
-
-	if (NISERR())
-		return 0;
-
-	if (nfa->pre->outs == NULL)
-		return REG_UIMPOSSIBLE;
-	for (a = nfa->pre->outs; a != NULL; a = a->outchain)
-		for (aa = a->to->outs; aa != NULL; aa = aa->outchain)
-			if (aa->to == nfa->post)
-				return REG_UEMPTYMATCH;
-	return 0;
-}
-
-/*
- * compact - construct the compact representation of an NFA
- */
-static void
-compact(struct nfa * nfa,
-		struct cnfa * cnfa)
-{
-	struct state *s;
-	struct arc *a;
-	size_t		nstates;
-	size_t		narcs;
-	struct carc *ca;
-	struct carc *first;
-
-	assert(!NISERR());
-
-	nstates = 0;
-	narcs = 0;
-	for (s = nfa->states; s != NULL; s = s->next)
-	{
-		nstates++;
-		narcs += s->nouts + 1;	/* need one extra for endmarker */
-	}
-
-	cnfa->stflags = (char *) MALLOC(nstates * sizeof(char));
-	cnfa->states = (struct carc **) MALLOC(nstates * sizeof(struct carc *));
-	cnfa->arcs = (struct carc *) MALLOC(narcs * sizeof(struct carc));
-	if (cnfa->stflags == NULL || cnfa->states == NULL || cnfa->arcs == NULL)
-	{
-		if (cnfa->stflags != NULL)
-			FREE(cnfa->stflags);
-		if (cnfa->states != NULL)
-			FREE(cnfa->states);
-		if (cnfa->arcs != NULL)
-			FREE(cnfa->arcs);
-		NERR(REG_ESPACE);
-		return;
-	}
-	cnfa->nstates = nstates;
-	cnfa->pre = nfa->pre->no;
-	cnfa->post = nfa->post->no;
-	cnfa->bos[0] = nfa->bos[0];
-	cnfa->bos[1] = nfa->bos[1];
-	cnfa->eos[0] = nfa->eos[0];
-	cnfa->eos[1] = nfa->eos[1];
-	cnfa->ncolors = maxcolor(nfa->cm) + 1;
-	cnfa->flags = 0;
-
-	ca = cnfa->arcs;
-	for (s = nfa->states; s != NULL; s = s->next)
-	{
-		assert((size_t) s->no < nstates);
-		cnfa->stflags[s->no] = 0;
-		cnfa->states[s->no] = ca;
-		first = ca;
-		for (a = s->outs; a != NULL; a = a->outchain)
-			switch (a->type)
-			{
-				case PLAIN:
-					ca->co = a->co;
-					ca->to = a->to->no;
-					ca++;
-					break;
-				case LACON:
-					assert(s->no != cnfa->pre);
-					ca->co = (color) (cnfa->ncolors + a->co);
-					ca->to = a->to->no;
-					ca++;
-					cnfa->flags |= HASLACONS;
-					break;
-				default:
-					NERR(REG_ASSERT);
-					break;
-			}
-		carcsort(first, ca - first);
-		ca->co = COLORLESS;
-		ca->to = 0;
-		ca++;
-	}
-	assert(ca == &cnfa->arcs[narcs]);
-	assert(cnfa->nstates != 0);
-
-	/* mark no-progress states */
-	for (a = nfa->pre->outs; a != NULL; a = a->outchain)
-		cnfa->stflags[a->to->no] = CNFA_NOPROGRESS;
-	cnfa->stflags[nfa->pre->no] = CNFA_NOPROGRESS;
-}
-
-/*
- * carcsort - sort compacted-NFA arcs by color
- */
-static void
-carcsort(struct carc * first, size_t n)
-{
-	if (n > 1)
-		qsort(first, n, sizeof(struct carc), carc_cmp);
-}
-
-static int
-carc_cmp(const void *a, const void *b)
-{
-	const struct carc *aa = (const struct carc *) a;
-	const struct carc *bb = (const struct carc *) b;
-
-	if (aa->co < bb->co)
-		return -1;
-	if (aa->co > bb->co)
-		return +1;
-	if (aa->to < bb->to)
-		return -1;
-	if (aa->to > bb->to)
-		return +1;
-	return 0;
-}
-
-/*
- * freecnfa - free a compacted NFA
- */
-static void
-freecnfa(struct cnfa * cnfa)
-{
-	assert(cnfa->nstates != 0); /* not empty already */
-	cnfa->nstates = 0;
-	FREE(cnfa->stflags);
-	FREE(cnfa->states);
-	FREE(cnfa->arcs);
-}
-
-/*
- * dumpnfa - dump an NFA in human-readable form
- */
-static void
-dumpnfa(struct nfa * nfa,
-		FILE *f)
-{
-#ifdef REG_DEBUG
-	struct state *s;
-	int			nstates = 0;
-	int			narcs = 0;
-
-	fprintf(f, "pre %d, post %d", nfa->pre->no, nfa->post->no);
-	if (nfa->bos[0] != COLORLESS)
-		fprintf(f, ", bos [%ld]", (long) nfa->bos[0]);
-	if (nfa->bos[1] != COLORLESS)
-		fprintf(f, ", bol [%ld]", (long) nfa->bos[1]);
-	if (nfa->eos[0] != COLORLESS)
-		fprintf(f, ", eos [%ld]", (long) nfa->eos[0]);
-	if (nfa->eos[1] != COLORLESS)
-		fprintf(f, ", eol [%ld]", (long) nfa->eos[1]);
-	fprintf(f, "\n");
-	for (s = nfa->states; s != NULL; s = s->next)
-	{
-		dumpstate(s, f);
-		nstates++;
-		narcs += s->nouts;
-	}
-	fprintf(f, "total of %d states, %d arcs\n", nstates, narcs);
-	if (nfa->parent == NULL)
-		dumpcolors(nfa->cm, f);
-	fflush(f);
-#endif
-}
-
-#ifdef REG_DEBUG				/* subordinates of dumpnfa */
-
-/*
- * dumpstate - dump an NFA state in human-readable form
- */
-static void
-dumpstate(struct state * s,
-		  FILE *f)
-{
-	struct arc *a;
-
-	fprintf(f, "%d%s%c", s->no, (s->tmp != NULL) ? "T" : "",
-			(s->flag) ? s->flag : '.');
-	if (s->prev != NULL && s->prev->next != s)
-		fprintf(f, "\tstate chain bad\n");
-	if (s->nouts == 0)
-		fprintf(f, "\tno out arcs\n");
-	else
-		dumparcs(s, f);
-	fflush(f);
-	for (a = s->ins; a != NULL; a = a->inchain)
-	{
-		if (a->to != s)
-			fprintf(f, "\tlink from %d to %d on %d's in-chain\n",
-					a->from->no, a->to->no, s->no);
-	}
-}
-
-/*
- * dumparcs - dump out-arcs in human-readable form
- */
-static void
-dumparcs(struct state * s,
-		 FILE *f)
-{
-	int			pos;
-	struct arc *a;
-
-	/* printing oldest arcs first is usually clearer */
-	a = s->outs;
-	assert(a != NULL);
-	while (a->outchain != NULL)
-		a = a->outchain;
-	pos = 1;
-	do
-	{
-		dumparc(a, s, f);
-		if (pos == 5)
-		{
-			fprintf(f, "\n");
-			pos = 1;
-		}
-		else
-			pos++;
-		a = a->outchainRev;
-	} while (a != NULL);
-	if (pos != 1)
-		fprintf(f, "\n");
-}
-
-/*
- * dumparc - dump one outarc in readable form, including prefixing tab
- */
-static void
-dumparc(struct arc * a,
-		struct state * s,
-		FILE *f)
-{
-	struct arc *aa;
-	struct arcbatch *ab;
-
-	fprintf(f, "\t");
-	switch (a->type)
-	{
-		case PLAIN:
-			fprintf(f, "[%ld]", (long) a->co);
-			break;
-		case AHEAD:
-			fprintf(f, ">%ld>", (long) a->co);
-			break;
-		case BEHIND:
-			fprintf(f, "<%ld<", (long) a->co);
-			break;
-		case LACON:
-			fprintf(f, ":%ld:", (long) a->co);
-			break;
-		case '^':
-		case '$':
-			fprintf(f, "%c%d", a->type, (int) a->co);
-			break;
-		case EMPTY:
-			break;
-		default:
-			fprintf(f, "0x%x/0%lo", a->type, (long) a->co);
-			break;
-	}
-	if (a->from != s)
-		fprintf(f, "?%d?", a->from->no);
-	for (ab = &a->from->oas; ab != NULL; ab = ab->next)
-	{
-		for (aa = &ab->a[0]; aa < &ab->a[ABSIZE]; aa++)
-			if (aa == a)
-				break;			/* NOTE BREAK OUT */
-		if (aa < &ab->a[ABSIZE])	/* propagate break */
-			break;				/* NOTE BREAK OUT */
-	}
-	if (ab == NULL)
-		fprintf(f, "?!?");		/* not in allocated space */
-	fprintf(f, "->");
-	if (a->to == NULL)
-	{
-		fprintf(f, "NULL");
-		return;
-	}
-	fprintf(f, "%d", a->to->no);
-	for (aa = a->to->ins; aa != NULL; aa = aa->inchain)
-		if (aa == a)
-			break;				/* NOTE BREAK OUT */
-	if (aa == NULL)
-		fprintf(f, "?!?");		/* missing from in-chain */
-}
-#endif   /* REG_DEBUG */
-
-/*
- * dumpcnfa - dump a compacted NFA in human-readable form
- */
-#ifdef REG_DEBUG
-static void
-dumpcnfa(struct cnfa * cnfa,
-		 FILE *f)
-{
-	int			st;
-
-	fprintf(f, "pre %d, post %d", cnfa->pre, cnfa->post);
-	if (cnfa->bos[0] != COLORLESS)
-		fprintf(f, ", bos [%ld]", (long) cnfa->bos[0]);
-	if (cnfa->bos[1] != COLORLESS)
-		fprintf(f, ", bol [%ld]", (long) cnfa->bos[1]);
-	if (cnfa->eos[0] != COLORLESS)
-		fprintf(f, ", eos [%ld]", (long) cnfa->eos[0]);
-	if (cnfa->eos[1] != COLORLESS)
-		fprintf(f, ", eol [%ld]", (long) cnfa->eos[1]);
-	if (cnfa->flags & HASLACONS)
-		fprintf(f, ", haslacons");
-	fprintf(f, "\n");
-	for (st = 0; st < cnfa->nstates; st++)
-		dumpcstate(st, cnfa, f);
-	fflush(f);
-}
-#endif
-
-#ifdef REG_DEBUG				/* subordinates of dumpcnfa */
-
-/*
- * dumpcstate - dump a compacted-NFA state in human-readable form
- */
-static void
-dumpcstate(int st,
-		   struct cnfa * cnfa,
-		   FILE *f)
-{
-	struct carc *ca;
-	int			pos;
-
-	fprintf(f, "%d%s", st, (cnfa->stflags[st] & CNFA_NOPROGRESS) ? ":" : ".");
-	pos = 1;
-	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-	{
-		if (ca->co < cnfa->ncolors)
-			fprintf(f, "\t[%ld]->%d", (long) ca->co, ca->to);
-		else
-			fprintf(f, "\t:%ld:->%d", (long) (ca->co - cnfa->ncolors), ca->to);
-		if (pos == 5)
-		{
-			fprintf(f, "\n");
-			pos = 1;
-		}
-		else
-			pos++;
-	}
-	if (ca == cnfa->states[st] || pos != 1)
-		fprintf(f, "\n");
-	fflush(f);
-}
-
-#endif   /* REG_DEBUG */
diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
deleted file mode 100644
index b707b06..0000000
--- a/src/backend/regex/regc_pg_locale.c
+++ /dev/null
@@ -1,878 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * regc_pg_locale.c
- *	  ctype functions adapted to work on pg_wchar (a/k/a chr),
- *	  and functions to cache the results of wholesale ctype probing.
- *
- * This file is #included by regcomp.c; it's not meant to compile standalone.
- *
- * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/regex/regc_pg_locale.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "catalog/pg_collation.h"
-#include "utils/pg_locale.h"
-
-/*
- * To provide as much functionality as possible on a variety of platforms,
- * without going so far as to implement everything from scratch, we use
- * several implementation strategies depending on the situation:
- *
- * 1. In C/POSIX collations, we use hard-wired code.  We can't depend on
- * the <ctype.h> functions since those will obey LC_CTYPE.  Note that these
- * collations don't give a fig about multibyte characters.
- *
- * 2. In the "default" collation (which is supposed to obey LC_CTYPE):
- *
- * 2a. When working in UTF8 encoding, we use the <wctype.h> functions if
- * available.  This assumes that every platform uses Unicode codepoints
- * directly as the wchar_t representation of Unicode.  On some platforms
- * wchar_t is only 16 bits wide, so we have to punt for codepoints > 0xFFFF.
- *
- * 2b. In all other encodings, or on machines that lack <wctype.h>, we use
- * the <ctype.h> functions for pg_wchar values up to 255, and punt for values
- * above that.  This is only 100% correct in single-byte encodings such as
- * LATINn.  However, non-Unicode multibyte encodings are mostly Far Eastern
- * character sets for which the properties being tested here aren't very
- * relevant for higher code values anyway.  The difficulty with using the
- * <wctype.h> functions with non-Unicode multibyte encodings is that we can
- * have no certainty that the platform's wchar_t representation matches
- * what we do in pg_wchar conversions.
- *
- * 3. Other collations are only supported on platforms that HAVE_LOCALE_T.
- * Here, we use the locale_t-extended forms of the <wctype.h> and <ctype.h>
- * functions, under exactly the same cases as #2.
- *
- * There is one notable difference between cases 2 and 3: in the "default"
- * collation we force ASCII letters to follow ASCII upcase/downcase rules,
- * while in a non-default collation we just let the library functions do what
- * they will.  The case where this matters is treatment of I/i in Turkish,
- * and the behavior is meant to match the upper()/lower() SQL functions.
- *
- * We store the active collation setting in static variables.  In principle
- * it could be passed down to here via the regex library's "struct vars" data
- * structure; but that would require somewhat invasive changes in the regex
- * library, and right now there's no real benefit to be gained from that.
- *
- * NB: the coding here assumes pg_wchar is an unsigned type.
- */
-
-typedef enum
-{
-	PG_REGEX_LOCALE_C,			/* C locale (encoding independent) */
-	PG_REGEX_LOCALE_WIDE,		/* Use <wctype.h> functions */
-	PG_REGEX_LOCALE_1BYTE,		/* Use <ctype.h> functions */
-	PG_REGEX_LOCALE_WIDE_L,		/* Use locale_t <wctype.h> functions */
-	PG_REGEX_LOCALE_1BYTE_L		/* Use locale_t <ctype.h> functions */
-} PG_Locale_Strategy;
-
-static PG_Locale_Strategy pg_regex_strategy;
-static pg_locale_t pg_regex_locale;
-static Oid	pg_regex_collation;
-
-/*
- * Hard-wired character properties for C locale
- */
-#define PG_ISDIGIT	0x01
-#define PG_ISALPHA	0x02
-#define PG_ISALNUM	(PG_ISDIGIT | PG_ISALPHA)
-#define PG_ISUPPER	0x04
-#define PG_ISLOWER	0x08
-#define PG_ISGRAPH	0x10
-#define PG_ISPRINT	0x20
-#define PG_ISPUNCT	0x40
-#define PG_ISSPACE	0x80
-
-static const unsigned char pg_char_properties[128] = {
-	 /* NUL */ 0,
-	 /* ^A */ 0,
-	 /* ^B */ 0,
-	 /* ^C */ 0,
-	 /* ^D */ 0,
-	 /* ^E */ 0,
-	 /* ^F */ 0,
-	 /* ^G */ 0,
-	 /* ^H */ 0,
-	 /* ^I */ PG_ISSPACE,
-	 /* ^J */ PG_ISSPACE,
-	 /* ^K */ PG_ISSPACE,
-	 /* ^L */ PG_ISSPACE,
-	 /* ^M */ PG_ISSPACE,
-	 /* ^N */ 0,
-	 /* ^O */ 0,
-	 /* ^P */ 0,
-	 /* ^Q */ 0,
-	 /* ^R */ 0,
-	 /* ^S */ 0,
-	 /* ^T */ 0,
-	 /* ^U */ 0,
-	 /* ^V */ 0,
-	 /* ^W */ 0,
-	 /* ^X */ 0,
-	 /* ^Y */ 0,
-	 /* ^Z */ 0,
-	 /* ^[ */ 0,
-	 /* ^\ */ 0,
-	 /* ^] */ 0,
-	 /* ^^ */ 0,
-	 /* ^_ */ 0,
-	 /* */ PG_ISPRINT | PG_ISSPACE,
-	 /* !  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* "  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* #  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* $  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* %  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* &  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* '  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* (  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* )  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* *  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* +  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ,  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* -  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* .  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* /  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* 0  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 1  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 2  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 3  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 4  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 5  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 6  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 7  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 8  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* 9  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
-	 /* :  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ;  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* <  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* =  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* >  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ?  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* @  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* A  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* B  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* C  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* D  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* E  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* F  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* G  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* H  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* I  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* J  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* K  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* L  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* M  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* N  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* O  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* P  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* Q  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* R  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* S  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* T  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* U  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* V  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* W  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* X  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* Y  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* Z  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
-	 /* [  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* \  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ]  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ^  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* _  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* `  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* a  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* b  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* c  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* d  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* e  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* f  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* g  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* h  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* i  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* j  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* k  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* l  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* m  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* n  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* o  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* p  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* q  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* r  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* s  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* t  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* u  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* v  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* w  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* x  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* y  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* z  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
-	 /* {  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* |  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* }  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* ~  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
-	 /* DEL */ 0
-};
-
-
-/*
- * pg_set_regex_collation: set collation for these functions to obey
- *
- * This is called when beginning compilation or execution of a regexp.
- * Since there's no need for re-entrancy of regexp operations, it's okay
- * to store the results in static variables.
- */
-void
-pg_set_regex_collation(Oid collation)
-{
-	if (lc_ctype_is_c(collation))
-	{
-		/* C/POSIX collations use this path regardless of database encoding */
-		pg_regex_strategy = PG_REGEX_LOCALE_C;
-		pg_regex_locale = 0;
-		pg_regex_collation = C_COLLATION_OID;
-	}
-	else
-	{
-		if (collation == DEFAULT_COLLATION_OID)
-			pg_regex_locale = 0;
-		else if (OidIsValid(collation))
-		{
-			/*
-			 * NB: pg_newlocale_from_collation will fail if not HAVE_LOCALE_T;
-			 * the case of pg_regex_locale != 0 but not HAVE_LOCALE_T does not
-			 * have to be considered below.
-			 */
-			pg_regex_locale = pg_newlocale_from_collation(collation);
-		}
-		else
-		{
-			/*
-			 * This typically means that the parser could not resolve a
-			 * conflict of implicit collations, so report it that way.
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_INDETERMINATE_COLLATION),
-					 errmsg("could not determine which collation to use for regular expression"),
-					 errhint("Use the COLLATE clause to set the collation explicitly.")));
-		}
-
-#ifdef USE_WIDE_UPPER_LOWER
-		if (GetDatabaseEncoding() == PG_UTF8)
-		{
-			if (pg_regex_locale)
-				pg_regex_strategy = PG_REGEX_LOCALE_WIDE_L;
-			else
-				pg_regex_strategy = PG_REGEX_LOCALE_WIDE;
-		}
-		else
-#endif   /* USE_WIDE_UPPER_LOWER */
-		{
-			if (pg_regex_locale)
-				pg_regex_strategy = PG_REGEX_LOCALE_1BYTE_L;
-			else
-				pg_regex_strategy = PG_REGEX_LOCALE_1BYTE;
-		}
-
-		pg_regex_collation = collation;
-	}
-}
-
-static int
-pg_wc_isdigit(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISDIGIT));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswdigit((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isdigit((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswdigit_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isdigit_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isalpha(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISALPHA));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswalpha((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isalpha((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswalpha_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isalpha_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isalnum(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISALNUM));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswalnum((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isalnum((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswalnum_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isalnum_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isupper(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISUPPER));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswupper((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isupper((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswupper_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isupper_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_islower(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISLOWER));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswlower((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					islower((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswlower_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					islower_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isgraph(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISGRAPH));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswgraph((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isgraph((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswgraph_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isgraph_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isprint(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISPRINT));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswprint((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isprint((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswprint_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isprint_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_ispunct(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISPUNCT));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswpunct((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					ispunct((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswpunct_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					ispunct_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static int
-pg_wc_isspace(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			return (c <= (pg_wchar) 127 &&
-					(pg_char_properties[c] & PG_ISSPACE));
-		case PG_REGEX_LOCALE_WIDE:
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswspace((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isspace((unsigned char) c));
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return iswspace_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			return (c <= (pg_wchar) UCHAR_MAX &&
-					isspace_l((unsigned char) c, pg_regex_locale));
-#endif
-			break;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static pg_wchar
-pg_wc_toupper(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_toupper((unsigned char) c);
-			return c;
-		case PG_REGEX_LOCALE_WIDE:
-			/* force C behavior for ASCII characters, per comments above */
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_toupper((unsigned char) c);
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return towupper((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			/* force C behavior for ASCII characters, per comments above */
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_toupper((unsigned char) c);
-			if (c <= (pg_wchar) UCHAR_MAX)
-				return toupper((unsigned char) c);
-			return c;
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return towupper_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			if (c <= (pg_wchar) UCHAR_MAX)
-				return toupper_l((unsigned char) c, pg_regex_locale);
-#endif
-			return c;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-static pg_wchar
-pg_wc_tolower(pg_wchar c)
-{
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_tolower((unsigned char) c);
-			return c;
-		case PG_REGEX_LOCALE_WIDE:
-			/* force C behavior for ASCII characters, per comments above */
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_tolower((unsigned char) c);
-#ifdef USE_WIDE_UPPER_LOWER
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return towlower((wint_t) c);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE:
-			/* force C behavior for ASCII characters, per comments above */
-			if (c <= (pg_wchar) 127)
-				return pg_ascii_tolower((unsigned char) c);
-			if (c <= (pg_wchar) UCHAR_MAX)
-				return tolower((unsigned char) c);
-			return c;
-		case PG_REGEX_LOCALE_WIDE_L:
-#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
-			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
-				return towlower_l((wint_t) c, pg_regex_locale);
-#endif
-			/* FALL THRU */
-		case PG_REGEX_LOCALE_1BYTE_L:
-#ifdef HAVE_LOCALE_T
-			if (c <= (pg_wchar) UCHAR_MAX)
-				return tolower_l((unsigned char) c, pg_regex_locale);
-#endif
-			return c;
-	}
-	return 0;					/* can't get here, but keep compiler quiet */
-}
-
-
-/*
- * These functions cache the results of probing libc's ctype behavior for
- * all character codes of interest in a given encoding/collation.  The
- * result is provided as a "struct cvec", but notice that the representation
- * is a touch different from a cvec created by regc_cvec.c: we allocate the
- * chrs[] and ranges[] arrays separately from the struct so that we can
- * realloc them larger at need.  This is okay since the cvecs made here
- * should never be freed by freecvec().
- *
- * We use malloc not palloc since we mustn't lose control on out-of-memory;
- * the main regex code expects us to return a failure indication instead.
- */
-
-typedef int (*pg_wc_probefunc) (pg_wchar c);
-
-typedef struct pg_ctype_cache
-{
-	pg_wc_probefunc probefunc;	/* pg_wc_isalpha or a sibling */
-	Oid			collation;		/* collation this entry is for */
-	struct cvec cv;				/* cache entry contents */
-	struct pg_ctype_cache *next;	/* chain link */
-} pg_ctype_cache;
-
-static pg_ctype_cache *pg_ctype_cache_list = NULL;
-
-/*
- * Add a chr or range to pcc->cv; return false if run out of memory
- */
-static bool
-store_match(pg_ctype_cache *pcc, pg_wchar chr1, int nchrs)
-{
-	chr		   *newchrs;
-
-	if (nchrs > 1)
-	{
-		if (pcc->cv.nranges >= pcc->cv.rangespace)
-		{
-			pcc->cv.rangespace *= 2;
-			newchrs = (chr *) realloc(pcc->cv.ranges,
-									  pcc->cv.rangespace * sizeof(chr) * 2);
-			if (newchrs == NULL)
-				return false;
-			pcc->cv.ranges = newchrs;
-		}
-		pcc->cv.ranges[pcc->cv.nranges * 2] = chr1;
-		pcc->cv.ranges[pcc->cv.nranges * 2 + 1] = chr1 + nchrs - 1;
-		pcc->cv.nranges++;
-	}
-	else
-	{
-		assert(nchrs == 1);
-		if (pcc->cv.nchrs >= pcc->cv.chrspace)
-		{
-			pcc->cv.chrspace *= 2;
-			newchrs = (chr *) realloc(pcc->cv.chrs,
-									  pcc->cv.chrspace * sizeof(chr));
-			if (newchrs == NULL)
-				return false;
-			pcc->cv.chrs = newchrs;
-		}
-		pcc->cv.chrs[pcc->cv.nchrs++] = chr1;
-	}
-	return true;
-}
-
-/*
- * Given a probe function (e.g., pg_wc_isalpha) get a struct cvec for all
- * chrs satisfying the probe function.  The active collation is the one
- * previously set by pg_set_regex_collation.  Return NULL if out of memory.
- *
- * Note that the result must not be freed or modified by caller.
- */
-static struct cvec *
-pg_ctype_get_cache(pg_wc_probefunc probefunc)
-{
-	pg_ctype_cache *pcc;
-	pg_wchar	max_chr;
-	pg_wchar	cur_chr;
-	int			nmatches;
-	chr		   *newchrs;
-
-	/*
-	 * Do we already have the answer cached?
-	 */
-	for (pcc = pg_ctype_cache_list; pcc != NULL; pcc = pcc->next)
-	{
-		if (pcc->probefunc == probefunc &&
-			pcc->collation == pg_regex_collation)
-			return &pcc->cv;
-	}
-
-	/*
-	 * Nope, so initialize some workspace ...
-	 */
-	pcc = (pg_ctype_cache *) malloc(sizeof(pg_ctype_cache));
-	if (pcc == NULL)
-		return NULL;
-	pcc->probefunc = probefunc;
-	pcc->collation = pg_regex_collation;
-	pcc->cv.nchrs = 0;
-	pcc->cv.chrspace = 128;
-	pcc->cv.chrs = (chr *) malloc(pcc->cv.chrspace * sizeof(chr));
-	pcc->cv.nranges = 0;
-	pcc->cv.rangespace = 64;
-	pcc->cv.ranges = (chr *) malloc(pcc->cv.rangespace * sizeof(chr) * 2);
-	if (pcc->cv.chrs == NULL || pcc->cv.ranges == NULL)
-		goto out_of_memory;
-
-	/*
-	 * Decide how many character codes we ought to look through.  For C locale
-	 * there's no need to go further than 127.  Otherwise, if the encoding is
-	 * UTF8 go up to 0x7FF, which is a pretty arbitrary cutoff but we cannot
-	 * extend it as far as we'd like (say, 0xFFFF, the end of the Basic
-	 * Multilingual Plane) without creating significant performance issues due
-	 * to too many characters being fed through the colormap code.  This will
-	 * need redesign to fix reasonably, but at least for the moment we have
-	 * all common European languages covered.  Otherwise (not C, not UTF8) go
-	 * up to 255.  These limits are interrelated with restrictions discussed
-	 * at the head of this file.
-	 */
-	switch (pg_regex_strategy)
-	{
-		case PG_REGEX_LOCALE_C:
-			max_chr = (pg_wchar) 127;
-			break;
-		case PG_REGEX_LOCALE_WIDE:
-		case PG_REGEX_LOCALE_WIDE_L:
-			max_chr = (pg_wchar) 0x7FF;
-			break;
-		case PG_REGEX_LOCALE_1BYTE:
-		case PG_REGEX_LOCALE_1BYTE_L:
-			max_chr = (pg_wchar) UCHAR_MAX;
-			break;
-		default:
-			max_chr = 0;		/* can't get here, but keep compiler quiet */
-			break;
-	}
-
-	/*
-	 * And scan 'em ...
-	 */
-	nmatches = 0;				/* number of consecutive matches */
-
-	for (cur_chr = 0; cur_chr <= max_chr; cur_chr++)
-	{
-		if ((*probefunc) (cur_chr))
-			nmatches++;
-		else if (nmatches > 0)
-		{
-			if (!store_match(pcc, cur_chr - nmatches, nmatches))
-				goto out_of_memory;
-			nmatches = 0;
-		}
-	}
-
-	if (nmatches > 0)
-		if (!store_match(pcc, cur_chr - nmatches, nmatches))
-			goto out_of_memory;
-
-	/*
-	 * We might have allocated more memory than needed, if so free it
-	 */
-	if (pcc->cv.nchrs == 0)
-	{
-		free(pcc->cv.chrs);
-		pcc->cv.chrs = NULL;
-		pcc->cv.chrspace = 0;
-	}
-	else if (pcc->cv.nchrs < pcc->cv.chrspace)
-	{
-		newchrs = (chr *) realloc(pcc->cv.chrs,
-								  pcc->cv.nchrs * sizeof(chr));
-		if (newchrs == NULL)
-			goto out_of_memory;
-		pcc->cv.chrs = newchrs;
-		pcc->cv.chrspace = pcc->cv.nchrs;
-	}
-	if (pcc->cv.nranges == 0)
-	{
-		free(pcc->cv.ranges);
-		pcc->cv.ranges = NULL;
-		pcc->cv.rangespace = 0;
-	}
-	else if (pcc->cv.nranges < pcc->cv.rangespace)
-	{
-		newchrs = (chr *) realloc(pcc->cv.ranges,
-								  pcc->cv.nranges * sizeof(chr) * 2);
-		if (newchrs == NULL)
-			goto out_of_memory;
-		pcc->cv.ranges = newchrs;
-		pcc->cv.rangespace = pcc->cv.nranges;
-	}
-
-	/*
-	 * Success, link it into cache chain
-	 */
-	pcc->next = pg_ctype_cache_list;
-	pg_ctype_cache_list = pcc;
-
-	return &pcc->cv;
-
-	/*
-	 * Failure, clean up
-	 */
-out_of_memory:
-	if (pcc->cv.chrs)
-		free(pcc->cv.chrs);
-	if (pcc->cv.ranges)
-		free(pcc->cv.ranges);
-	free(pcc);
-
-	return NULL;
-}
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
deleted file mode 100644
index a165b3b..0000000
--- a/src/backend/regex/regcomp.c
+++ /dev/null
@@ -1,2221 +0,0 @@
-/*
- * re_*comp and friends - compile REs
- * This file #includes several others (see the bottom).
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regcomp.c
- *
- */
-
-#include "regex/regguts.h"
-
-#include "miscadmin.h"			/* needed by rcancelrequested/rstacktoodeep */
-
-/*
- * forward declarations, up here so forward datatypes etc. are defined early
- */
-/* === regcomp.c === */
-static void moresubs(struct vars *, int);
-static int	freev(struct vars *, int);
-static void makesearch(struct vars *, struct nfa *);
-static struct subre *parse(struct vars *, int, int, struct state *, struct state *);
-static struct subre *parsebranch(struct vars *, int, int, struct state *, struct state *, int);
-static void parseqatom(struct vars *, int, int, struct state *, struct state *, struct subre *);
-static void nonword(struct vars *, int, struct state *, struct state *);
-static void word(struct vars *, int, struct state *, struct state *);
-static int	scannum(struct vars *);
-static void repeat(struct vars *, struct state *, struct state *, int, int);
-static void bracket(struct vars *, struct state *, struct state *);
-static void cbracket(struct vars *, struct state *, struct state *);
-static void brackpart(struct vars *, struct state *, struct state *);
-static const chr *scanplain(struct vars *);
-static void onechr(struct vars *, chr, struct state *, struct state *);
-static void dovec(struct vars *, struct cvec *, struct state *, struct state *);
-static void wordchrs(struct vars *);
-static void processlacon(struct vars *, struct state *, struct state *, int,
-			 struct state *, struct state *);
-static struct subre *subre(struct vars *, int, int, struct state *, struct state *);
-static void freesubre(struct vars *, struct subre *);
-static void freesrnode(struct vars *, struct subre *);
-static void optst(struct vars *, struct subre *);
-static int	numst(struct subre *, int);
-static void markst(struct subre *);
-static void cleanst(struct vars *);
-static long nfatree(struct vars *, struct subre *, FILE *);
-static long nfanode(struct vars *, struct subre *, int, FILE *);
-static int	newlacon(struct vars *, struct state *, struct state *, int);
-static void freelacons(struct subre *, int);
-static void rfree(regex_t *);
-static int	rcancelrequested(void);
-static int	rstacktoodeep(void);
-
-#ifdef REG_DEBUG
-static void dump(regex_t *, FILE *);
-static void dumpst(struct subre *, FILE *, int);
-static void stdump(struct subre *, FILE *, int);
-static const char *stid(struct subre *, char *, size_t);
-#endif
-/* === regc_lex.c === */
-static void lexstart(struct vars *);
-static void prefixes(struct vars *);
-static void lexnest(struct vars *, const chr *, const chr *);
-static void lexword(struct vars *);
-static int	next(struct vars *);
-static int	lexescape(struct vars *);
-static chr	lexdigits(struct vars *, int, int, int);
-static int	brenext(struct vars *, chr);
-static void skip(struct vars *);
-static chr	newline(void);
-static chr	chrnamed(struct vars *, const chr *, const chr *, chr);
-
-/* === regc_color.c === */
-static void initcm(struct vars *, struct colormap *);
-static void freecm(struct colormap *);
-static void cmtreefree(struct colormap *, union tree *, int);
-static color setcolor(struct colormap *, chr, pcolor);
-static color maxcolor(struct colormap *);
-static color newcolor(struct colormap *);
-static void freecolor(struct colormap *, pcolor);
-static color pseudocolor(struct colormap *);
-static color subcolor(struct colormap *, chr c);
-static color newsub(struct colormap *, pcolor);
-static void subrange(struct vars *, chr, chr, struct state *, struct state *);
-static void subblock(struct vars *, chr, struct state *, struct state *);
-static void okcolors(struct nfa *, struct colormap *);
-static void colorchain(struct colormap *, struct arc *);
-static void uncolorchain(struct colormap *, struct arc *);
-static void rainbow(struct nfa *, struct colormap *, int, pcolor, struct state *, struct state *);
-static void colorcomplement(struct nfa *, struct colormap *, int, struct state *, struct state *, struct state *);
-
-#ifdef REG_DEBUG
-static void dumpcolors(struct colormap *, FILE *);
-static void fillcheck(struct colormap *, union tree *, int, FILE *);
-static void dumpchr(chr, FILE *);
-#endif
-/* === regc_nfa.c === */
-static struct nfa *newnfa(struct vars *, struct colormap *, struct nfa *);
-static void freenfa(struct nfa *);
-static struct state *newstate(struct nfa *);
-static struct state *newfstate(struct nfa *, int flag);
-static void dropstate(struct nfa *, struct state *);
-static void freestate(struct nfa *, struct state *);
-static void destroystate(struct nfa *, struct state *);
-static void newarc(struct nfa *, int, pcolor, struct state *, struct state *);
-static void createarc(struct nfa *, int, pcolor, struct state *, struct state *);
-static struct arc *allocarc(struct nfa *, struct state *);
-static void freearc(struct nfa *, struct arc *);
-static void changearctarget(struct arc *, struct state *);
-static int	hasnonemptyout(struct state *);
-static struct arc *findarc(struct state *, int, pcolor);
-static void cparc(struct nfa *, struct arc *, struct state *, struct state *);
-static void sortins(struct nfa *, struct state *);
-static int	sortins_cmp(const void *, const void *);
-static void sortouts(struct nfa *, struct state *);
-static int	sortouts_cmp(const void *, const void *);
-static void moveins(struct nfa *, struct state *, struct state *);
-static void copyins(struct nfa *, struct state *, struct state *);
-static void mergeins(struct nfa *, struct state *, struct arc **, int);
-static void moveouts(struct nfa *, struct state *, struct state *);
-static void copyouts(struct nfa *, struct state *, struct state *);
-static void cloneouts(struct nfa *, struct state *, struct state *, struct state *, int);
-static void delsub(struct nfa *, struct state *, struct state *);
-static void deltraverse(struct nfa *, struct state *, struct state *);
-static void dupnfa(struct nfa *, struct state *, struct state *, struct state *, struct state *);
-static void duptraverse(struct nfa *, struct state *, struct state *);
-static void cleartraverse(struct nfa *, struct state *);
-static struct state *single_color_transition(struct state *, struct state *);
-static void specialcolors(struct nfa *);
-static long optimize(struct nfa *, FILE *);
-static void pullback(struct nfa *, FILE *);
-static int	pull(struct nfa *, struct arc *, struct state **);
-static void pushfwd(struct nfa *, FILE *);
-static int	push(struct nfa *, struct arc *, struct state **);
-
-#define INCOMPATIBLE	1		/* destroys arc */
-#define SATISFIED	2			/* constraint satisfied */
-#define COMPATIBLE	3			/* compatible but not satisfied yet */
-static int	combine(struct arc *, struct arc *);
-static void fixempties(struct nfa *, FILE *);
-static struct state *emptyreachable(struct nfa *, struct state *,
-			   struct state *, struct arc **);
-static int	isconstraintarc(struct arc *);
-static int	hasconstraintout(struct state *);
-static void fixconstraintloops(struct nfa *, FILE *);
-static int	findconstraintloop(struct nfa *, struct state *);
-static void breakconstraintloop(struct nfa *, struct state *);
-static void clonesuccessorstates(struct nfa *, struct state *, struct state *,
-					 struct state *, struct arc *,
-					 char *, char *, int);
-static void cleanup(struct nfa *);
-static void markreachable(struct nfa *, struct state *, struct state *, struct state *);
-static void markcanreach(struct nfa *, struct state *, struct state *, struct state *);
-static long analyze(struct nfa *);
-static void compact(struct nfa *, struct cnfa *);
-static void carcsort(struct carc *, size_t);
-static int	carc_cmp(const void *, const void *);
-static void freecnfa(struct cnfa *);
-static void dumpnfa(struct nfa *, FILE *);
-
-#ifdef REG_DEBUG
-static void dumpstate(struct state *, FILE *);
-static void dumparcs(struct state *, FILE *);
-static void dumparc(struct arc *, struct state *, FILE *);
-static void dumpcnfa(struct cnfa *, FILE *);
-static void dumpcstate(int, struct cnfa *, FILE *);
-#endif
-/* === regc_cvec.c === */
-static struct cvec *newcvec(int, int);
-static struct cvec *clearcvec(struct cvec *);
-static void addchr(struct cvec *, chr);
-static void addrange(struct cvec *, chr, chr);
-static struct cvec *getcvec(struct vars *, int, int);
-static void freecvec(struct cvec *);
-
-/* === regc_pg_locale.c === */
-static int	pg_wc_isdigit(pg_wchar c);
-static int	pg_wc_isalpha(pg_wchar c);
-static int	pg_wc_isalnum(pg_wchar c);
-static int	pg_wc_isupper(pg_wchar c);
-static int	pg_wc_islower(pg_wchar c);
-static int	pg_wc_isgraph(pg_wchar c);
-static int	pg_wc_isprint(pg_wchar c);
-static int	pg_wc_ispunct(pg_wchar c);
-static int	pg_wc_isspace(pg_wchar c);
-static pg_wchar pg_wc_toupper(pg_wchar c);
-static pg_wchar pg_wc_tolower(pg_wchar c);
-
-/* === regc_locale.c === */
-static celt element(struct vars *, const chr *, const chr *);
-static struct cvec *range(struct vars *, celt, celt, int);
-static int	before(celt, celt);
-static struct cvec *eclass(struct vars *, celt, int);
-static struct cvec *cclass(struct vars *, const chr *, const chr *, int);
-static struct cvec *allcases(struct vars *, chr);
-static int	cmp(const chr *, const chr *, size_t);
-static int	casecmp(const chr *, const chr *, size_t);
-
-
-/* internal variables, bundled for easy passing around */
-struct vars
-{
-	regex_t    *re;
-	const chr  *now;			/* scan pointer into string */
-	const chr  *stop;			/* end of string */
-	const chr  *savenow;		/* saved now and stop for "subroutine call" */
-	const chr  *savestop;
-	int			err;			/* error code (0 if none) */
-	int			cflags;			/* copy of compile flags */
-	int			lasttype;		/* type of previous token */
-	int			nexttype;		/* type of next token */
-	chr			nextvalue;		/* value (if any) of next token */
-	int			lexcon;			/* lexical context type (see lex.c) */
-	int			nsubexp;		/* subexpression count */
-	struct subre **subs;		/* subRE pointer vector */
-	size_t		nsubs;			/* length of vector */
-	struct subre *sub10[10];	/* initial vector, enough for most */
-	struct nfa *nfa;			/* the NFA */
-	struct colormap *cm;		/* character color map */
-	color		nlcolor;		/* color of newline */
-	struct state *wordchrs;		/* state in nfa holding word-char outarcs */
-	struct subre *tree;			/* subexpression tree */
-	struct subre *treechain;	/* all tree nodes allocated */
-	struct subre *treefree;		/* any free tree nodes */
-	int			ntree;			/* number of tree nodes, plus one */
-	struct cvec *cv;			/* interface cvec */
-	struct cvec *cv2;			/* utility cvec */
-	struct subre *lacons;		/* lookaround-constraint vector */
-	int			nlacons;		/* size of lacons[]; note that only slots
-								 * numbered 1 .. nlacons-1 are used */
-	size_t		spaceused;		/* approx. space used for compilation */
-};
-
-/* parsing macros; most know that `v' is the struct vars pointer */
-#define NEXT()	(next(v))		/* advance by one token */
-#define SEE(t)	(v->nexttype == (t))	/* is next token this? */
-#define EAT(t)	(SEE(t) && next(v))		/* if next is this, swallow it */
-#define VISERR(vv)	((vv)->err != 0)	/* have we seen an error yet? */
-#define ISERR() VISERR(v)
-#define VERR(vv,e)	((vv)->nexttype = EOS, \
-					 (vv)->err = ((vv)->err ? (vv)->err : (e)))
-#define ERR(e)	VERR(v, e)		/* record an error */
-#define NOERR() {if (ISERR()) return;}	/* if error seen, return */
-#define NOERRN()	{if (ISERR()) return NULL;} /* NOERR with retval */
-#define NOERRZ()	{if (ISERR()) return 0;}	/* NOERR with retval */
-#define INSIST(c, e) do { if (!(c)) ERR(e); } while (0) /* error if c false */
-#define NOTE(b) (v->re->re_info |= (b)) /* note visible condition */
-#define EMPTYARC(x, y)	newarc(v->nfa, EMPTY, 0, x, y)
-
-/* token type codes, some also used as NFA arc types */
-#define EMPTY	'n'				/* no token present */
-#define EOS 'e'					/* end of string */
-#define PLAIN	'p'				/* ordinary character */
-#define DIGIT	'd'				/* digit (in bound) */
-#define BACKREF 'b'				/* back reference */
-#define COLLEL	'I'				/* start of [. */
-#define ECLASS	'E'				/* start of [= */
-#define CCLASS	'C'				/* start of [: */
-#define END 'X'					/* end of [. [= [: */
-#define RANGE	'R'				/* - within [] which might be range delim. */
-#define LACON	'L'				/* lookaround constraint subRE */
-#define AHEAD	'a'				/* color-lookahead arc */
-#define BEHIND	'r'				/* color-lookbehind arc */
-#define WBDRY	'w'				/* word boundary constraint */
-#define NWBDRY	'W'				/* non-word-boundary constraint */
-#define SBEGIN	'A'				/* beginning of string (even if not BOL) */
-#define SEND	'Z'				/* end of string (even if not EOL) */
-#define PREFER	'P'				/* length preference */
-
-/* is an arc colored, and hence on a color chain? */
-#define COLORED(a) \
-	((a)->type == PLAIN || (a)->type == AHEAD || (a)->type == BEHIND)
-
-
-/* static function list */
-static const struct fns functions = {
-	rfree,						/* regfree insides */
-	rcancelrequested,			/* check for cancel request */
-	rstacktoodeep				/* check for stack getting dangerously deep */
-};
-
-
-
-/*
- * pg_regcomp - compile regular expression
- *
- * Note: on failure, no resources remain allocated, so pg_regfree()
- * need not be applied to re.
- */
-int
-pg_regcomp(regex_t *re,
-		   const chr *string,
-		   size_t len,
-		   int flags,
-		   Oid collation)
-{
-	struct vars var;
-	struct vars *v = &var;
-	struct guts *g;
-	int			i;
-	size_t		j;
-
-#ifdef REG_DEBUG
-	FILE	   *debug = (flags & REG_PROGRESS) ? stdout : (FILE *) NULL;
-#else
-	FILE	   *debug = (FILE *) NULL;
-#endif
-
-#define  CNOERR()	 { if (ISERR()) return freev(v, v->err); }
-
-	/* sanity checks */
-
-	if (re == NULL || string == NULL)
-		return REG_INVARG;
-	if ((flags & REG_QUOTE) &&
-		(flags & (REG_ADVANCED | REG_EXPANDED | REG_NEWLINE)))
-		return REG_INVARG;
-	if (!(flags & REG_EXTENDED) && (flags & REG_ADVF))
-		return REG_INVARG;
-
-	/* Initialize locale-dependent support */
-	pg_set_regex_collation(collation);
-
-	/* initial setup (after which freev() is callable) */
-	v->re = re;
-	v->now = string;
-	v->stop = v->now + len;
-	v->savenow = v->savestop = NULL;
-	v->err = 0;
-	v->cflags = flags;
-	v->nsubexp = 0;
-	v->subs = v->sub10;
-	v->nsubs = 10;
-	for (j = 0; j < v->nsubs; j++)
-		v->subs[j] = NULL;
-	v->nfa = NULL;
-	v->cm = NULL;
-	v->nlcolor = COLORLESS;
-	v->wordchrs = NULL;
-	v->tree = NULL;
-	v->treechain = NULL;
-	v->treefree = NULL;
-	v->cv = NULL;
-	v->cv2 = NULL;
-	v->lacons = NULL;
-	v->nlacons = 0;
-	v->spaceused = 0;
-	re->re_magic = REMAGIC;
-	re->re_info = 0;			/* bits get set during parse */
-	re->re_csize = sizeof(chr);
-	re->re_collation = collation;
-	re->re_guts = NULL;
-	re->re_fns = VS(&functions);
-
-	/* more complex setup, malloced things */
-	re->re_guts = VS(MALLOC(sizeof(struct guts)));
-	if (re->re_guts == NULL)
-		return freev(v, REG_ESPACE);
-	g = (struct guts *) re->re_guts;
-	g->tree = NULL;
-	initcm(v, &g->cmap);
-	v->cm = &g->cmap;
-	g->lacons = NULL;
-	g->nlacons = 0;
-	ZAPCNFA(g->search);
-	v->nfa = newnfa(v, v->cm, (struct nfa *) NULL);
-	CNOERR();
-	/* set up a reasonably-sized transient cvec for getcvec usage */
-	v->cv = newcvec(100, 20);
-	if (v->cv == NULL)
-		return freev(v, REG_ESPACE);
-
-	/* parsing */
-	lexstart(v);				/* also handles prefixes */
-	if ((v->cflags & REG_NLSTOP) || (v->cflags & REG_NLANCH))
-	{
-		/* assign newline a unique color */
-		v->nlcolor = subcolor(v->cm, newline());
-		okcolors(v->nfa, v->cm);
-	}
-	CNOERR();
-	v->tree = parse(v, EOS, PLAIN, v->nfa->init, v->nfa->final);
-	assert(SEE(EOS));			/* even if error; ISERR() => SEE(EOS) */
-	CNOERR();
-	assert(v->tree != NULL);
-
-	/* finish setup of nfa and its subre tree */
-	specialcolors(v->nfa);
-	CNOERR();
-#ifdef REG_DEBUG
-	if (debug != NULL)
-	{
-		fprintf(debug, "\n\n\n========= RAW ==========\n");
-		dumpnfa(v->nfa, debug);
-		dumpst(v->tree, debug, 1);
-	}
-#endif
-	optst(v, v->tree);
-	v->ntree = numst(v->tree, 1);
-	markst(v->tree);
-	cleanst(v);
-#ifdef REG_DEBUG
-	if (debug != NULL)
-	{
-		fprintf(debug, "\n\n\n========= TREE FIXED ==========\n");
-		dumpst(v->tree, debug, 1);
-	}
-#endif
-
-	/* build compacted NFAs for tree and lacons */
-	re->re_info |= nfatree(v, v->tree, debug);
-	CNOERR();
-	assert(v->nlacons == 0 || v->lacons != NULL);
-	for (i = 1; i < v->nlacons; i++)
-	{
-		struct subre *lasub = &v->lacons[i];
-
-#ifdef REG_DEBUG
-		if (debug != NULL)
-			fprintf(debug, "\n\n\n========= LA%d ==========\n", i);
-#endif
-
-		/* Prepend .* to pattern if it's a lookbehind LACON */
-		nfanode(v, lasub, !LATYPE_IS_AHEAD(lasub->subno), debug);
-	}
-	CNOERR();
-	if (v->tree->flags & SHORTER)
-		NOTE(REG_USHORTEST);
-
-	/* build compacted NFAs for tree, lacons, fast search */
-#ifdef REG_DEBUG
-	if (debug != NULL)
-		fprintf(debug, "\n\n\n========= SEARCH ==========\n");
-#endif
-	/* can sacrifice main NFA now, so use it as work area */
-	(DISCARD) optimize(v->nfa, debug);
-	CNOERR();
-	makesearch(v, v->nfa);
-	CNOERR();
-	compact(v->nfa, &g->search);
-	CNOERR();
-
-	/* looks okay, package it up */
-	re->re_nsub = v->nsubexp;
-	v->re = NULL;				/* freev no longer frees re */
-	g->magic = GUTSMAGIC;
-	g->cflags = v->cflags;
-	g->info = re->re_info;
-	g->nsub = re->re_nsub;
-	g->tree = v->tree;
-	v->tree = NULL;
-	g->ntree = v->ntree;
-	g->compare = (v->cflags & REG_ICASE) ? casecmp : cmp;
-	g->lacons = v->lacons;
-	v->lacons = NULL;
-	g->nlacons = v->nlacons;
-
-#ifdef REG_DEBUG
-	if (flags & REG_DUMP)
-		dump(re, stdout);
-#endif
-
-	assert(v->err == 0);
-	return freev(v, 0);
-}
-
-/*
- * moresubs - enlarge subRE vector
- */
-static void
-moresubs(struct vars * v,
-		 int wanted)			/* want enough room for this one */
-{
-	struct subre **p;
-	size_t		n;
-
-	assert(wanted > 0 && (size_t) wanted >= v->nsubs);
-	n = (size_t) wanted *3 / 2 + 1;
-
-	if (v->subs == v->sub10)
-	{
-		p = (struct subre **) MALLOC(n * sizeof(struct subre *));
-		if (p != NULL)
-			memcpy(VS(p), VS(v->subs),
-				   v->nsubs * sizeof(struct subre *));
-	}
-	else
-		p = (struct subre **) REALLOC(v->subs, n * sizeof(struct subre *));
-	if (p == NULL)
-	{
-		ERR(REG_ESPACE);
-		return;
-	}
-	v->subs = p;
-	for (p = &v->subs[v->nsubs]; v->nsubs < n; p++, v->nsubs++)
-		*p = NULL;
-	assert(v->nsubs == n);
-	assert((size_t) wanted < v->nsubs);
-}
-
-/*
- * freev - free vars struct's substructures where necessary
- *
- * Optionally does error-number setting, and always returns error code
- * (if any), to make error-handling code terser.
- */
-static int
-freev(struct vars * v,
-	  int err)
-{
-	if (v->re != NULL)
-		rfree(v->re);
-	if (v->subs != v->sub10)
-		FREE(v->subs);
-	if (v->nfa != NULL)
-		freenfa(v->nfa);
-	if (v->tree != NULL)
-		freesubre(v, v->tree);
-	if (v->treechain != NULL)
-		cleanst(v);
-	if (v->cv != NULL)
-		freecvec(v->cv);
-	if (v->cv2 != NULL)
-		freecvec(v->cv2);
-	if (v->lacons != NULL)
-		freelacons(v->lacons, v->nlacons);
-	ERR(err);					/* nop if err==0 */
-
-	return v->err;
-}
-
-/*
- * makesearch - turn an NFA into a search NFA (implicit prepend of .*?)
- * NFA must have been optimize()d already.
- */
-static void
-makesearch(struct vars * v,
-		   struct nfa * nfa)
-{
-	struct arc *a;
-	struct arc *b;
-	struct state *pre = nfa->pre;
-	struct state *s;
-	struct state *s2;
-	struct state *slist;
-
-	/* no loops are needed if it's anchored */
-	for (a = pre->outs; a != NULL; a = a->outchain)
-	{
-		assert(a->type == PLAIN);
-		if (a->co != nfa->bos[0] && a->co != nfa->bos[1])
-			break;
-	}
-	if (a != NULL)
-	{
-		/* add implicit .* in front */
-		rainbow(nfa, v->cm, PLAIN, COLORLESS, pre, pre);
-
-		/* and ^* and \A* too -- not always necessary, but harmless */
-		newarc(nfa, PLAIN, nfa->bos[0], pre, pre);
-		newarc(nfa, PLAIN, nfa->bos[1], pre, pre);
-	}
-
-	/*
-	 * Now here's the subtle part.  Because many REs have no lookback
-	 * constraints, often knowing when you were in the pre state tells you
-	 * little; it's the next state(s) that are informative.  But some of them
-	 * may have other inarcs, i.e. it may be possible to make actual progress
-	 * and then return to one of them.  We must de-optimize such cases,
-	 * splitting each such state into progress and no-progress states.
-	 */
-
-	/* first, make a list of the states reachable from pre and elsewhere */
-	slist = NULL;
-	for (a = pre->outs; a != NULL; a = a->outchain)
-	{
-		s = a->to;
-		for (b = s->ins; b != NULL; b = b->inchain)
-		{
-			if (b->from != pre)
-				break;
-		}
-
-		/*
-		 * We want to mark states as being in the list already by having non
-		 * NULL tmp fields, but we can't just store the old slist value in tmp
-		 * because that doesn't work for the first such state.  Instead, the
-		 * first list entry gets its own address in tmp.
-		 */
-		if (b != NULL && s->tmp == NULL)
-		{
-			s->tmp = (slist != NULL) ? slist : s;
-			slist = s;
-		}
-	}
-
-	/* do the splits */
-	for (s = slist; s != NULL; s = s2)
-	{
-		s2 = newstate(nfa);
-		NOERR();
-		copyouts(nfa, s, s2);
-		NOERR();
-		for (a = s->ins; a != NULL; a = b)
-		{
-			b = a->inchain;
-			if (a->from != pre)
-			{
-				cparc(nfa, a, a->from, s2);
-				freearc(nfa, a);
-			}
-		}
-		s2 = (s->tmp != s) ? s->tmp : NULL;
-		s->tmp = NULL;			/* clean up while we're at it */
-	}
-}
-
-/*
- * parse - parse an RE
- *
- * This is actually just the top level, which parses a bunch of branches
- * tied together with '|'.  They appear in the tree as the left children
- * of a chain of '|' subres.
- */
-static struct subre *
-parse(struct vars * v,
-	  int stopper,				/* EOS or ')' */
-	  int type,					/* LACON (lookaround subRE) or PLAIN */
-	  struct state * init,		/* initial state */
-	  struct state * final)		/* final state */
-{
-	struct state *left;			/* scaffolding for branch */
-	struct state *right;
-	struct subre *branches;		/* top level */
-	struct subre *branch;		/* current branch */
-	struct subre *t;			/* temporary */
-	int			firstbranch;	/* is this the first branch? */
-
-	assert(stopper == ')' || stopper == EOS);
-
-	branches = subre(v, '|', LONGER, init, final);
-	NOERRN();
-	branch = branches;
-	firstbranch = 1;
-	do
-	{							/* a branch */
-		if (!firstbranch)
-		{
-			/* need a place to hang it */
-			branch->right = subre(v, '|', LONGER, init, final);
-			NOERRN();
-			branch = branch->right;
-		}
-		firstbranch = 0;
-		left = newstate(v->nfa);
-		right = newstate(v->nfa);
-		NOERRN();
-		EMPTYARC(init, left);
-		EMPTYARC(right, final);
-		NOERRN();
-		branch->left = parsebranch(v, stopper, type, left, right, 0);
-		NOERRN();
-		branch->flags |= UP(branch->flags | branch->left->flags);
-		if ((branch->flags & ~branches->flags) != 0)	/* new flags */
-			for (t = branches; t != branch; t = t->right)
-				t->flags |= branch->flags;
-	} while (EAT('|'));
-	assert(SEE(stopper) || SEE(EOS));
-
-	if (!SEE(stopper))
-	{
-		assert(stopper == ')' && SEE(EOS));
-		ERR(REG_EPAREN);
-	}
-
-	/* optimize out simple cases */
-	if (branch == branches)
-	{							/* only one branch */
-		assert(branch->right == NULL);
-		t = branch->left;
-		branch->left = NULL;
-		freesubre(v, branches);
-		branches = t;
-	}
-	else if (!MESSY(branches->flags))
-	{							/* no interesting innards */
-		freesubre(v, branches->left);
-		branches->left = NULL;
-		freesubre(v, branches->right);
-		branches->right = NULL;
-		branches->op = '=';
-	}
-
-	return branches;
-}
-
-/*
- * parsebranch - parse one branch of an RE
- *
- * This mostly manages concatenation, working closely with parseqatom().
- * Concatenated things are bundled up as much as possible, with separate
- * ',' nodes introduced only when necessary due to substructure.
- */
-static struct subre *
-parsebranch(struct vars * v,
-			int stopper,		/* EOS or ')' */
-			int type,			/* LACON (lookaround subRE) or PLAIN */
-			struct state * left,	/* leftmost state */
-			struct state * right,		/* rightmost state */
-			int partial)		/* is this only part of a branch? */
-{
-	struct state *lp;			/* left end of current construct */
-	int			seencontent;	/* is there anything in this branch yet? */
-	struct subre *t;
-
-	lp = left;
-	seencontent = 0;
-	t = subre(v, '=', 0, left, right);	/* op '=' is tentative */
-	NOERRN();
-	while (!SEE('|') && !SEE(stopper) && !SEE(EOS))
-	{
-		if (seencontent)
-		{						/* implicit concat operator */
-			lp = newstate(v->nfa);
-			NOERRN();
-			moveins(v->nfa, right, lp);
-		}
-		seencontent = 1;
-
-		/* NB, recursion in parseqatom() may swallow rest of branch */
-		parseqatom(v, stopper, type, lp, right, t);
-		NOERRN();
-	}
-
-	if (!seencontent)
-	{							/* empty branch */
-		if (!partial)
-			NOTE(REG_UUNSPEC);
-		assert(lp == left);
-		EMPTYARC(left, right);
-	}
-
-	return t;
-}
-
-/*
- * parseqatom - parse one quantified atom or constraint of an RE
- *
- * The bookkeeping near the end cooperates very closely with parsebranch();
- * in particular, it contains a recursion that can involve parsing the rest
- * of the branch, making this function's name somewhat inaccurate.
- */
-static void
-parseqatom(struct vars * v,
-		   int stopper,			/* EOS or ')' */
-		   int type,			/* LACON (lookaround subRE) or PLAIN */
-		   struct state * lp,	/* left state to hang it on */
-		   struct state * rp,	/* right state to hang it on */
-		   struct subre * top)	/* subtree top */
-{
-	struct state *s;			/* temporaries for new states */
-	struct state *s2;
-
-#define  ARCV(t, val)	 newarc(v->nfa, t, val, lp, rp)
-	int			m,
-				n;
-	struct subre *atom;			/* atom's subtree */
-	struct subre *t;
-	int			cap;			/* capturing parens? */
-	int			latype;			/* lookaround constraint type */
-	int			subno;			/* capturing-parens or backref number */
-	int			atomtype;
-	int			qprefer;		/* quantifier short/long preference */
-	int			f;
-	struct subre **atomp;		/* where the pointer to atom is */
-
-	/* initial bookkeeping */
-	atom = NULL;
-	assert(lp->nouts == 0);		/* must string new code */
-	assert(rp->nins == 0);		/* between lp and rp */
-	subno = 0;					/* just to shut lint up */
-
-	/* an atom or constraint... */
-	atomtype = v->nexttype;
-	switch (atomtype)
-	{
-			/* first, constraints, which end by returning */
-		case '^':
-			ARCV('^', 1);
-			if (v->cflags & REG_NLANCH)
-				ARCV(BEHIND, v->nlcolor);
-			NEXT();
-			return;
-			break;
-		case '$':
-			ARCV('$', 1);
-			if (v->cflags & REG_NLANCH)
-				ARCV(AHEAD, v->nlcolor);
-			NEXT();
-			return;
-			break;
-		case SBEGIN:
-			ARCV('^', 1);		/* BOL */
-			ARCV('^', 0);		/* or BOS */
-			NEXT();
-			return;
-			break;
-		case SEND:
-			ARCV('$', 1);		/* EOL */
-			ARCV('$', 0);		/* or EOS */
-			NEXT();
-			return;
-			break;
-		case '<':
-			wordchrs(v);		/* does NEXT() */
-			s = newstate(v->nfa);
-			NOERR();
-			nonword(v, BEHIND, lp, s);
-			word(v, AHEAD, s, rp);
-			return;
-			break;
-		case '>':
-			wordchrs(v);		/* does NEXT() */
-			s = newstate(v->nfa);
-			NOERR();
-			word(v, BEHIND, lp, s);
-			nonword(v, AHEAD, s, rp);
-			return;
-			break;
-		case WBDRY:
-			wordchrs(v);		/* does NEXT() */
-			s = newstate(v->nfa);
-			NOERR();
-			nonword(v, BEHIND, lp, s);
-			word(v, AHEAD, s, rp);
-			s = newstate(v->nfa);
-			NOERR();
-			word(v, BEHIND, lp, s);
-			nonword(v, AHEAD, s, rp);
-			return;
-			break;
-		case NWBDRY:
-			wordchrs(v);		/* does NEXT() */
-			s = newstate(v->nfa);
-			NOERR();
-			word(v, BEHIND, lp, s);
-			word(v, AHEAD, s, rp);
-			s = newstate(v->nfa);
-			NOERR();
-			nonword(v, BEHIND, lp, s);
-			nonword(v, AHEAD, s, rp);
-			return;
-			break;
-		case LACON:				/* lookaround constraint */
-			latype = v->nextvalue;
-			NEXT();
-			s = newstate(v->nfa);
-			s2 = newstate(v->nfa);
-			NOERR();
-			t = parse(v, ')', LACON, s, s2);
-			freesubre(v, t);	/* internal structure irrelevant */
-			NOERR();
-			assert(SEE(')'));
-			NEXT();
-			processlacon(v, s, s2, latype, lp, rp);
-			return;
-			break;
-			/* then errors, to get them out of the way */
-		case '*':
-		case '+':
-		case '?':
-		case '{':
-			ERR(REG_BADRPT);
-			return;
-			break;
-		default:
-			ERR(REG_ASSERT);
-			return;
-			break;
-			/* then plain characters, and minor variants on that theme */
-		case ')':				/* unbalanced paren */
-			if ((v->cflags & REG_ADVANCED) != REG_EXTENDED)
-			{
-				ERR(REG_EPAREN);
-				return;
-			}
-			/* legal in EREs due to specification botch */
-			NOTE(REG_UPBOTCH);
-			/* fallthrough into case PLAIN */
-		case PLAIN:
-			onechr(v, v->nextvalue, lp, rp);
-			okcolors(v->nfa, v->cm);
-			NOERR();
-			NEXT();
-			break;
-		case '[':
-			if (v->nextvalue == 1)
-				bracket(v, lp, rp);
-			else
-				cbracket(v, lp, rp);
-			assert(SEE(']') || ISERR());
-			NEXT();
-			break;
-		case '.':
-			rainbow(v->nfa, v->cm, PLAIN,
-					(v->cflags & REG_NLSTOP) ? v->nlcolor : COLORLESS,
-					lp, rp);
-			NEXT();
-			break;
-			/* and finally the ugly stuff */
-		case '(':				/* value flags as capturing or non */
-			cap = (type == LACON) ? 0 : v->nextvalue;
-			if (cap)
-			{
-				v->nsubexp++;
-				subno = v->nsubexp;
-				if ((size_t) subno >= v->nsubs)
-					moresubs(v, subno);
-				assert((size_t) subno < v->nsubs);
-			}
-			else
-				atomtype = PLAIN;		/* something that's not '(' */
-			NEXT();
-			/* need new endpoints because tree will contain pointers */
-			s = newstate(v->nfa);
-			s2 = newstate(v->nfa);
-			NOERR();
-			EMPTYARC(lp, s);
-			EMPTYARC(s2, rp);
-			NOERR();
-			atom = parse(v, ')', type, s, s2);
-			assert(SEE(')') || ISERR());
-			NEXT();
-			NOERR();
-			if (cap)
-			{
-				v->subs[subno] = atom;
-				t = subre(v, '(', atom->flags | CAP, lp, rp);
-				NOERR();
-				t->subno = subno;
-				t->left = atom;
-				atom = t;
-			}
-			/* postpone everything else pending possible {0} */
-			break;
-		case BACKREF:			/* the Feature From The Black Lagoon */
-			INSIST(type != LACON, REG_ESUBREG);
-			INSIST(v->nextvalue < v->nsubs, REG_ESUBREG);
-			INSIST(v->subs[v->nextvalue] != NULL, REG_ESUBREG);
-			NOERR();
-			assert(v->nextvalue > 0);
-			atom = subre(v, 'b', BACKR, lp, rp);
-			NOERR();
-			subno = v->nextvalue;
-			atom->subno = subno;
-			EMPTYARC(lp, rp);	/* temporarily, so there's something */
-			NEXT();
-			break;
-	}
-
-	/* ...and an atom may be followed by a quantifier */
-	switch (v->nexttype)
-	{
-		case '*':
-			m = 0;
-			n = DUPINF;
-			qprefer = (v->nextvalue) ? LONGER : SHORTER;
-			NEXT();
-			break;
-		case '+':
-			m = 1;
-			n = DUPINF;
-			qprefer = (v->nextvalue) ? LONGER : SHORTER;
-			NEXT();
-			break;
-		case '?':
-			m = 0;
-			n = 1;
-			qprefer = (v->nextvalue) ? LONGER : SHORTER;
-			NEXT();
-			break;
-		case '{':
-			NEXT();
-			m = scannum(v);
-			if (EAT(','))
-			{
-				if (SEE(DIGIT))
-					n = scannum(v);
-				else
-					n = DUPINF;
-				if (m > n)
-				{
-					ERR(REG_BADBR);
-					return;
-				}
-				/* {m,n} exercises preference, even if it's {m,m} */
-				qprefer = (v->nextvalue) ? LONGER : SHORTER;
-			}
-			else
-			{
-				n = m;
-				/* {m} passes operand's preference through */
-				qprefer = 0;
-			}
-			if (!SEE('}'))
-			{					/* catches errors too */
-				ERR(REG_BADBR);
-				return;
-			}
-			NEXT();
-			break;
-		default:				/* no quantifier */
-			m = n = 1;
-			qprefer = 0;
-			break;
-	}
-
-	/* annoying special case:  {0} or {0,0} cancels everything */
-	if (m == 0 && n == 0)
-	{
-		if (atom != NULL)
-			freesubre(v, atom);
-		if (atomtype == '(')
-			v->subs[subno] = NULL;
-		delsub(v->nfa, lp, rp);
-		EMPTYARC(lp, rp);
-		return;
-	}
-
-	/* if not a messy case, avoid hard part */
-	assert(!MESSY(top->flags));
-	f = top->flags | qprefer | ((atom != NULL) ? atom->flags : 0);
-	if (atomtype != '(' && atomtype != BACKREF && !MESSY(UP(f)))
-	{
-		if (!(m == 1 && n == 1))
-			repeat(v, lp, rp, m, n);
-		if (atom != NULL)
-			freesubre(v, atom);
-		top->flags = f;
-		return;
-	}
-
-	/*
-	 * hard part:  something messy
-	 *
-	 * That is, capturing parens, back reference, short/long clash, or an atom
-	 * with substructure containing one of those.
-	 */
-
-	/* now we'll need a subre for the contents even if they're boring */
-	if (atom == NULL)
-	{
-		atom = subre(v, '=', 0, lp, rp);
-		NOERR();
-	}
-
-	/*----------
-	 * Prepare a general-purpose state skeleton.
-	 *
-	 * In the no-backrefs case, we want this:
-	 *
-	 * [lp] ---> [s] ---prefix---> [begin] ---atom---> [end] ---rest---> [rp]
-	 *
-	 * where prefix is some repetitions of atom.  In the general case we need
-	 *
-	 * [lp] ---> [s] ---iterator---> [s2] ---rest---> [rp]
-	 *
-	 * where the iterator wraps around [begin] ---atom---> [end]
-	 *
-	 * We make the s state here for both cases; s2 is made below if needed
-	 *----------
-	 */
-	s = newstate(v->nfa);		/* first, new endpoints for the atom */
-	s2 = newstate(v->nfa);
-	NOERR();
-	moveouts(v->nfa, lp, s);
-	moveins(v->nfa, rp, s2);
-	NOERR();
-	atom->begin = s;
-	atom->end = s2;
-	s = newstate(v->nfa);		/* set up starting state */
-	NOERR();
-	EMPTYARC(lp, s);
-	NOERR();
-
-	/* break remaining subRE into x{...} and what follows */
-	t = subre(v, '.', COMBINE(qprefer, atom->flags), lp, rp);
-	NOERR();
-	t->left = atom;
-	atomp = &t->left;
-
-	/* here we should recurse... but we must postpone that to the end */
-
-	/* split top into prefix and remaining */
-	assert(top->op == '=' && top->left == NULL && top->right == NULL);
-	top->left = subre(v, '=', top->flags, top->begin, lp);
-	NOERR();
-	top->op = '.';
-	top->right = t;
-
-	/* if it's a backref, now is the time to replicate the subNFA */
-	if (atomtype == BACKREF)
-	{
-		assert(atom->begin->nouts == 1);		/* just the EMPTY */
-		delsub(v->nfa, atom->begin, atom->end);
-		assert(v->subs[subno] != NULL);
-
-		/*
-		 * And here's why the recursion got postponed: it must wait until the
-		 * skeleton is filled in, because it may hit a backref that wants to
-		 * copy the filled-in skeleton.
-		 */
-		dupnfa(v->nfa, v->subs[subno]->begin, v->subs[subno]->end,
-			   atom->begin, atom->end);
-		NOERR();
-	}
-
-	/*
-	 * It's quantifier time.  If the atom is just a backref, we'll let it deal
-	 * with quantifiers internally.
-	 */
-	if (atomtype == BACKREF)
-	{
-		/* special case:  backrefs have internal quantifiers */
-		EMPTYARC(s, atom->begin);		/* empty prefix */
-		/* just stuff everything into atom */
-		repeat(v, atom->begin, atom->end, m, n);
-		atom->min = (short) m;
-		atom->max = (short) n;
-		atom->flags |= COMBINE(qprefer, atom->flags);
-		/* rest of branch can be strung starting from atom->end */
-		s2 = atom->end;
-	}
-	else if (m == 1 && n == 1)
-	{
-		/* no/vacuous quantifier:  done */
-		EMPTYARC(s, atom->begin);		/* empty prefix */
-		/* rest of branch can be strung starting from atom->end */
-		s2 = atom->end;
-	}
-	else if (m > 0 && !(atom->flags & BACKR))
-	{
-		/*
-		 * If there's no backrefs involved, we can turn x{m,n} into
-		 * x{m-1,n-1}x, with capturing parens in only the second x.  This is
-		 * valid because we only care about capturing matches from the final
-		 * iteration of the quantifier.  It's a win because we can implement
-		 * the backref-free left side as a plain DFA node, since we don't
-		 * really care where its submatches are.
-		 */
-		dupnfa(v->nfa, atom->begin, atom->end, s, atom->begin);
-		assert(m >= 1 && m != DUPINF && n >= 1);
-		repeat(v, s, atom->begin, m - 1, (n == DUPINF) ? n : n - 1);
-		f = COMBINE(qprefer, atom->flags);
-		t = subre(v, '.', f, s, atom->end);		/* prefix and atom */
-		NOERR();
-		t->left = subre(v, '=', PREF(f), s, atom->begin);
-		NOERR();
-		t->right = atom;
-		*atomp = t;
-		/* rest of branch can be strung starting from atom->end */
-		s2 = atom->end;
-	}
-	else
-	{
-		/* general case: need an iteration node */
-		s2 = newstate(v->nfa);
-		NOERR();
-		moveouts(v->nfa, atom->end, s2);
-		NOERR();
-		dupnfa(v->nfa, atom->begin, atom->end, s, s2);
-		repeat(v, s, s2, m, n);
-		f = COMBINE(qprefer, atom->flags);
-		t = subre(v, '*', f, s, s2);
-		NOERR();
-		t->min = (short) m;
-		t->max = (short) n;
-		t->left = atom;
-		*atomp = t;
-		/* rest of branch is to be strung from iteration's end state */
-	}
-
-	/* and finally, look after that postponed recursion */
-	t = top->right;
-	if (!(SEE('|') || SEE(stopper) || SEE(EOS)))
-		t->right = parsebranch(v, stopper, type, s2, rp, 1);
-	else
-	{
-		EMPTYARC(s2, rp);
-		t->right = subre(v, '=', 0, s2, rp);
-	}
-	NOERR();
-	assert(SEE('|') || SEE(stopper) || SEE(EOS));
-	t->flags |= COMBINE(t->flags, t->right->flags);
-	top->flags |= COMBINE(top->flags, t->flags);
-}
-
-/*
- * nonword - generate arcs for non-word-character ahead or behind
- */
-static void
-nonword(struct vars * v,
-		int dir,				/* AHEAD or BEHIND */
-		struct state * lp,
-		struct state * rp)
-{
-	int			anchor = (dir == AHEAD) ? '$' : '^';
-
-	assert(dir == AHEAD || dir == BEHIND);
-	newarc(v->nfa, anchor, 1, lp, rp);
-	newarc(v->nfa, anchor, 0, lp, rp);
-	colorcomplement(v->nfa, v->cm, dir, v->wordchrs, lp, rp);
-	/* (no need for special attention to \n) */
-}
-
-/*
- * word - generate arcs for word character ahead or behind
- */
-static void
-word(struct vars * v,
-	 int dir,					/* AHEAD or BEHIND */
-	 struct state * lp,
-	 struct state * rp)
-{
-	assert(dir == AHEAD || dir == BEHIND);
-	cloneouts(v->nfa, v->wordchrs, lp, rp, dir);
-	/* (no need for special attention to \n) */
-}
-
-/*
- * scannum - scan a number
- */
-static int						/* value, <= DUPMAX */
-scannum(struct vars * v)
-{
-	int			n = 0;
-
-	while (SEE(DIGIT) && n < DUPMAX)
-	{
-		n = n * 10 + v->nextvalue;
-		NEXT();
-	}
-	if (SEE(DIGIT) || n > DUPMAX)
-	{
-		ERR(REG_BADBR);
-		return 0;
-	}
-	return n;
-}
-
-/*
- * repeat - replicate subNFA for quantifiers
- *
- * The sub-NFA strung from lp to rp is modified to represent m to n
- * repetitions of its initial contents.
- *
- * The duplication sequences used here are chosen carefully so that any
- * pointers starting out pointing into the subexpression end up pointing into
- * the last occurrence.  (Note that it may not be strung between the same
- * left and right end states, however!)  This used to be important for the
- * subRE tree, although the important bits are now handled by the in-line
- * code in parse(), and when this is called, it doesn't matter any more.
- */
-static void
-repeat(struct vars * v,
-	   struct state * lp,
-	   struct state * rp,
-	   int m,
-	   int n)
-{
-#define  SOME	 2
-#define  INF	 3
-#define  PAIR(x, y)  ((x)*4 + (y))
-#define  REDUCE(x)	 ( ((x) == DUPINF) ? INF : (((x) > 1) ? SOME : (x)) )
-	const int	rm = REDUCE(m);
-	const int	rn = REDUCE(n);
-	struct state *s;
-	struct state *s2;
-
-	switch (PAIR(rm, rn))
-	{
-		case PAIR(0, 0):		/* empty string */
-			delsub(v->nfa, lp, rp);
-			EMPTYARC(lp, rp);
-			break;
-		case PAIR(0, 1):		/* do as x| */
-			EMPTYARC(lp, rp);
-			break;
-		case PAIR(0, SOME):		/* do as x{1,n}| */
-			repeat(v, lp, rp, 1, n);
-			NOERR();
-			EMPTYARC(lp, rp);
-			break;
-		case PAIR(0, INF):		/* loop x around */
-			s = newstate(v->nfa);
-			NOERR();
-			moveouts(v->nfa, lp, s);
-			moveins(v->nfa, rp, s);
-			EMPTYARC(lp, s);
-			EMPTYARC(s, rp);
-			break;
-		case PAIR(1, 1):		/* no action required */
-			break;
-		case PAIR(1, SOME):		/* do as x{0,n-1}x = (x{1,n-1}|)x */
-			s = newstate(v->nfa);
-			NOERR();
-			moveouts(v->nfa, lp, s);
-			dupnfa(v->nfa, s, rp, lp, s);
-			NOERR();
-			repeat(v, lp, s, 1, n - 1);
-			NOERR();
-			EMPTYARC(lp, s);
-			break;
-		case PAIR(1, INF):		/* add loopback arc */
-			s = newstate(v->nfa);
-			s2 = newstate(v->nfa);
-			NOERR();
-			moveouts(v->nfa, lp, s);
-			moveins(v->nfa, rp, s2);
-			EMPTYARC(lp, s);
-			EMPTYARC(s2, rp);
-			EMPTYARC(s2, s);
-			break;
-		case PAIR(SOME, SOME):	/* do as x{m-1,n-1}x */
-			s = newstate(v->nfa);
-			NOERR();
-			moveouts(v->nfa, lp, s);
-			dupnfa(v->nfa, s, rp, lp, s);
-			NOERR();
-			repeat(v, lp, s, m - 1, n - 1);
-			break;
-		case PAIR(SOME, INF):	/* do as x{m-1,}x */
-			s = newstate(v->nfa);
-			NOERR();
-			moveouts(v->nfa, lp, s);
-			dupnfa(v->nfa, s, rp, lp, s);
-			NOERR();
-			repeat(v, lp, s, m - 1, n);
-			break;
-		default:
-			ERR(REG_ASSERT);
-			break;
-	}
-}
-
-/*
- * bracket - handle non-complemented bracket expression
- * Also called from cbracket for complemented bracket expressions.
- */
-static void
-bracket(struct vars * v,
-		struct state * lp,
-		struct state * rp)
-{
-	assert(SEE('['));
-	NEXT();
-	while (!SEE(']') && !SEE(EOS))
-		brackpart(v, lp, rp);
-	assert(SEE(']') || ISERR());
-	okcolors(v->nfa, v->cm);
-}
-
-/*
- * cbracket - handle complemented bracket expression
- * We do it by calling bracket() with dummy endpoints, and then complementing
- * the result.  The alternative would be to invoke rainbow(), and then delete
- * arcs as the b.e. is seen... but that gets messy.
- */
-static void
-cbracket(struct vars * v,
-		 struct state * lp,
-		 struct state * rp)
-{
-	struct state *left = newstate(v->nfa);
-	struct state *right = newstate(v->nfa);
-
-	NOERR();
-	bracket(v, left, right);
-	if (v->cflags & REG_NLSTOP)
-		newarc(v->nfa, PLAIN, v->nlcolor, left, right);
-	NOERR();
-
-	assert(lp->nouts == 0);		/* all outarcs will be ours */
-
-	/*
-	 * Easy part of complementing, and all there is to do since the MCCE code
-	 * was removed.
-	 */
-	colorcomplement(v->nfa, v->cm, PLAIN, left, lp, rp);
-	NOERR();
-	dropstate(v->nfa, left);
-	assert(right->nins == 0);
-	freestate(v->nfa, right);
-}
-
-/*
- * brackpart - handle one item (or range) within a bracket expression
- */
-static void
-brackpart(struct vars * v,
-		  struct state * lp,
-		  struct state * rp)
-{
-	celt		startc;
-	celt		endc;
-	struct cvec *cv;
-	const chr  *startp;
-	const chr  *endp;
-	chr			c[1];
-
-	/* parse something, get rid of special cases, take shortcuts */
-	switch (v->nexttype)
-	{
-		case RANGE:				/* a-b-c or other botch */
-			ERR(REG_ERANGE);
-			return;
-			break;
-		case PLAIN:
-			c[0] = v->nextvalue;
-			NEXT();
-			/* shortcut for ordinary chr (not range) */
-			if (!SEE(RANGE))
-			{
-				onechr(v, c[0], lp, rp);
-				return;
-			}
-			startc = element(v, c, c + 1);
-			NOERR();
-			break;
-		case COLLEL:
-			startp = v->now;
-			endp = scanplain(v);
-			INSIST(startp < endp, REG_ECOLLATE);
-			NOERR();
-			startc = element(v, startp, endp);
-			NOERR();
-			break;
-		case ECLASS:
-			startp = v->now;
-			endp = scanplain(v);
-			INSIST(startp < endp, REG_ECOLLATE);
-			NOERR();
-			startc = element(v, startp, endp);
-			NOERR();
-			cv = eclass(v, startc, (v->cflags & REG_ICASE));
-			NOERR();
-			dovec(v, cv, lp, rp);
-			return;
-			break;
-		case CCLASS:
-			startp = v->now;
-			endp = scanplain(v);
-			INSIST(startp < endp, REG_ECTYPE);
-			NOERR();
-			cv = cclass(v, startp, endp, (v->cflags & REG_ICASE));
-			NOERR();
-			dovec(v, cv, lp, rp);
-			return;
-			break;
-		default:
-			ERR(REG_ASSERT);
-			return;
-			break;
-	}
-
-	if (SEE(RANGE))
-	{
-		NEXT();
-		switch (v->nexttype)
-		{
-			case PLAIN:
-			case RANGE:
-				c[0] = v->nextvalue;
-				NEXT();
-				endc = element(v, c, c + 1);
-				NOERR();
-				break;
-			case COLLEL:
-				startp = v->now;
-				endp = scanplain(v);
-				INSIST(startp < endp, REG_ECOLLATE);
-				NOERR();
-				endc = element(v, startp, endp);
-				NOERR();
-				break;
-			default:
-				ERR(REG_ERANGE);
-				return;
-				break;
-		}
-	}
-	else
-		endc = startc;
-
-	/*
-	 * Ranges are unportable.  Actually, standard C does guarantee that digits
-	 * are contiguous, but making that an exception is just too complicated.
-	 */
-	if (startc != endc)
-		NOTE(REG_UUNPORT);
-	cv = range(v, startc, endc, (v->cflags & REG_ICASE));
-	NOERR();
-	dovec(v, cv, lp, rp);
-}
-
-/*
- * scanplain - scan PLAIN contents of [. etc.
- *
- * Certain bits of trickery in lex.c know that this code does not try
- * to look past the final bracket of the [. etc.
- */
-static const chr *				/* just after end of sequence */
-scanplain(struct vars * v)
-{
-	const chr  *endp;
-
-	assert(SEE(COLLEL) || SEE(ECLASS) || SEE(CCLASS));
-	NEXT();
-
-	endp = v->now;
-	while (SEE(PLAIN))
-	{
-		endp = v->now;
-		NEXT();
-	}
-
-	assert(SEE(END) || ISERR());
-	NEXT();
-
-	return endp;
-}
-
-/*
- * onechr - fill in arcs for a plain character, and possible case complements
- * This is mostly a shortcut for efficient handling of the common case.
- */
-static void
-onechr(struct vars * v,
-	   chr c,
-	   struct state * lp,
-	   struct state * rp)
-{
-	if (!(v->cflags & REG_ICASE))
-	{
-		newarc(v->nfa, PLAIN, subcolor(v->cm, c), lp, rp);
-		return;
-	}
-
-	/* rats, need general case anyway... */
-	dovec(v, allcases(v, c), lp, rp);
-}
-
-/*
- * dovec - fill in arcs for each element of a cvec
- */
-static void
-dovec(struct vars * v,
-	  struct cvec * cv,
-	  struct state * lp,
-	  struct state * rp)
-{
-	chr			ch,
-				from,
-				to;
-	const chr  *p;
-	int			i;
-
-	/* ordinary characters */
-	for (p = cv->chrs, i = cv->nchrs; i > 0; p++, i--)
-	{
-		ch = *p;
-		newarc(v->nfa, PLAIN, subcolor(v->cm, ch), lp, rp);
-	}
-
-	/* and the ranges */
-	for (p = cv->ranges, i = cv->nranges; i > 0; p += 2, i--)
-	{
-		from = *p;
-		to = *(p + 1);
-		if (from <= to)
-			subrange(v, from, to, lp, rp);
-	}
-}
-
-/*
- * wordchrs - set up word-chr list for word-boundary stuff, if needed
- *
- * The list is kept as a bunch of arcs between two dummy states; it's
- * disposed of by the unreachable-states sweep in NFA optimization.
- * Does NEXT().  Must not be called from any unusual lexical context.
- * This should be reconciled with the \w etc. handling in lex.c, and
- * should be cleaned up to reduce dependencies on input scanning.
- */
-static void
-wordchrs(struct vars * v)
-{
-	struct state *left;
-	struct state *right;
-
-	if (v->wordchrs != NULL)
-	{
-		NEXT();					/* for consistency */
-		return;
-	}
-
-	left = newstate(v->nfa);
-	right = newstate(v->nfa);
-	NOERR();
-	/* fine point:	implemented with [::], and lexer will set REG_ULOCALE */
-	lexword(v);
-	NEXT();
-	assert(v->savenow != NULL && SEE('['));
-	bracket(v, left, right);
-	assert((v->savenow != NULL && SEE(']')) || ISERR());
-	NEXT();
-	NOERR();
-	v->wordchrs = left;
-}
-
-/*
- * processlacon - generate the NFA representation of a LACON
- *
- * In the general case this is just newlacon() + newarc(), but some cases
- * can be optimized.
- */
-static void
-processlacon(struct vars * v,
-			 struct state * begin,		/* start of parsed LACON sub-re */
-			 struct state * end,	/* end of parsed LACON sub-re */
-			 int latype,
-			 struct state * lp, /* left state to hang it on */
-			 struct state * rp) /* right state to hang it on */
-{
-	struct state *s1;
-	int			n;
-
-	/*
-	 * Check for lookaround RE consisting of a single plain color arc (or set
-	 * of arcs); this would typically be a simple chr or a bracket expression.
-	 */
-	s1 = single_color_transition(begin, end);
-	switch (latype)
-	{
-		case LATYPE_AHEAD_POS:
-			/* If lookahead RE is just colorset C, convert to AHEAD(C) */
-			if (s1 != NULL)
-			{
-				cloneouts(v->nfa, s1, lp, rp, AHEAD);
-				return;
-			}
-			break;
-		case LATYPE_AHEAD_NEG:
-			/* If lookahead RE is just colorset C, convert to AHEAD(^C)|$ */
-			if (s1 != NULL)
-			{
-				colorcomplement(v->nfa, v->cm, AHEAD, s1, lp, rp);
-				newarc(v->nfa, '$', 1, lp, rp);
-				newarc(v->nfa, '$', 0, lp, rp);
-				return;
-			}
-			break;
-		case LATYPE_BEHIND_POS:
-			/* If lookbehind RE is just colorset C, convert to BEHIND(C) */
-			if (s1 != NULL)
-			{
-				cloneouts(v->nfa, s1, lp, rp, BEHIND);
-				return;
-			}
-			break;
-		case LATYPE_BEHIND_NEG:
-			/* If lookbehind RE is just colorset C, convert to BEHIND(^C)|^ */
-			if (s1 != NULL)
-			{
-				colorcomplement(v->nfa, v->cm, BEHIND, s1, lp, rp);
-				newarc(v->nfa, '^', 1, lp, rp);
-				newarc(v->nfa, '^', 0, lp, rp);
-				return;
-			}
-			break;
-		default:
-			assert(NOTREACHED);
-	}
-
-	/* General case: we need a LACON subre and arc */
-	n = newlacon(v, begin, end, latype);
-	newarc(v->nfa, LACON, n, lp, rp);
-}
-
-/*
- * subre - allocate a subre
- */
-static struct subre *
-subre(struct vars * v,
-	  int op,
-	  int flags,
-	  struct state * begin,
-	  struct state * end)
-{
-	struct subre *ret = v->treefree;
-
-	/*
-	 * Checking for stack overflow here is sufficient to protect parse() and
-	 * its recursive subroutines.
-	 */
-	if (STACK_TOO_DEEP(v->re))
-	{
-		ERR(REG_ETOOBIG);
-		return NULL;
-	}
-
-	if (ret != NULL)
-		v->treefree = ret->left;
-	else
-	{
-		ret = (struct subre *) MALLOC(sizeof(struct subre));
-		if (ret == NULL)
-		{
-			ERR(REG_ESPACE);
-			return NULL;
-		}
-		ret->chain = v->treechain;
-		v->treechain = ret;
-	}
-
-	assert(strchr("=b|.*(", op) != NULL);
-
-	ret->op = op;
-	ret->flags = flags;
-	ret->id = 0;				/* will be assigned later */
-	ret->subno = 0;
-	ret->min = ret->max = 1;
-	ret->left = NULL;
-	ret->right = NULL;
-	ret->begin = begin;
-	ret->end = end;
-	ZAPCNFA(ret->cnfa);
-
-	return ret;
-}
-
-/*
- * freesubre - free a subRE subtree
- */
-static void
-freesubre(struct vars * v,		/* might be NULL */
-		  struct subre * sr)
-{
-	if (sr == NULL)
-		return;
-
-	if (sr->left != NULL)
-		freesubre(v, sr->left);
-	if (sr->right != NULL)
-		freesubre(v, sr->right);
-
-	freesrnode(v, sr);
-}
-
-/*
- * freesrnode - free one node in a subRE subtree
- */
-static void
-freesrnode(struct vars * v,		/* might be NULL */
-		   struct subre * sr)
-{
-	if (sr == NULL)
-		return;
-
-	if (!NULLCNFA(sr->cnfa))
-		freecnfa(&sr->cnfa);
-	sr->flags = 0;
-
-	if (v != NULL && v->treechain != NULL)
-	{
-		/* we're still parsing, maybe we can reuse the subre */
-		sr->left = v->treefree;
-		v->treefree = sr;
-	}
-	else
-		FREE(sr);
-}
-
-/*
- * optst - optimize a subRE subtree
- */
-static void
-optst(struct vars * v,
-	  struct subre * t)
-{
-	/*
-	 * DGP (2007-11-13): I assume it was the programmer's intent to eventually
-	 * come back and add code to optimize subRE trees, but the routine coded
-	 * just spends effort traversing the tree and doing nothing. We can do
-	 * nothing with less effort.
-	 */
-	return;
-}
-
-/*
- * numst - number tree nodes (assigning "id" indexes)
- */
-static int						/* next number */
-numst(struct subre * t,
-	  int start)				/* starting point for subtree numbers */
-{
-	int			i;
-
-	assert(t != NULL);
-
-	i = start;
-	t->id = (short) i++;
-	if (t->left != NULL)
-		i = numst(t->left, i);
-	if (t->right != NULL)
-		i = numst(t->right, i);
-	return i;
-}
-
-/*
- * markst - mark tree nodes as INUSE
- *
- * Note: this is a great deal more subtle than it looks.  During initial
- * parsing of a regex, all subres are linked into the treechain list;
- * discarded ones are also linked into the treefree list for possible reuse.
- * After we are done creating all subres required for a regex, we run markst()
- * then cleanst(), which results in discarding all subres not reachable from
- * v->tree.  We then clear v->treechain, indicating that subres must be found
- * by descending from v->tree.  This changes the behavior of freesubre(): it
- * will henceforth FREE() unwanted subres rather than sticking them into the
- * treefree list.  (Doing that any earlier would result in dangling links in
- * the treechain list.)  This all means that freev() will clean up correctly
- * if invoked before or after markst()+cleanst(); but it would not work if
- * called partway through this state conversion, so we mustn't error out
- * in or between these two functions.
- */
-static void
-markst(struct subre * t)
-{
-	assert(t != NULL);
-
-	t->flags |= INUSE;
-	if (t->left != NULL)
-		markst(t->left);
-	if (t->right != NULL)
-		markst(t->right);
-}
-
-/*
- * cleanst - free any tree nodes not marked INUSE
- */
-static void
-cleanst(struct vars * v)
-{
-	struct subre *t;
-	struct subre *next;
-
-	for (t = v->treechain; t != NULL; t = next)
-	{
-		next = t->chain;
-		if (!(t->flags & INUSE))
-			FREE(t);
-	}
-	v->treechain = NULL;
-	v->treefree = NULL;			/* just on general principles */
-}
-
-/*
- * nfatree - turn a subRE subtree into a tree of compacted NFAs
- */
-static long						/* optimize results from top node */
-nfatree(struct vars * v,
-		struct subre * t,
-		FILE *f)				/* for debug output */
-{
-	assert(t != NULL && t->begin != NULL);
-
-	if (t->left != NULL)
-		(DISCARD) nfatree(v, t->left, f);
-	if (t->right != NULL)
-		(DISCARD) nfatree(v, t->right, f);
-
-	return nfanode(v, t, 0, f);
-}
-
-/*
- * nfanode - do one NFA for nfatree or lacons
- *
- * If converttosearch is true, apply makesearch() to the NFA.
- */
-static long						/* optimize results */
-nfanode(struct vars * v,
-		struct subre * t,
-		int converttosearch,
-		FILE *f)				/* for debug output */
-{
-	struct nfa *nfa;
-	long		ret = 0;
-
-	assert(t->begin != NULL);
-
-#ifdef REG_DEBUG
-	if (f != NULL)
-	{
-		char		idbuf[50];
-
-		fprintf(f, "\n\n\n========= TREE NODE %s ==========\n",
-				stid(t, idbuf, sizeof(idbuf)));
-	}
-#endif
-	nfa = newnfa(v, v->cm, v->nfa);
-	NOERRZ();
-	dupnfa(nfa, t->begin, t->end, nfa->init, nfa->final);
-	if (!ISERR())
-		specialcolors(nfa);
-	if (!ISERR())
-		ret = optimize(nfa, f);
-	if (converttosearch && !ISERR())
-		makesearch(v, nfa);
-	if (!ISERR())
-		compact(nfa, &t->cnfa);
-
-	freenfa(nfa);
-	return ret;
-}
-
-/*
- * newlacon - allocate a lookaround-constraint subRE
- */
-static int						/* lacon number */
-newlacon(struct vars * v,
-		 struct state * begin,
-		 struct state * end,
-		 int latype)
-{
-	int			n;
-	struct subre *newlacons;
-	struct subre *sub;
-
-	if (v->nlacons == 0)
-	{
-		n = 1;					/* skip 0th */
-		newlacons = (struct subre *) MALLOC(2 * sizeof(struct subre));
-	}
-	else
-	{
-		n = v->nlacons;
-		newlacons = (struct subre *) REALLOC(v->lacons,
-											 (n + 1) * sizeof(struct subre));
-	}
-	if (newlacons == NULL)
-	{
-		ERR(REG_ESPACE);
-		return 0;
-	}
-	v->lacons = newlacons;
-	v->nlacons = n + 1;
-	sub = &v->lacons[n];
-	sub->begin = begin;
-	sub->end = end;
-	sub->subno = latype;
-	ZAPCNFA(sub->cnfa);
-	return n;
-}
-
-/*
- * freelacons - free lookaround-constraint subRE vector
- */
-static void
-freelacons(struct subre * subs,
-		   int n)
-{
-	struct subre *sub;
-	int			i;
-
-	assert(n > 0);
-	for (sub = subs + 1, i = n - 1; i > 0; sub++, i--)	/* no 0th */
-		if (!NULLCNFA(sub->cnfa))
-			freecnfa(&sub->cnfa);
-	FREE(subs);
-}
-
-/*
- * rfree - free a whole RE (insides of regfree)
- */
-static void
-rfree(regex_t *re)
-{
-	struct guts *g;
-
-	if (re == NULL || re->re_magic != REMAGIC)
-		return;
-
-	re->re_magic = 0;			/* invalidate RE */
-	g = (struct guts *) re->re_guts;
-	re->re_guts = NULL;
-	re->re_fns = NULL;
-	if (g != NULL)
-	{
-		g->magic = 0;
-		freecm(&g->cmap);
-		if (g->tree != NULL)
-			freesubre((struct vars *) NULL, g->tree);
-		if (g->lacons != NULL)
-			freelacons(g->lacons, g->nlacons);
-		if (!NULLCNFA(g->search))
-			freecnfa(&g->search);
-		FREE(g);
-	}
-}
-
-/*
- * rcancelrequested - check for external request to cancel regex operation
- *
- * Return nonzero to fail the operation with error code REG_CANCEL,
- * zero to keep going
- *
- * The current implementation is Postgres-specific.  If we ever get around
- * to splitting the regex code out as a standalone library, there will need
- * to be some API to let applications define a callback function for this.
- */
-static int
-rcancelrequested(void)
-{
-	return InterruptPending && (QueryCancelPending || ProcDiePending);
-}
-
-/*
- * rstacktoodeep - check for stack getting dangerously deep
- *
- * Return nonzero to fail the operation with error code REG_ETOOBIG,
- * zero to keep going
- *
- * The current implementation is Postgres-specific.  If we ever get around
- * to splitting the regex code out as a standalone library, there will need
- * to be some API to let applications define a callback function for this.
- */
-static int
-rstacktoodeep(void)
-{
-	return stack_is_too_deep();
-}
-
-#ifdef REG_DEBUG
-
-/*
- * dump - dump an RE in human-readable form
- */
-static void
-dump(regex_t *re,
-	 FILE *f)
-{
-	struct guts *g;
-	int			i;
-
-	if (re->re_magic != REMAGIC)
-		fprintf(f, "bad magic number (0x%x not 0x%x)\n", re->re_magic,
-				REMAGIC);
-	if (re->re_guts == NULL)
-	{
-		fprintf(f, "NULL guts!!!\n");
-		return;
-	}
-	g = (struct guts *) re->re_guts;
-	if (g->magic != GUTSMAGIC)
-		fprintf(f, "bad guts magic number (0x%x not 0x%x)\n", g->magic,
-				GUTSMAGIC);
-
-	fprintf(f, "\n\n\n========= DUMP ==========\n");
-	fprintf(f, "nsub %d, info 0%lo, csize %d, ntree %d\n",
-			(int) re->re_nsub, re->re_info, re->re_csize, g->ntree);
-
-	dumpcolors(&g->cmap, f);
-	if (!NULLCNFA(g->search))
-	{
-		fprintf(f, "\nsearch:\n");
-		dumpcnfa(&g->search, f);
-	}
-	for (i = 1; i < g->nlacons; i++)
-	{
-		struct subre *lasub = &g->lacons[i];
-		const char *latype;
-
-		switch (lasub->subno)
-		{
-			case LATYPE_AHEAD_POS:
-				latype = "positive lookahead";
-				break;
-			case LATYPE_AHEAD_NEG:
-				latype = "negative lookahead";
-				break;
-			case LATYPE_BEHIND_POS:
-				latype = "positive lookbehind";
-				break;
-			case LATYPE_BEHIND_NEG:
-				latype = "negative lookbehind";
-				break;
-			default:
-				latype = "???";
-				break;
-		}
-		fprintf(f, "\nla%d (%s):\n", i, latype);
-		dumpcnfa(&lasub->cnfa, f);
-	}
-	fprintf(f, "\n");
-	dumpst(g->tree, f, 0);
-}
-
-/*
- * dumpst - dump a subRE tree
- */
-static void
-dumpst(struct subre * t,
-	   FILE *f,
-	   int nfapresent)			/* is the original NFA still around? */
-{
-	if (t == NULL)
-		fprintf(f, "null tree\n");
-	else
-		stdump(t, f, nfapresent);
-	fflush(f);
-}
-
-/*
- * stdump - recursive guts of dumpst
- */
-static void
-stdump(struct subre * t,
-	   FILE *f,
-	   int nfapresent)			/* is the original NFA still around? */
-{
-	char		idbuf[50];
-
-	fprintf(f, "%s. `%c'", stid(t, idbuf, sizeof(idbuf)), t->op);
-	if (t->flags & LONGER)
-		fprintf(f, " longest");
-	if (t->flags & SHORTER)
-		fprintf(f, " shortest");
-	if (t->flags & MIXED)
-		fprintf(f, " hasmixed");
-	if (t->flags & CAP)
-		fprintf(f, " hascapture");
-	if (t->flags & BACKR)
-		fprintf(f, " hasbackref");
-	if (!(t->flags & INUSE))
-		fprintf(f, " UNUSED");
-	if (t->subno != 0)
-		fprintf(f, " (#%d)", t->subno);
-	if (t->min != 1 || t->max != 1)
-	{
-		fprintf(f, " {%d,", t->min);
-		if (t->max != DUPINF)
-			fprintf(f, "%d", t->max);
-		fprintf(f, "}");
-	}
-	if (nfapresent)
-		fprintf(f, " %ld-%ld", (long) t->begin->no, (long) t->end->no);
-	if (t->left != NULL)
-		fprintf(f, " L:%s", stid(t->left, idbuf, sizeof(idbuf)));
-	if (t->right != NULL)
-		fprintf(f, " R:%s", stid(t->right, idbuf, sizeof(idbuf)));
-	if (!NULLCNFA(t->cnfa))
-	{
-		fprintf(f, "\n");
-		dumpcnfa(&t->cnfa, f);
-	}
-	fprintf(f, "\n");
-	if (t->left != NULL)
-		stdump(t->left, f, nfapresent);
-	if (t->right != NULL)
-		stdump(t->right, f, nfapresent);
-}
-
-/*
- * stid - identify a subtree node for dumping
- */
-static const char *				/* points to buf or constant string */
-stid(struct subre * t,
-	 char *buf,
-	 size_t bufsize)
-{
-	/* big enough for hex int or decimal t->id? */
-	if (bufsize < sizeof(void *) * 2 + 3 || bufsize < sizeof(t->id) * 3 + 1)
-		return "unable";
-	if (t->id != 0)
-		sprintf(buf, "%d", t->id);
-	else
-		sprintf(buf, "%p", t);
-	return buf;
-}
-#endif   /* REG_DEBUG */
-
-
-#include "regc_lex.c"
-#include "regc_color.c"
-#include "regc_nfa.c"
-#include "regc_cvec.c"
-#include "regc_pg_locale.c"
-#include "regc_locale.c"
diff --git a/src/backend/regex/rege_dfa.c b/src/backend/regex/rege_dfa.c
deleted file mode 100644
index 7d90242..0000000
--- a/src/backend/regex/rege_dfa.c
+++ /dev/null
@@ -1,929 +0,0 @@
-/*
- * DFA routines
- * This file is #included by regexec.c.
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/rege_dfa.c
- *
- */
-
-/*
- * longest - longest-preferred matching engine
- *
- * On success, returns match endpoint address.  Returns NULL on no match.
- * Internal errors also return NULL, with v->err set.
- */
-static chr *
-longest(struct vars * v,
-		struct dfa * d,
-		chr *start,				/* where the match should start */
-		chr *stop,				/* match must end at or before here */
-		int *hitstopp)			/* record whether hit v->stop, if non-NULL */
-{
-	chr		   *cp;
-	chr		   *realstop = (stop == v->stop) ? stop : stop + 1;
-	color		co;
-	struct sset *css;
-	struct sset *ss;
-	chr		   *post;
-	int			i;
-	struct colormap *cm = d->cm;
-
-	/* prevent "uninitialized variable" warnings */
-	if (hitstopp != NULL)
-		*hitstopp = 0;
-
-	/* initialize */
-	css = initialize(v, d, start);
-	if (css == NULL)
-		return NULL;
-	cp = start;
-
-	/* startup */
-	FDEBUG(("+++ startup +++\n"));
-	if (cp == v->start)
-	{
-		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-	}
-	else
-	{
-		co = GETCOLOR(cm, *(cp - 1));
-		FDEBUG(("char %c, color %ld\n", (char) *(cp - 1), (long) co));
-	}
-	css = miss(v, d, css, co, cp, start);
-	if (css == NULL)
-		return NULL;
-	css->lastseen = cp;
-
-	/*
-	 * This is the main text-scanning loop.  It seems worth having two copies
-	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
-	 * builds, when you're not actively tracing.
-	 */
-#ifdef REG_DEBUG
-	if (v->eflags & REG_FTRACE)
-	{
-		while (cp < realstop)
-		{
-			FDEBUG(("+++ at c%d +++\n", (int) (css - d->ssets)));
-			co = GETCOLOR(cm, *cp);
-			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-		}
-	}
-	else
-#endif
-	{
-		while (cp < realstop)
-		{
-			co = GETCOLOR(cm, *cp);
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-		}
-	}
-
-	if (ISERR())
-		return NULL;
-
-	/* shutdown */
-	FDEBUG(("+++ shutdown at c%d +++\n", (int) (css - d->ssets)));
-	if (cp == v->stop && stop == v->stop)
-	{
-		if (hitstopp != NULL)
-			*hitstopp = 1;
-		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-		ss = miss(v, d, css, co, cp, start);
-		if (ISERR())
-			return NULL;
-		/* special case:  match ended at eol? */
-		if (ss != NULL && (ss->flags & POSTSTATE))
-			return cp;
-		else if (ss != NULL)
-			ss->lastseen = cp;	/* to be tidy */
-	}
-
-	/* find last match, if any */
-	post = d->lastpost;
-	for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--)
-		if ((ss->flags & POSTSTATE) && post != ss->lastseen &&
-			(post == NULL || post < ss->lastseen))
-			post = ss->lastseen;
-	if (post != NULL)			/* found one */
-		return post - 1;
-
-	return NULL;
-}
-
-/*
- * shortest - shortest-preferred matching engine
- *
- * On success, returns match endpoint address.  Returns NULL on no match.
- * Internal errors also return NULL, with v->err set.
- */
-static chr *
-shortest(struct vars * v,
-		 struct dfa * d,
-		 chr *start,			/* where the match should start */
-		 chr *min,				/* match must end at or after here */
-		 chr *max,				/* match must end at or before here */
-		 chr **coldp,			/* store coldstart pointer here, if non-NULL */
-		 int *hitstopp)			/* record whether hit v->stop, if non-NULL */
-{
-	chr		   *cp;
-	chr		   *realmin = (min == v->stop) ? min : min + 1;
-	chr		   *realmax = (max == v->stop) ? max : max + 1;
-	color		co;
-	struct sset *css;
-	struct sset *ss;
-	struct colormap *cm = d->cm;
-
-	/* prevent "uninitialized variable" warnings */
-	if (coldp != NULL)
-		*coldp = NULL;
-	if (hitstopp != NULL)
-		*hitstopp = 0;
-
-	/* initialize */
-	css = initialize(v, d, start);
-	if (css == NULL)
-		return NULL;
-	cp = start;
-
-	/* startup */
-	FDEBUG(("--- startup ---\n"));
-	if (cp == v->start)
-	{
-		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-	}
-	else
-	{
-		co = GETCOLOR(cm, *(cp - 1));
-		FDEBUG(("char %c, color %ld\n", (char) *(cp - 1), (long) co));
-	}
-	css = miss(v, d, css, co, cp, start);
-	if (css == NULL)
-		return NULL;
-	css->lastseen = cp;
-	ss = css;
-
-	/*
-	 * This is the main text-scanning loop.  It seems worth having two copies
-	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
-	 * builds, when you're not actively tracing.
-	 */
-#ifdef REG_DEBUG
-	if (v->eflags & REG_FTRACE)
-	{
-		while (cp < realmax)
-		{
-			FDEBUG(("--- at c%d ---\n", (int) (css - d->ssets)));
-			co = GETCOLOR(cm, *cp);
-			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-			if ((ss->flags & POSTSTATE) && cp >= realmin)
-				break;			/* NOTE BREAK OUT */
-		}
-	}
-	else
-#endif
-	{
-		while (cp < realmax)
-		{
-			co = GETCOLOR(cm, *cp);
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-			if ((ss->flags & POSTSTATE) && cp >= realmin)
-				break;			/* NOTE BREAK OUT */
-		}
-	}
-
-	if (ss == NULL)
-		return NULL;
-
-	if (coldp != NULL)			/* report last no-progress state set, if any */
-		*coldp = lastcold(v, d);
-
-	if ((ss->flags & POSTSTATE) && cp > min)
-	{
-		assert(cp >= realmin);
-		cp--;
-	}
-	else if (cp == v->stop && max == v->stop)
-	{
-		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-		ss = miss(v, d, css, co, cp, start);
-		/* match might have ended at eol */
-		if ((ss == NULL || !(ss->flags & POSTSTATE)) && hitstopp != NULL)
-			*hitstopp = 1;
-	}
-
-	if (ss == NULL || !(ss->flags & POSTSTATE))
-		return NULL;
-
-	return cp;
-}
-
-/*
- * matchuntil - incremental matching engine
- *
- * This is meant for use with a search-style NFA (that is, the pattern is
- * known to act as though it had a leading .*).  We determine whether a
- * match exists starting at v->start and ending at probe.  Multiple calls
- * require only O(N) time not O(N^2) so long as the probe values are
- * nondecreasing.  *lastcss and *lastcp must be initialized to NULL before
- * starting a series of calls.
- *
- * Returns 1 if a match exists, 0 if not.
- * Internal errors also return 0, with v->err set.
- */
-static int
-matchuntil(struct vars * v,
-		   struct dfa * d,
-		   chr *probe,			/* we want to know if a match ends here */
-		   struct sset ** lastcss,		/* state storage across calls */
-		   chr **lastcp)		/* state storage across calls */
-{
-	chr		   *cp = *lastcp;
-	color		co;
-	struct sset *css = *lastcss;
-	struct sset *ss;
-	struct colormap *cm = d->cm;
-
-	/* initialize and startup, or restart, if necessary */
-	if (cp == NULL || cp > probe)
-	{
-		cp = v->start;
-		css = initialize(v, d, cp);
-		if (css == NULL)
-			return 0;
-
-		FDEBUG((">>> startup >>>\n"));
-		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-
-		css = miss(v, d, css, co, cp, v->start);
-		if (css == NULL)
-			return 0;
-		css->lastseen = cp;
-	}
-	else if (css == NULL)
-	{
-		/* we previously found that no match is possible beyond *lastcp */
-		return 0;
-	}
-	ss = css;
-
-	/*
-	 * This is the main text-scanning loop.  It seems worth having two copies
-	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
-	 * builds, when you're not actively tracing.
-	 */
-#ifdef REG_DEBUG
-	if (v->eflags & REG_FTRACE)
-	{
-		while (cp < probe)
-		{
-			FDEBUG((">>> at c%d >>>\n", (int) (css - d->ssets)));
-			co = GETCOLOR(cm, *cp);
-			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, v->start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-		}
-	}
-	else
-#endif
-	{
-		while (cp < probe)
-		{
-			co = GETCOLOR(cm, *cp);
-			ss = css->outs[co];
-			if (ss == NULL)
-			{
-				ss = miss(v, d, css, co, cp + 1, v->start);
-				if (ss == NULL)
-					break;		/* NOTE BREAK OUT */
-			}
-			cp++;
-			ss->lastseen = cp;
-			css = ss;
-		}
-	}
-
-	*lastcss = ss;
-	*lastcp = cp;
-
-	if (ss == NULL)
-		return 0;				/* impossible match, or internal error */
-
-	/* We need to process one more chr, or the EOS symbol, to check match */
-	if (cp < v->stop)
-	{
-		FDEBUG((">>> at c%d >>>\n", (int) (css - d->ssets)));
-		co = GETCOLOR(cm, *cp);
-		FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
-		ss = css->outs[co];
-		if (ss == NULL)
-			ss = miss(v, d, css, co, cp + 1, v->start);
-	}
-	else
-	{
-		assert(cp == v->stop);
-		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
-		FDEBUG(("color %ld\n", (long) co));
-		ss = miss(v, d, css, co, cp, v->start);
-	}
-
-	if (ss == NULL || !(ss->flags & POSTSTATE))
-		return 0;
-
-	return 1;
-}
-
-/*
- * lastcold - determine last point at which no progress had been made
- */
-static chr *					/* endpoint, or NULL */
-lastcold(struct vars * v,
-		 struct dfa * d)
-{
-	struct sset *ss;
-	chr		   *nopr;
-	int			i;
-
-	nopr = d->lastnopr;
-	if (nopr == NULL)
-		nopr = v->start;
-	for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--)
-		if ((ss->flags & NOPROGRESS) && nopr < ss->lastseen)
-			nopr = ss->lastseen;
-	return nopr;
-}
-
-/*
- * newdfa - set up a fresh DFA
- */
-static struct dfa *
-newdfa(struct vars * v,
-	   struct cnfa * cnfa,
-	   struct colormap * cm,
-	   struct smalldfa * sml)	/* preallocated space, may be NULL */
-{
-	struct dfa *d;
-	size_t		nss = cnfa->nstates * 2;
-	int			wordsper = (cnfa->nstates + UBITS - 1) / UBITS;
-	struct smalldfa *smallwas = sml;
-
-	assert(cnfa != NULL && cnfa->nstates != 0);
-
-	if (nss <= FEWSTATES && cnfa->ncolors <= FEWCOLORS)
-	{
-		assert(wordsper == 1);
-		if (sml == NULL)
-		{
-			sml = (struct smalldfa *) MALLOC(sizeof(struct smalldfa));
-			if (sml == NULL)
-			{
-				ERR(REG_ESPACE);
-				return NULL;
-			}
-		}
-		d = &sml->dfa;
-		d->ssets = sml->ssets;
-		d->statesarea = sml->statesarea;
-		d->work = &d->statesarea[nss];
-		d->outsarea = sml->outsarea;
-		d->incarea = sml->incarea;
-		d->cptsmalloced = 0;
-		d->mallocarea = (smallwas == NULL) ? (char *) sml : NULL;
-	}
-	else
-	{
-		d = (struct dfa *) MALLOC(sizeof(struct dfa));
-		if (d == NULL)
-		{
-			ERR(REG_ESPACE);
-			return NULL;
-		}
-		d->ssets = (struct sset *) MALLOC(nss * sizeof(struct sset));
-		d->statesarea = (unsigned *) MALLOC((nss + WORK) * wordsper *
-											sizeof(unsigned));
-		d->work = &d->statesarea[nss * wordsper];
-		d->outsarea = (struct sset **) MALLOC(nss * cnfa->ncolors *
-											  sizeof(struct sset *));
-		d->incarea = (struct arcp *) MALLOC(nss * cnfa->ncolors *
-											sizeof(struct arcp));
-		d->cptsmalloced = 1;
-		d->mallocarea = (char *) d;
-		if (d->ssets == NULL || d->statesarea == NULL ||
-			d->outsarea == NULL || d->incarea == NULL)
-		{
-			freedfa(d);
-			ERR(REG_ESPACE);
-			return NULL;
-		}
-	}
-
-	d->nssets = (v->eflags & REG_SMALL) ? 7 : nss;
-	d->nssused = 0;
-	d->nstates = cnfa->nstates;
-	d->ncolors = cnfa->ncolors;
-	d->wordsper = wordsper;
-	d->cnfa = cnfa;
-	d->cm = cm;
-	d->lastpost = NULL;
-	d->lastnopr = NULL;
-	d->search = d->ssets;
-
-	/* initialization of sset fields is done as needed */
-
-	return d;
-}
-
-/*
- * freedfa - free a DFA
- */
-static void
-freedfa(struct dfa * d)
-{
-	if (d->cptsmalloced)
-	{
-		if (d->ssets != NULL)
-			FREE(d->ssets);
-		if (d->statesarea != NULL)
-			FREE(d->statesarea);
-		if (d->outsarea != NULL)
-			FREE(d->outsarea);
-		if (d->incarea != NULL)
-			FREE(d->incarea);
-	}
-
-	if (d->mallocarea != NULL)
-		FREE(d->mallocarea);
-}
-
-/*
- * hash - construct a hash code for a bitvector
- *
- * There are probably better ways, but they're more expensive.
- */
-static unsigned
-hash(unsigned *uv,
-	 int n)
-{
-	int			i;
-	unsigned	h;
-
-	h = 0;
-	for (i = 0; i < n; i++)
-		h ^= uv[i];
-	return h;
-}
-
-/*
- * initialize - hand-craft a cache entry for startup, otherwise get ready
- */
-static struct sset *
-initialize(struct vars * v,
-		   struct dfa * d,
-		   chr *start)
-{
-	struct sset *ss;
-	int			i;
-
-	/* is previous one still there? */
-	if (d->nssused > 0 && (d->ssets[0].flags & STARTER))
-		ss = &d->ssets[0];
-	else
-	{							/* no, must (re)build it */
-		ss = getvacant(v, d, start, start);
-		if (ss == NULL)
-			return NULL;
-		for (i = 0; i < d->wordsper; i++)
-			ss->states[i] = 0;
-		BSET(ss->states, d->cnfa->pre);
-		ss->hash = HASH(ss->states, d->wordsper);
-		assert(d->cnfa->pre != d->cnfa->post);
-		ss->flags = STARTER | LOCKED | NOPROGRESS;
-		/* lastseen dealt with below */
-	}
-
-	for (i = 0; i < d->nssused; i++)
-		d->ssets[i].lastseen = NULL;
-	ss->lastseen = start;		/* maybe untrue, but harmless */
-	d->lastpost = NULL;
-	d->lastnopr = NULL;
-	return ss;
-}
-
-/*
- * miss - handle a stateset cache miss
- *
- * css is the current stateset, co is the color of the current input character,
- * cp points to the character after that (which is where we may need to test
- * LACONs).  start does not affect matching behavior but is needed for pickss'
- * heuristics about which stateset cache entry to replace.
- *
- * Ordinarily, returns the address of the next stateset (the one that is
- * valid after consuming the input character).  Returns NULL if no valid
- * NFA states remain, ie we have a certain match failure.
- * Internal errors also return NULL, with v->err set.
- */
-static struct sset *
-miss(struct vars * v,
-	 struct dfa * d,
-	 struct sset * css,
-	 pcolor co,
-	 chr *cp,					/* next chr */
-	 chr *start)				/* where the attempt got started */
-{
-	struct cnfa *cnfa = d->cnfa;
-	int			i;
-	unsigned	h;
-	struct carc *ca;
-	struct sset *p;
-	int			ispost;
-	int			noprogress;
-	int			gotstate;
-	int			dolacons;
-	int			sawlacons;
-
-	/* for convenience, we can be called even if it might not be a miss */
-	if (css->outs[co] != NULL)
-	{
-		FDEBUG(("hit\n"));
-		return css->outs[co];
-	}
-	FDEBUG(("miss\n"));
-
-	/*
-	 * Checking for operation cancel in the inner text search loop seems
-	 * unduly expensive.  As a compromise, check during cache misses.
-	 */
-	if (CANCEL_REQUESTED(v->re))
-	{
-		ERR(REG_CANCEL);
-		return NULL;
-	}
-
-	/*
-	 * What set of states would we end up in after consuming the co character?
-	 * We first consider PLAIN arcs that consume the character, and then look
-	 * to see what LACON arcs could be traversed after consuming it.
-	 */
-	for (i = 0; i < d->wordsper; i++)
-		d->work[i] = 0;			/* build new stateset bitmap in d->work */
-	ispost = 0;
-	noprogress = 1;
-	gotstate = 0;
-	for (i = 0; i < d->nstates; i++)
-		if (ISBSET(css->states, i))
-			for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++)
-				if (ca->co == co)
-				{
-					BSET(d->work, ca->to);
-					gotstate = 1;
-					if (ca->to == cnfa->post)
-						ispost = 1;
-					if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS))
-						noprogress = 0;
-					FDEBUG(("%d -> %d\n", i, ca->to));
-				}
-	if (!gotstate)
-		return NULL;			/* character cannot reach any new state */
-	dolacons = (cnfa->flags & HASLACONS);
-	sawlacons = 0;
-	/* outer loop handles transitive closure of reachable-by-LACON states */
-	while (dolacons)
-	{
-		dolacons = 0;
-		for (i = 0; i < d->nstates; i++)
-			if (ISBSET(d->work, i))
-				for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++)
-				{
-					if (ca->co < cnfa->ncolors)
-						continue;		/* not a LACON arc */
-					if (ISBSET(d->work, ca->to))
-						continue;		/* arc would be a no-op anyway */
-					sawlacons = 1;		/* this LACON affects our result */
-					if (!lacon(v, cnfa, cp, ca->co))
-					{
-						if (ISERR())
-							return NULL;
-						continue;		/* LACON arc cannot be traversed */
-					}
-					if (ISERR())
-						return NULL;
-					BSET(d->work, ca->to);
-					dolacons = 1;
-					if (ca->to == cnfa->post)
-						ispost = 1;
-					if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS))
-						noprogress = 0;
-					FDEBUG(("%d :> %d\n", i, ca->to));
-				}
-	}
-	h = HASH(d->work, d->wordsper);
-
-	/* Is this stateset already in the cache? */
-	for (p = d->ssets, i = d->nssused; i > 0; p++, i--)
-		if (HIT(h, d->work, p, d->wordsper))
-		{
-			FDEBUG(("cached c%d\n", (int) (p - d->ssets)));
-			break;				/* NOTE BREAK OUT */
-		}
-	if (i == 0)
-	{							/* nope, need a new cache entry */
-		p = getvacant(v, d, cp, start);
-		if (p == NULL)
-			return NULL;
-		assert(p != css);
-		for (i = 0; i < d->wordsper; i++)
-			p->states[i] = d->work[i];
-		p->hash = h;
-		p->flags = (ispost) ? POSTSTATE : 0;
-		if (noprogress)
-			p->flags |= NOPROGRESS;
-		/* lastseen to be dealt with by caller */
-	}
-
-	/*
-	 * Link new stateset to old, unless a LACON affected the result, in which
-	 * case we don't create the link.  That forces future transitions across
-	 * this same arc (same prior stateset and character color) to come through
-	 * miss() again, so that we can recheck the LACON(s), which might or might
-	 * not pass since context will be different.
-	 */
-	if (!sawlacons)
-	{
-		FDEBUG(("c%d[%d]->c%d\n",
-				(int) (css - d->ssets), co, (int) (p - d->ssets)));
-		css->outs[co] = p;
-		css->inchain[co] = p->ins;
-		p->ins.ss = css;
-		p->ins.co = (color) co;
-	}
-	return p;
-}
-
-/*
- * lacon - lookaround-constraint checker for miss()
- */
-static int						/* predicate:  constraint satisfied? */
-lacon(struct vars * v,
-	  struct cnfa * pcnfa,		/* parent cnfa */
-	  chr *cp,
-	  pcolor co)				/* "color" of the lookaround constraint */
-{
-	int			n;
-	struct subre *sub;
-	struct dfa *d;
-	chr		   *end;
-	int			satisfied;
-
-	/* Since this is recursive, it could be driven to stack overflow */
-	if (STACK_TOO_DEEP(v->re))
-	{
-		ERR(REG_ETOOBIG);
-		return 0;
-	}
-
-	n = co - pcnfa->ncolors;
-	assert(n > 0 && n < v->g->nlacons && v->g->lacons != NULL);
-	FDEBUG(("=== testing lacon %d\n", n));
-	sub = &v->g->lacons[n];
-	d = getladfa(v, n);
-	if (d == NULL)
-		return 0;
-	if (LATYPE_IS_AHEAD(sub->subno))
-	{
-		/* used to use longest() here, but shortest() could be much cheaper */
-		end = shortest(v, d, cp, cp, v->stop,
-					   (chr **) NULL, (int *) NULL);
-		satisfied = LATYPE_IS_POS(sub->subno) ? (end != NULL) : (end == NULL);
-	}
-	else
-	{
-		/*
-		 * To avoid doing O(N^2) work when repeatedly testing a lookbehind
-		 * constraint in an N-character string, we use matchuntil() which can
-		 * cache the DFA state across calls.  We only need to restart if the
-		 * probe point decreases, which is not common.  The NFA we're using is
-		 * a search NFA, so it doesn't mind scanning over stuff before the
-		 * nominal match.
-		 */
-		satisfied = matchuntil(v, d, cp, &v->lblastcss[n], &v->lblastcp[n]);
-		if (!LATYPE_IS_POS(sub->subno))
-			satisfied = !satisfied;
-	}
-	FDEBUG(("=== lacon %d satisfied %d\n", n, satisfied));
-	return satisfied;
-}
-
-/*
- * getvacant - get a vacant state set
- *
- * This routine clears out the inarcs and outarcs, but does not otherwise
- * clear the innards of the state set -- that's up to the caller.
- */
-static struct sset *
-getvacant(struct vars * v,
-		  struct dfa * d,
-		  chr *cp,
-		  chr *start)
-{
-	int			i;
-	struct sset *ss;
-	struct sset *p;
-	struct arcp ap;
-	color		co;
-
-	ss = pickss(v, d, cp, start);
-	if (ss == NULL)
-		return NULL;
-	assert(!(ss->flags & LOCKED));
-
-	/* clear out its inarcs, including self-referential ones */
-	ap = ss->ins;
-	while ((p = ap.ss) != NULL)
-	{
-		co = ap.co;
-		FDEBUG(("zapping c%d's %ld outarc\n", (int) (p - d->ssets), (long) co));
-		p->outs[co] = NULL;
-		ap = p->inchain[co];
-		p->inchain[co].ss = NULL;		/* paranoia */
-	}
-	ss->ins.ss = NULL;
-
-	/* take it off the inarc chains of the ssets reached by its outarcs */
-	for (i = 0; i < d->ncolors; i++)
-	{
-		p = ss->outs[i];
-		assert(p != ss);		/* not self-referential */
-		if (p == NULL)
-			continue;			/* NOTE CONTINUE */
-		FDEBUG(("del outarc %d from c%d's in chn\n", i, (int) (p - d->ssets)));
-		if (p->ins.ss == ss && p->ins.co == i)
-			p->ins = ss->inchain[i];
-		else
-		{
-			struct arcp lastap = {NULL, 0};
-
-			assert(p->ins.ss != NULL);
-			for (ap = p->ins; ap.ss != NULL &&
-				 !(ap.ss == ss && ap.co == i);
-				 ap = ap.ss->inchain[ap.co])
-				lastap = ap;
-			assert(ap.ss != NULL);
-			lastap.ss->inchain[lastap.co] = ss->inchain[i];
-		}
-		ss->outs[i] = NULL;
-		ss->inchain[i].ss = NULL;
-	}
-
-	/* if ss was a success state, may need to remember location */
-	if ((ss->flags & POSTSTATE) && ss->lastseen != d->lastpost &&
-		(d->lastpost == NULL || d->lastpost < ss->lastseen))
-		d->lastpost = ss->lastseen;
-
-	/* likewise for a no-progress state */
-	if ((ss->flags & NOPROGRESS) && ss->lastseen != d->lastnopr &&
-		(d->lastnopr == NULL || d->lastnopr < ss->lastseen))
-		d->lastnopr = ss->lastseen;
-
-	return ss;
-}
-
-/*
- * pickss - pick the next stateset to be used
- */
-static struct sset *
-pickss(struct vars * v,
-	   struct dfa * d,
-	   chr *cp,
-	   chr *start)
-{
-	int			i;
-	struct sset *ss;
-	struct sset *end;
-	chr		   *ancient;
-
-	/* shortcut for cases where cache isn't full */
-	if (d->nssused < d->nssets)
-	{
-		i = d->nssused;
-		d->nssused++;
-		ss = &d->ssets[i];
-		FDEBUG(("new c%d\n", i));
-		/* set up innards */
-		ss->states = &d->statesarea[i * d->wordsper];
-		ss->flags = 0;
-		ss->ins.ss = NULL;
-		ss->ins.co = WHITE;		/* give it some value */
-		ss->outs = &d->outsarea[i * d->ncolors];
-		ss->inchain = &d->incarea[i * d->ncolors];
-		for (i = 0; i < d->ncolors; i++)
-		{
-			ss->outs[i] = NULL;
-			ss->inchain[i].ss = NULL;
-		}
-		return ss;
-	}
-
-	/* look for oldest, or old enough anyway */
-	if (cp - start > d->nssets * 2 / 3) /* oldest 33% are expendable */
-		ancient = cp - d->nssets * 2 / 3;
-	else
-		ancient = start;
-	for (ss = d->search, end = &d->ssets[d->nssets]; ss < end; ss++)
-		if ((ss->lastseen == NULL || ss->lastseen < ancient) &&
-			!(ss->flags & LOCKED))
-		{
-			d->search = ss + 1;
-			FDEBUG(("replacing c%d\n", (int) (ss - d->ssets)));
-			return ss;
-		}
-	for (ss = d->ssets, end = d->search; ss < end; ss++)
-		if ((ss->lastseen == NULL || ss->lastseen < ancient) &&
-			!(ss->flags & LOCKED))
-		{
-			d->search = ss + 1;
-			FDEBUG(("replacing c%d\n", (int) (ss - d->ssets)));
-			return ss;
-		}
-
-	/* nobody's old enough?!? -- something's really wrong */
-	FDEBUG(("cannot find victim to replace!\n"));
-	ERR(REG_ASSERT);
-	return NULL;
-}
diff --git a/src/backend/regex/regerror.c b/src/backend/regex/regerror.c
deleted file mode 100644
index f2fe696..0000000
--- a/src/backend/regex/regerror.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * regerror - error-code expansion
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regerror.c
- *
- */
-
-#include "regex/regguts.h"
-
-/* unknown-error explanation */
-static const char unk[] = "*** unknown regex error code 0x%x ***";
-
-/* struct to map among codes, code names, and explanations */
-static const struct rerr
-{
-	int			code;
-	const char *name;
-	const char *explain;
-}	rerrs[] =
-
-{
-	/* the actual table is built from regex.h */
-#include "regex/regerrs.h"		/* pgrminclude ignore */
-	{
-		-1, "", "oops"
-	},							/* explanation special-cased in code */
-};
-
-/*
- * pg_regerror - the interface to error numbers
- */
-/* ARGSUSED */
-size_t							/* actual space needed (including NUL) */
-pg_regerror(int errcode,		/* error code, or REG_ATOI or REG_ITOA */
-			const regex_t *preg,	/* associated regex_t (unused at present) */
-			char *errbuf,		/* result buffer (unless errbuf_size==0) */
-			size_t errbuf_size) /* available space in errbuf, can be 0 */
-{
-	const struct rerr *r;
-	const char *msg;
-	char		convbuf[sizeof(unk) + 50];		/* 50 = plenty for int */
-	size_t		len;
-	int			icode;
-
-	switch (errcode)
-	{
-		case REG_ATOI:			/* convert name to number */
-			for (r = rerrs; r->code >= 0; r++)
-				if (strcmp(r->name, errbuf) == 0)
-					break;
-			sprintf(convbuf, "%d", r->code);	/* -1 for unknown */
-			msg = convbuf;
-			break;
-		case REG_ITOA:			/* convert number to name */
-			icode = atoi(errbuf);		/* not our problem if this fails */
-			for (r = rerrs; r->code >= 0; r++)
-				if (r->code == icode)
-					break;
-			if (r->code >= 0)
-				msg = r->name;
-			else
-			{					/* unknown; tell him the number */
-				sprintf(convbuf, "REG_%u", (unsigned) icode);
-				msg = convbuf;
-			}
-			break;
-		default:				/* a real, normal error code */
-			for (r = rerrs; r->code >= 0; r++)
-				if (r->code == errcode)
-					break;
-			if (r->code >= 0)
-				msg = r->explain;
-			else
-			{					/* unknown; say so */
-				sprintf(convbuf, unk, errcode);
-				msg = convbuf;
-			}
-			break;
-	}
-
-	len = strlen(msg) + 1;		/* space needed, including NUL */
-	if (errbuf_size > 0)
-	{
-		if (errbuf_size > len)
-			strcpy(errbuf, msg);
-		else
-		{						/* truncate to fit */
-			memcpy(errbuf, msg, errbuf_size - 1);
-			errbuf[errbuf_size - 1] = '\0';
-		}
-	}
-
-	return len;
-}
diff --git a/src/backend/regex/regexec.c b/src/backend/regex/regexec.c
deleted file mode 100644
index 82659a0..0000000
--- a/src/backend/regex/regexec.c
+++ /dev/null
@@ -1,1425 +0,0 @@
-/*
- * re_*exec and friends - match REs
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regexec.c
- *
- */
-
-#include "regex/regguts.h"
-
-
-
-/* lazy-DFA representation */
-struct arcp
-{								/* "pointer" to an outarc */
-	struct sset *ss;
-	color		co;
-};
-
-struct sset
-{								/* state set */
-	unsigned   *states;			/* pointer to bitvector */
-	unsigned	hash;			/* hash of bitvector */
-#define  HASH(bv, nw)	 (((nw) == 1) ? *(bv) : hash(bv, nw))
-#define  HIT(h,bv,ss,nw) ((ss)->hash == (h) && ((nw) == 1 || \
-		memcmp(VS(bv), VS((ss)->states), (nw)*sizeof(unsigned)) == 0))
-	int			flags;
-#define  STARTER	 01			/* the initial state set */
-#define  POSTSTATE	 02			/* includes the goal state */
-#define  LOCKED		 04			/* locked in cache */
-#define  NOPROGRESS  010		/* zero-progress state set */
-	struct arcp ins;			/* chain of inarcs pointing here */
-	chr		   *lastseen;		/* last entered on arrival here */
-	struct sset **outs;			/* outarc vector indexed by color */
-	struct arcp *inchain;		/* chain-pointer vector for outarcs */
-};
-
-struct dfa
-{
-	int			nssets;			/* size of cache */
-	int			nssused;		/* how many entries occupied yet */
-	int			nstates;		/* number of states */
-	int			ncolors;		/* length of outarc and inchain vectors */
-	int			wordsper;		/* length of state-set bitvectors */
-	struct sset *ssets;			/* state-set cache */
-	unsigned   *statesarea;		/* bitvector storage */
-	unsigned   *work;			/* pointer to work area within statesarea */
-	struct sset **outsarea;		/* outarc-vector storage */
-	struct arcp *incarea;		/* inchain storage */
-	struct cnfa *cnfa;
-	struct colormap *cm;
-	chr		   *lastpost;		/* location of last cache-flushed success */
-	chr		   *lastnopr;		/* location of last cache-flushed NOPROGRESS */
-	struct sset *search;		/* replacement-search-pointer memory */
-	int			cptsmalloced;	/* were the areas individually malloced? */
-	char	   *mallocarea;		/* self, or master malloced area, or NULL */
-};
-
-#define WORK	1				/* number of work bitvectors needed */
-
-/* setup for non-malloc allocation for small cases */
-#define FEWSTATES	20			/* must be less than UBITS */
-#define FEWCOLORS	15
-struct smalldfa
-{
-	struct dfa	dfa;
-	struct sset ssets[FEWSTATES * 2];
-	unsigned	statesarea[FEWSTATES * 2 + WORK];
-	struct sset *outsarea[FEWSTATES * 2 * FEWCOLORS];
-	struct arcp incarea[FEWSTATES * 2 * FEWCOLORS];
-};
-
-#define DOMALLOC	((struct smalldfa *)NULL)	/* force malloc */
-
-
-
-/* internal variables, bundled for easy passing around */
-struct vars
-{
-	regex_t    *re;
-	struct guts *g;
-	int			eflags;			/* copies of arguments */
-	size_t		nmatch;
-	regmatch_t *pmatch;
-	rm_detail_t *details;
-	chr		   *start;			/* start of string */
-	chr		   *search_start;	/* search start of string */
-	chr		   *stop;			/* just past end of string */
-	int			err;			/* error code if any (0 none) */
-	struct dfa **subdfas;		/* per-tree-subre DFAs */
-	struct dfa **ladfas;		/* per-lacon-subre DFAs */
-	struct sset **lblastcss;	/* per-lacon-subre lookbehind restart data */
-	chr		  **lblastcp;		/* per-lacon-subre lookbehind restart data */
-	struct smalldfa dfa1;
-	struct smalldfa dfa2;
-};
-
-#define VISERR(vv)	((vv)->err != 0)	/* have we seen an error yet? */
-#define ISERR() VISERR(v)
-#define VERR(vv,e)	((vv)->err = ((vv)->err ? (vv)->err : (e)))
-#define ERR(e)	VERR(v, e)		/* record an error */
-#define NOERR() {if (ISERR()) return v->err;}	/* if error seen, return it */
-#define OFF(p)	((p) - v->start)
-#define LOFF(p) ((long)OFF(p))
-
-
-
-/*
- * forward declarations
- */
-/* === regexec.c === */
-static struct dfa *getsubdfa(struct vars *, struct subre *);
-static struct dfa *getladfa(struct vars *, int);
-static int	find(struct vars *, struct cnfa *, struct colormap *);
-static int	cfind(struct vars *, struct cnfa *, struct colormap *);
-static int	cfindloop(struct vars *, struct cnfa *, struct colormap *, struct dfa *, struct dfa *, chr **);
-static void zapallsubs(regmatch_t *, size_t);
-static void zaptreesubs(struct vars *, struct subre *);
-static void subset(struct vars *, struct subre *, chr *, chr *);
-static int	cdissect(struct vars *, struct subre *, chr *, chr *);
-static int	ccondissect(struct vars *, struct subre *, chr *, chr *);
-static int	crevcondissect(struct vars *, struct subre *, chr *, chr *);
-static int	cbrdissect(struct vars *, struct subre *, chr *, chr *);
-static int	caltdissect(struct vars *, struct subre *, chr *, chr *);
-static int	citerdissect(struct vars *, struct subre *, chr *, chr *);
-static int	creviterdissect(struct vars *, struct subre *, chr *, chr *);
-
-/* === rege_dfa.c === */
-static chr *longest(struct vars *, struct dfa *, chr *, chr *, int *);
-static chr *shortest(struct vars *, struct dfa *, chr *, chr *, chr *, chr **, int *);
-static int	matchuntil(struct vars *, struct dfa *, chr *, struct sset **, chr **);
-static chr *lastcold(struct vars *, struct dfa *);
-static struct dfa *newdfa(struct vars *, struct cnfa *, struct colormap *, struct smalldfa *);
-static void freedfa(struct dfa *);
-static unsigned hash(unsigned *, int);
-static struct sset *initialize(struct vars *, struct dfa *, chr *);
-static struct sset *miss(struct vars *, struct dfa *, struct sset *, pcolor, chr *, chr *);
-static int	lacon(struct vars *, struct cnfa *, chr *, pcolor);
-static struct sset *getvacant(struct vars *, struct dfa *, chr *, chr *);
-static struct sset *pickss(struct vars *, struct dfa *, chr *, chr *);
-
-
-/*
- * pg_regexec - match regular expression
- */
-int
-pg_regexec(regex_t *re,
-		   const chr *string,
-		   size_t len,
-		   size_t search_start,
-		   rm_detail_t *details,
-		   size_t nmatch,
-		   regmatch_t pmatch[],
-		   int flags)
-{
-	struct vars var;
-	register struct vars *v = &var;
-	int			st;
-	size_t		n;
-	size_t		i;
-	int			backref;
-
-#define  LOCALMAT	 20
-	regmatch_t	mat[LOCALMAT];
-
-#define  LOCALDFAS	 40
-	struct dfa *subdfas[LOCALDFAS];
-
-	/* sanity checks */
-	if (re == NULL || string == NULL || re->re_magic != REMAGIC)
-		return REG_INVARG;
-	if (re->re_csize != sizeof(chr))
-		return REG_MIXED;
-
-	/* Initialize locale-dependent support */
-	pg_set_regex_collation(re->re_collation);
-
-	/* setup */
-	v->re = re;
-	v->g = (struct guts *) re->re_guts;
-	if ((v->g->cflags & REG_EXPECT) && details == NULL)
-		return REG_INVARG;
-	if (v->g->info & REG_UIMPOSSIBLE)
-		return REG_NOMATCH;
-	backref = (v->g->info & REG_UBACKREF) ? 1 : 0;
-	v->eflags = flags;
-	if (v->g->cflags & REG_NOSUB)
-		nmatch = 0;				/* override client */
-	v->nmatch = nmatch;
-	if (backref)
-	{
-		/* need work area */
-		if (v->g->nsub + 1 <= LOCALMAT)
-			v->pmatch = mat;
-		else
-			v->pmatch = (regmatch_t *) MALLOC((v->g->nsub + 1) *
-											  sizeof(regmatch_t));
-		if (v->pmatch == NULL)
-			return REG_ESPACE;
-		v->nmatch = v->g->nsub + 1;
-	}
-	else
-		v->pmatch = pmatch;
-	v->details = details;
-	v->start = (chr *) string;
-	v->search_start = (chr *) string + search_start;
-	v->stop = (chr *) string + len;
-	v->err = 0;
-	v->subdfas = NULL;
-	v->ladfas = NULL;
-	v->lblastcss = NULL;
-	v->lblastcp = NULL;
-	/* below this point, "goto cleanup" will behave sanely */
-
-	assert(v->g->ntree >= 0);
-	n = (size_t) v->g->ntree;
-	if (n <= LOCALDFAS)
-		v->subdfas = subdfas;
-	else
-	{
-		v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
-		if (v->subdfas == NULL)
-		{
-			st = REG_ESPACE;
-			goto cleanup;
-		}
-	}
-	for (i = 0; i < n; i++)
-		v->subdfas[i] = NULL;
-
-	assert(v->g->nlacons >= 0);
-	n = (size_t) v->g->nlacons;
-	if (n > 0)
-	{
-		v->ladfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
-		if (v->ladfas == NULL)
-		{
-			st = REG_ESPACE;
-			goto cleanup;
-		}
-		for (i = 0; i < n; i++)
-			v->ladfas[i] = NULL;
-		v->lblastcss = (struct sset **) MALLOC(n * sizeof(struct sset *));
-		v->lblastcp = (chr **) MALLOC(n * sizeof(chr *));
-		if (v->lblastcss == NULL || v->lblastcp == NULL)
-		{
-			st = REG_ESPACE;
-			goto cleanup;
-		}
-		for (i = 0; i < n; i++)
-		{
-			v->lblastcss[i] = NULL;
-			v->lblastcp[i] = NULL;
-		}
-	}
-
-	/* do it */
-	assert(v->g->tree != NULL);
-	if (backref)
-		st = cfind(v, &v->g->tree->cnfa, &v->g->cmap);
-	else
-		st = find(v, &v->g->tree->cnfa, &v->g->cmap);
-
-	/* copy (portion of) match vector over if necessary */
-	if (st == REG_OKAY && v->pmatch != pmatch && nmatch > 0)
-	{
-		zapallsubs(pmatch, nmatch);
-		n = (nmatch < v->nmatch) ? nmatch : v->nmatch;
-		memcpy(VS(pmatch), VS(v->pmatch), n * sizeof(regmatch_t));
-	}
-
-	/* clean up */
-cleanup:
-	if (v->pmatch != pmatch && v->pmatch != mat)
-		FREE(v->pmatch);
-	if (v->subdfas != NULL)
-	{
-		n = (size_t) v->g->ntree;
-		for (i = 0; i < n; i++)
-		{
-			if (v->subdfas[i] != NULL)
-				freedfa(v->subdfas[i]);
-		}
-		if (v->subdfas != subdfas)
-			FREE(v->subdfas);
-	}
-	if (v->ladfas != NULL)
-	{
-		n = (size_t) v->g->nlacons;
-		for (i = 0; i < n; i++)
-		{
-			if (v->ladfas[i] != NULL)
-				freedfa(v->ladfas[i]);
-		}
-		FREE(v->ladfas);
-	}
-	if (v->lblastcss != NULL)
-		FREE(v->lblastcss);
-	if (v->lblastcp != NULL)
-		FREE(v->lblastcp);
-
-	return st;
-}
-
-/*
- * getsubdfa - create or re-fetch the DFA for a tree subre node
- *
- * We only need to create the DFA once per overall regex execution.
- * The DFA will be freed by the cleanup step in pg_regexec().
- */
-static struct dfa *
-getsubdfa(struct vars * v,
-		  struct subre * t)
-{
-	if (v->subdfas[t->id] == NULL)
-	{
-		v->subdfas[t->id] = newdfa(v, &t->cnfa, &v->g->cmap, DOMALLOC);
-		if (ISERR())
-			return NULL;
-	}
-	return v->subdfas[t->id];
-}
-
-/*
- * getladfa - create or re-fetch the DFA for a LACON subre node
- *
- * Same as above, but for LACONs.
- */
-static struct dfa *
-getladfa(struct vars * v,
-		 int n)
-{
-	assert(n > 0 && n < v->g->nlacons && v->g->lacons != NULL);
-
-	if (v->ladfas[n] == NULL)
-	{
-		struct subre *sub = &v->g->lacons[n];
-
-		v->ladfas[n] = newdfa(v, &sub->cnfa, &v->g->cmap, DOMALLOC);
-		if (ISERR())
-			return NULL;
-	}
-	return v->ladfas[n];
-}
-
-/*
- * find - find a match for the main NFA (no-complications case)
- */
-static int
-find(struct vars * v,
-	 struct cnfa * cnfa,
-	 struct colormap * cm)
-{
-	struct dfa *s;
-	struct dfa *d;
-	chr		   *begin;
-	chr		   *end = NULL;
-	chr		   *cold;
-	chr		   *open;			/* open and close of range of possible starts */
-	chr		   *close;
-	int			hitend;
-	int			shorter = (v->g->tree->flags & SHORTER) ? 1 : 0;
-
-	/* first, a shot with the search RE */
-	s = newdfa(v, &v->g->search, cm, &v->dfa1);
-	assert(!(ISERR() && s != NULL));
-	NOERR();
-	MDEBUG(("\nsearch at %ld\n", LOFF(v->start)));
-	cold = NULL;
-	close = shortest(v, s, v->search_start, v->search_start, v->stop,
-					 &cold, (int *) NULL);
-	freedfa(s);
-	NOERR();
-	if (v->g->cflags & REG_EXPECT)
-	{
-		assert(v->details != NULL);
-		if (cold != NULL)
-			v->details->rm_extend.rm_so = OFF(cold);
-		else
-			v->details->rm_extend.rm_so = OFF(v->stop);
-		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
-	}
-	if (close == NULL)			/* not found */
-		return REG_NOMATCH;
-	if (v->nmatch == 0)			/* found, don't need exact location */
-		return REG_OKAY;
-
-	/* find starting point and match */
-	assert(cold != NULL);
-	open = cold;
-	cold = NULL;
-	MDEBUG(("between %ld and %ld\n", LOFF(open), LOFF(close)));
-	d = newdfa(v, cnfa, cm, &v->dfa1);
-	assert(!(ISERR() && d != NULL));
-	NOERR();
-	for (begin = open; begin <= close; begin++)
-	{
-		MDEBUG(("\nfind trying at %ld\n", LOFF(begin)));
-		if (shorter)
-			end = shortest(v, d, begin, begin, v->stop,
-						   (chr **) NULL, &hitend);
-		else
-			end = longest(v, d, begin, v->stop, &hitend);
-		if (ISERR())
-		{
-			freedfa(d);
-			return v->err;
-		}
-		if (hitend && cold == NULL)
-			cold = begin;
-		if (end != NULL)
-			break;				/* NOTE BREAK OUT */
-	}
-	assert(end != NULL);		/* search RE succeeded so loop should */
-	freedfa(d);
-
-	/* and pin down details */
-	assert(v->nmatch > 0);
-	v->pmatch[0].rm_so = OFF(begin);
-	v->pmatch[0].rm_eo = OFF(end);
-	if (v->g->cflags & REG_EXPECT)
-	{
-		if (cold != NULL)
-			v->details->rm_extend.rm_so = OFF(cold);
-		else
-			v->details->rm_extend.rm_so = OFF(v->stop);
-		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
-	}
-	if (v->nmatch == 1)			/* no need for submatches */
-		return REG_OKAY;
-
-	/* find submatches */
-	zapallsubs(v->pmatch, v->nmatch);
-	return cdissect(v, v->g->tree, begin, end);
-}
-
-/*
- * cfind - find a match for the main NFA (with complications)
- */
-static int
-cfind(struct vars * v,
-	  struct cnfa * cnfa,
-	  struct colormap * cm)
-{
-	struct dfa *s;
-	struct dfa *d;
-	chr		   *cold;
-	int			ret;
-
-	s = newdfa(v, &v->g->search, cm, &v->dfa1);
-	NOERR();
-	d = newdfa(v, cnfa, cm, &v->dfa2);
-	if (ISERR())
-	{
-		assert(d == NULL);
-		freedfa(s);
-		return v->err;
-	}
-
-	ret = cfindloop(v, cnfa, cm, d, s, &cold);
-
-	freedfa(d);
-	freedfa(s);
-	NOERR();
-	if (v->g->cflags & REG_EXPECT)
-	{
-		assert(v->details != NULL);
-		if (cold != NULL)
-			v->details->rm_extend.rm_so = OFF(cold);
-		else
-			v->details->rm_extend.rm_so = OFF(v->stop);
-		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
-	}
-	return ret;
-}
-
-/*
- * cfindloop - the heart of cfind
- */
-static int
-cfindloop(struct vars * v,
-		  struct cnfa * cnfa,
-		  struct colormap * cm,
-		  struct dfa * d,
-		  struct dfa * s,
-		  chr **coldp)			/* where to put coldstart pointer */
-{
-	chr		   *begin;
-	chr		   *end;
-	chr		   *cold;
-	chr		   *open;			/* open and close of range of possible starts */
-	chr		   *close;
-	chr		   *estart;
-	chr		   *estop;
-	int			er;
-	int			shorter = v->g->tree->flags & SHORTER;
-	int			hitend;
-
-	assert(d != NULL && s != NULL);
-	cold = NULL;
-	close = v->search_start;
-	do
-	{
-		/* Search with the search RE for match range at/beyond "close" */
-		MDEBUG(("\ncsearch at %ld\n", LOFF(close)));
-		close = shortest(v, s, close, close, v->stop, &cold, (int *) NULL);
-		if (ISERR())
-		{
-			*coldp = cold;
-			return v->err;
-		}
-		if (close == NULL)
-			break;				/* no more possible match anywhere */
-		assert(cold != NULL);
-		open = cold;
-		cold = NULL;
-		/* Search for matches starting between "open" and "close" inclusive */
-		MDEBUG(("cbetween %ld and %ld\n", LOFF(open), LOFF(close)));
-		for (begin = open; begin <= close; begin++)
-		{
-			MDEBUG(("\ncfind trying at %ld\n", LOFF(begin)));
-			estart = begin;
-			estop = v->stop;
-			for (;;)
-			{
-				/* Here we use the top node's detailed RE */
-				if (shorter)
-					end = shortest(v, d, begin, estart,
-								   estop, (chr **) NULL, &hitend);
-				else
-					end = longest(v, d, begin, estop,
-								  &hitend);
-				if (ISERR())
-				{
-					*coldp = cold;
-					return v->err;
-				}
-				if (hitend && cold == NULL)
-					cold = begin;
-				if (end == NULL)
-					break;		/* no match with this begin point, try next */
-				MDEBUG(("tentative end %ld\n", LOFF(end)));
-				/* Dissect the potential match to see if it really matches */
-				zapallsubs(v->pmatch, v->nmatch);
-				er = cdissect(v, v->g->tree, begin, end);
-				if (er == REG_OKAY)
-				{
-					if (v->nmatch > 0)
-					{
-						v->pmatch[0].rm_so = OFF(begin);
-						v->pmatch[0].rm_eo = OFF(end);
-					}
-					*coldp = cold;
-					return REG_OKAY;
-				}
-				if (er != REG_NOMATCH)
-				{
-					ERR(er);
-					*coldp = cold;
-					return er;
-				}
-				/* Try next longer/shorter match with same begin point */
-				if (shorter)
-				{
-					if (end == estop)
-						break;	/* no more, so try next begin point */
-					estart = end + 1;
-				}
-				else
-				{
-					if (end == begin)
-						break;	/* no more, so try next begin point */
-					estop = end - 1;
-				}
-			}					/* end loop over endpoint positions */
-		}						/* end loop over beginning positions */
-
-		/*
-		 * If we get here, there is no possible match starting at or before
-		 * "close", so consider matches beyond that.  We'll do a fresh search
-		 * with the search RE to find a new promising match range.
-		 */
-		close++;
-	} while (close < v->stop);
-
-	*coldp = cold;
-	return REG_NOMATCH;
-}
-
-/*
- * zapallsubs - initialize all subexpression matches to "no match"
- */
-static void
-zapallsubs(regmatch_t *p,
-		   size_t n)
-{
-	size_t		i;
-
-	for (i = n - 1; i > 0; i--)
-	{
-		p[i].rm_so = -1;
-		p[i].rm_eo = -1;
-	}
-}
-
-/*
- * zaptreesubs - initialize subexpressions within subtree to "no match"
- */
-static void
-zaptreesubs(struct vars * v,
-			struct subre * t)
-{
-	if (t->op == '(')
-	{
-		int			n = t->subno;
-
-		assert(n > 0);
-		if ((size_t) n < v->nmatch)
-		{
-			v->pmatch[n].rm_so = -1;
-			v->pmatch[n].rm_eo = -1;
-		}
-	}
-
-	if (t->left != NULL)
-		zaptreesubs(v, t->left);
-	if (t->right != NULL)
-		zaptreesubs(v, t->right);
-}
-
-/*
- * subset - set subexpression match data for a successful subre
- */
-static void
-subset(struct vars * v,
-	   struct subre * sub,
-	   chr *begin,
-	   chr *end)
-{
-	int			n = sub->subno;
-
-	assert(n > 0);
-	if ((size_t) n >= v->nmatch)
-		return;
-
-	MDEBUG(("setting %d\n", n));
-	v->pmatch[n].rm_so = OFF(begin);
-	v->pmatch[n].rm_eo = OFF(end);
-}
-
-/*
- * cdissect - check backrefs and determine subexpression matches
- *
- * cdissect recursively processes a subre tree to check matching of backrefs
- * and/or identify submatch boundaries for capture nodes.  The proposed match
- * runs from "begin" to "end" (not including "end"), and we are basically
- * "dissecting" it to see where the submatches are.
- *
- * Before calling any level of cdissect, the caller must have run the node's
- * DFA and found that the proposed substring satisfies the DFA.  (We make
- * the caller do that because in concatenation and iteration nodes, it's
- * much faster to check all the substrings against the child DFAs before we
- * recurse.)  Also, caller must have cleared subexpression match data via
- * zaptreesubs (or zapallsubs at the top level).
- */
-static int						/* regexec return code */
-cdissect(struct vars * v,
-		 struct subre * t,
-		 chr *begin,			/* beginning of relevant substring */
-		 chr *end)				/* end of same */
-{
-	int			er;
-
-	assert(t != NULL);
-	MDEBUG(("cdissect %ld-%ld %c\n", LOFF(begin), LOFF(end), t->op));
-
-	/* handy place to check for operation cancel */
-	if (CANCEL_REQUESTED(v->re))
-		return REG_CANCEL;
-	/* ... and stack overrun */
-	if (STACK_TOO_DEEP(v->re))
-		return REG_ETOOBIG;
-
-	switch (t->op)
-	{
-		case '=':				/* terminal node */
-			assert(t->left == NULL && t->right == NULL);
-			er = REG_OKAY;		/* no action, parent did the work */
-			break;
-		case 'b':				/* back reference */
-			assert(t->left == NULL && t->right == NULL);
-			er = cbrdissect(v, t, begin, end);
-			break;
-		case '.':				/* concatenation */
-			assert(t->left != NULL && t->right != NULL);
-			if (t->left->flags & SHORTER)		/* reverse scan */
-				er = crevcondissect(v, t, begin, end);
-			else
-				er = ccondissect(v, t, begin, end);
-			break;
-		case '|':				/* alternation */
-			assert(t->left != NULL);
-			er = caltdissect(v, t, begin, end);
-			break;
-		case '*':				/* iteration */
-			assert(t->left != NULL);
-			if (t->left->flags & SHORTER)		/* reverse scan */
-				er = creviterdissect(v, t, begin, end);
-			else
-				er = citerdissect(v, t, begin, end);
-			break;
-		case '(':				/* capturing */
-			assert(t->left != NULL && t->right == NULL);
-			assert(t->subno > 0);
-			er = cdissect(v, t->left, begin, end);
-			if (er == REG_OKAY)
-				subset(v, t, begin, end);
-			break;
-		default:
-			er = REG_ASSERT;
-			break;
-	}
-
-	/*
-	 * We should never have a match failure unless backrefs lurk below;
-	 * otherwise, either caller failed to check the DFA, or there's some
-	 * inconsistency between the DFA and the node's innards.
-	 */
-	assert(er != REG_NOMATCH || (t->flags & BACKR));
-
-	return er;
-}
-
-/*
- * ccondissect - dissect match for concatenation node
- */
-static int						/* regexec return code */
-ccondissect(struct vars * v,
-			struct subre * t,
-			chr *begin,			/* beginning of relevant substring */
-			chr *end)			/* end of same */
-{
-	struct dfa *d;
-	struct dfa *d2;
-	chr		   *mid;
-	int			er;
-
-	assert(t->op == '.');
-	assert(t->left != NULL && t->left->cnfa.nstates > 0);
-	assert(t->right != NULL && t->right->cnfa.nstates > 0);
-	assert(!(t->left->flags & SHORTER));
-
-	d = getsubdfa(v, t->left);
-	NOERR();
-	d2 = getsubdfa(v, t->right);
-	NOERR();
-	MDEBUG(("cconcat %d\n", t->id));
-
-	/* pick a tentative midpoint */
-	mid = longest(v, d, begin, end, (int *) NULL);
-	NOERR();
-	if (mid == NULL)
-		return REG_NOMATCH;
-	MDEBUG(("tentative midpoint %ld\n", LOFF(mid)));
-
-	/* iterate until satisfaction or failure */
-	for (;;)
-	{
-		/* try this midpoint on for size */
-		if (longest(v, d2, mid, end, (int *) NULL) == end)
-		{
-			er = cdissect(v, t->left, begin, mid);
-			if (er == REG_OKAY)
-			{
-				er = cdissect(v, t->right, mid, end);
-				if (er == REG_OKAY)
-				{
-					/* satisfaction */
-					MDEBUG(("successful\n"));
-					return REG_OKAY;
-				}
-			}
-			if (er != REG_NOMATCH)
-				return er;
-		}
-		NOERR();
-
-		/* that midpoint didn't work, find a new one */
-		if (mid == begin)
-		{
-			/* all possibilities exhausted */
-			MDEBUG(("%d no midpoint\n", t->id));
-			return REG_NOMATCH;
-		}
-		mid = longest(v, d, begin, mid - 1, (int *) NULL);
-		NOERR();
-		if (mid == NULL)
-		{
-			/* failed to find a new one */
-			MDEBUG(("%d failed midpoint\n", t->id));
-			return REG_NOMATCH;
-		}
-		MDEBUG(("%d: new midpoint %ld\n", t->id, LOFF(mid)));
-		zaptreesubs(v, t->left);
-		zaptreesubs(v, t->right);
-	}
-
-	/* can't get here */
-	return REG_ASSERT;
-}
-
-/*
- * crevcondissect - dissect match for concatenation node, shortest-first
- */
-static int						/* regexec return code */
-crevcondissect(struct vars * v,
-			   struct subre * t,
-			   chr *begin,		/* beginning of relevant substring */
-			   chr *end)		/* end of same */
-{
-	struct dfa *d;
-	struct dfa *d2;
-	chr		   *mid;
-	int			er;
-
-	assert(t->op == '.');
-	assert(t->left != NULL && t->left->cnfa.nstates > 0);
-	assert(t->right != NULL && t->right->cnfa.nstates > 0);
-	assert(t->left->flags & SHORTER);
-
-	d = getsubdfa(v, t->left);
-	NOERR();
-	d2 = getsubdfa(v, t->right);
-	NOERR();
-	MDEBUG(("crevcon %d\n", t->id));
-
-	/* pick a tentative midpoint */
-	mid = shortest(v, d, begin, begin, end, (chr **) NULL, (int *) NULL);
-	NOERR();
-	if (mid == NULL)
-		return REG_NOMATCH;
-	MDEBUG(("tentative midpoint %ld\n", LOFF(mid)));
-
-	/* iterate until satisfaction or failure */
-	for (;;)
-	{
-		/* try this midpoint on for size */
-		if (longest(v, d2, mid, end, (int *) NULL) == end)
-		{
-			er = cdissect(v, t->left, begin, mid);
-			if (er == REG_OKAY)
-			{
-				er = cdissect(v, t->right, mid, end);
-				if (er == REG_OKAY)
-				{
-					/* satisfaction */
-					MDEBUG(("successful\n"));
-					return REG_OKAY;
-				}
-			}
-			if (er != REG_NOMATCH)
-				return er;
-		}
-		NOERR();
-
-		/* that midpoint didn't work, find a new one */
-		if (mid == end)
-		{
-			/* all possibilities exhausted */
-			MDEBUG(("%d no midpoint\n", t->id));
-			return REG_NOMATCH;
-		}
-		mid = shortest(v, d, begin, mid + 1, end, (chr **) NULL, (int *) NULL);
-		NOERR();
-		if (mid == NULL)
-		{
-			/* failed to find a new one */
-			MDEBUG(("%d failed midpoint\n", t->id));
-			return REG_NOMATCH;
-		}
-		MDEBUG(("%d: new midpoint %ld\n", t->id, LOFF(mid)));
-		zaptreesubs(v, t->left);
-		zaptreesubs(v, t->right);
-	}
-
-	/* can't get here */
-	return REG_ASSERT;
-}
-
-/*
- * cbrdissect - dissect match for backref node
- */
-static int						/* regexec return code */
-cbrdissect(struct vars * v,
-		   struct subre * t,
-		   chr *begin,			/* beginning of relevant substring */
-		   chr *end)			/* end of same */
-{
-	int			n = t->subno;
-	size_t		numreps;
-	size_t		tlen;
-	size_t		brlen;
-	chr		   *brstring;
-	chr		   *p;
-	int			min = t->min;
-	int			max = t->max;
-
-	assert(t != NULL);
-	assert(t->op == 'b');
-	assert(n >= 0);
-	assert((size_t) n < v->nmatch);
-
-	MDEBUG(("cbackref n%d %d{%d-%d}\n", t->id, n, min, max));
-
-	/* get the backreferenced string */
-	if (v->pmatch[n].rm_so == -1)
-		return REG_NOMATCH;
-	brstring = v->start + v->pmatch[n].rm_so;
-	brlen = v->pmatch[n].rm_eo - v->pmatch[n].rm_so;
-
-	/* special cases for zero-length strings */
-	if (brlen == 0)
-	{
-		/*
-		 * matches only if target is zero length, but any number of
-		 * repetitions can be considered to be present
-		 */
-		if (begin == end && min <= max)
-		{
-			MDEBUG(("cbackref matched trivially\n"));
-			return REG_OKAY;
-		}
-		return REG_NOMATCH;
-	}
-	if (begin == end)
-	{
-		/* matches only if zero repetitions are okay */
-		if (min == 0)
-		{
-			MDEBUG(("cbackref matched trivially\n"));
-			return REG_OKAY;
-		}
-		return REG_NOMATCH;
-	}
-
-	/*
-	 * check target length to see if it could possibly be an allowed number of
-	 * repetitions of brstring
-	 */
-	assert(end > begin);
-	tlen = end - begin;
-	if (tlen % brlen != 0)
-		return REG_NOMATCH;
-	numreps = tlen / brlen;
-	if (numreps < min || (numreps > max && max != DUPINF))
-		return REG_NOMATCH;
-
-	/* okay, compare the actual string contents */
-	p = begin;
-	while (numreps-- > 0)
-	{
-		if ((*v->g->compare) (brstring, p, brlen) != 0)
-			return REG_NOMATCH;
-		p += brlen;
-	}
-
-	MDEBUG(("cbackref matched\n"));
-	return REG_OKAY;
-}
-
-/*
- * caltdissect - dissect match for alternation node
- */
-static int						/* regexec return code */
-caltdissect(struct vars * v,
-			struct subre * t,
-			chr *begin,			/* beginning of relevant substring */
-			chr *end)			/* end of same */
-{
-	struct dfa *d;
-	int			er;
-
-	/* We loop, rather than tail-recurse, to handle a chain of alternatives */
-	while (t != NULL)
-	{
-		assert(t->op == '|');
-		assert(t->left != NULL && t->left->cnfa.nstates > 0);
-
-		MDEBUG(("calt n%d\n", t->id));
-
-		d = getsubdfa(v, t->left);
-		NOERR();
-		if (longest(v, d, begin, end, (int *) NULL) == end)
-		{
-			MDEBUG(("calt matched\n"));
-			er = cdissect(v, t->left, begin, end);
-			if (er != REG_NOMATCH)
-				return er;
-		}
-		NOERR();
-
-		t = t->right;
-	}
-
-	return REG_NOMATCH;
-}
-
-/*
- * citerdissect - dissect match for iteration node
- */
-static int						/* regexec return code */
-citerdissect(struct vars * v,
-			 struct subre * t,
-			 chr *begin,		/* beginning of relevant substring */
-			 chr *end)			/* end of same */
-{
-	struct dfa *d;
-	chr		  **endpts;
-	chr		   *limit;
-	int			min_matches;
-	size_t		max_matches;
-	int			nverified;
-	int			k;
-	int			i;
-	int			er;
-
-	assert(t->op == '*');
-	assert(t->left != NULL && t->left->cnfa.nstates > 0);
-	assert(!(t->left->flags & SHORTER));
-	assert(begin <= end);
-
-	/*
-	 * For the moment, assume the minimum number of matches is 1.  If zero
-	 * matches are allowed, and the target string is empty, we are allowed to
-	 * match regardless of the contents of the iter node --- but we would
-	 * prefer to match once, so that capturing parens get set.  (An example of
-	 * the concern here is a pattern like "()*\1", which historically this
-	 * code has allowed to succeed.)  Therefore, we deal with the zero-matches
-	 * case at the bottom, after failing to find any other way to match.
-	 */
-	min_matches = t->min;
-	if (min_matches <= 0)
-		min_matches = 1;
-
-	/*
-	 * We need workspace to track the endpoints of each sub-match.  Normally
-	 * we consider only nonzero-length sub-matches, so there can be at most
-	 * end-begin of them.  However, if min is larger than that, we will also
-	 * consider zero-length sub-matches in order to find enough matches.
-	 *
-	 * For convenience, endpts[0] contains the "begin" pointer and we store
-	 * sub-match endpoints in endpts[1..max_matches].
-	 */
-	max_matches = end - begin;
-	if (max_matches > t->max && t->max != DUPINF)
-		max_matches = t->max;
-	if (max_matches < min_matches)
-		max_matches = min_matches;
-	endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
-	if (endpts == NULL)
-		return REG_ESPACE;
-	endpts[0] = begin;
-
-	d = getsubdfa(v, t->left);
-	if (ISERR())
-	{
-		FREE(endpts);
-		return v->err;
-	}
-	MDEBUG(("citer %d\n", t->id));
-
-	/*
-	 * Our strategy is to first find a set of sub-match endpoints that are
-	 * valid according to the child node's DFA, and then recursively dissect
-	 * each sub-match to confirm validity.  If any validity check fails,
-	 * backtrack the last sub-match and try again.  And, when we next try for
-	 * a validity check, we need not recheck any successfully verified
-	 * sub-matches that we didn't move the endpoints of.  nverified remembers
-	 * how many sub-matches are currently known okay.
-	 */
-
-	/* initialize to consider first sub-match */
-	nverified = 0;
-	k = 1;
-	limit = end;
-
-	/* iterate until satisfaction or failure */
-	while (k > 0)
-	{
-		/* try to find an endpoint for the k'th sub-match */
-		endpts[k] = longest(v, d, endpts[k - 1], limit, (int *) NULL);
-		if (ISERR())
-		{
-			FREE(endpts);
-			return v->err;
-		}
-		if (endpts[k] == NULL)
-		{
-			/* no match possible, so see if we can shorten previous one */
-			k--;
-			goto backtrack;
-		}
-		MDEBUG(("%d: working endpoint %d: %ld\n",
-				t->id, k, LOFF(endpts[k])));
-
-		/* k'th sub-match can no longer be considered verified */
-		if (nverified >= k)
-			nverified = k - 1;
-
-		if (endpts[k] != end)
-		{
-			/* haven't reached end yet, try another iteration if allowed */
-			if (k >= max_matches)
-			{
-				/* must try to shorten some previous match */
-				k--;
-				goto backtrack;
-			}
-
-			/* reject zero-length match unless necessary to achieve min */
-			if (endpts[k] == endpts[k - 1] &&
-				(k >= min_matches || min_matches - k < end - endpts[k]))
-				goto backtrack;
-
-			k++;
-			limit = end;
-			continue;
-		}
-
-		/*
-		 * We've identified a way to divide the string into k sub-matches that
-		 * works so far as the child DFA can tell.  If k is an allowed number
-		 * of matches, start the slow part: recurse to verify each sub-match.
-		 * We always have k <= max_matches, needn't check that.
-		 */
-		if (k < min_matches)
-			goto backtrack;
-
-		MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k));
-
-		for (i = nverified + 1; i <= k; i++)
-		{
-			zaptreesubs(v, t->left);
-			er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
-			if (er == REG_OKAY)
-			{
-				nverified = i;
-				continue;
-			}
-			if (er == REG_NOMATCH)
-				break;
-			/* oops, something failed */
-			FREE(endpts);
-			return er;
-		}
-
-		if (i > k)
-		{
-			/* satisfaction */
-			MDEBUG(("%d successful\n", t->id));
-			FREE(endpts);
-			return REG_OKAY;
-		}
-
-		/* match failed to verify, so backtrack */
-
-backtrack:
-
-		/*
-		 * Must consider shorter versions of the current sub-match.  However,
-		 * we'll only ask for a zero-length match if necessary.
-		 */
-		while (k > 0)
-		{
-			chr		   *prev_end = endpts[k - 1];
-
-			if (endpts[k] > prev_end)
-			{
-				limit = endpts[k] - 1;
-				if (limit > prev_end ||
-					(k < min_matches && min_matches - k >= end - prev_end))
-				{
-					/* break out of backtrack loop, continue the outer one */
-					break;
-				}
-			}
-			/* can't shorten k'th sub-match any more, consider previous one */
-			k--;
-		}
-	}
-
-	/* all possibilities exhausted */
-	FREE(endpts);
-
-	/*
-	 * Now consider the possibility that we can match to a zero-length string
-	 * by using zero repetitions.
-	 */
-	if (t->min == 0 && begin == end)
-	{
-		MDEBUG(("%d allowing zero matches\n", t->id));
-		return REG_OKAY;
-	}
-
-	MDEBUG(("%d failed\n", t->id));
-	return REG_NOMATCH;
-}
-
-/*
- * creviterdissect - dissect match for iteration node, shortest-first
- */
-static int						/* regexec return code */
-creviterdissect(struct vars * v,
-				struct subre * t,
-				chr *begin,		/* beginning of relevant substring */
-				chr *end)		/* end of same */
-{
-	struct dfa *d;
-	chr		  **endpts;
-	chr		   *limit;
-	int			min_matches;
-	size_t		max_matches;
-	int			nverified;
-	int			k;
-	int			i;
-	int			er;
-
-	assert(t->op == '*');
-	assert(t->left != NULL && t->left->cnfa.nstates > 0);
-	assert(t->left->flags & SHORTER);
-	assert(begin <= end);
-
-	/*
-	 * If zero matches are allowed, and target string is empty, just declare
-	 * victory.  OTOH, if target string isn't empty, zero matches can't work
-	 * so we pretend the min is 1.
-	 */
-	min_matches = t->min;
-	if (min_matches <= 0)
-	{
-		if (begin == end)
-			return REG_OKAY;
-		min_matches = 1;
-	}
-
-	/*
-	 * We need workspace to track the endpoints of each sub-match.  Normally
-	 * we consider only nonzero-length sub-matches, so there can be at most
-	 * end-begin of them.  However, if min is larger than that, we will also
-	 * consider zero-length sub-matches in order to find enough matches.
-	 *
-	 * For convenience, endpts[0] contains the "begin" pointer and we store
-	 * sub-match endpoints in endpts[1..max_matches].
-	 */
-	max_matches = end - begin;
-	if (max_matches > t->max && t->max != DUPINF)
-		max_matches = t->max;
-	if (max_matches < min_matches)
-		max_matches = min_matches;
-	endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
-	if (endpts == NULL)
-		return REG_ESPACE;
-	endpts[0] = begin;
-
-	d = getsubdfa(v, t->left);
-	if (ISERR())
-	{
-		FREE(endpts);
-		return v->err;
-	}
-	MDEBUG(("creviter %d\n", t->id));
-
-	/*
-	 * Our strategy is to first find a set of sub-match endpoints that are
-	 * valid according to the child node's DFA, and then recursively dissect
-	 * each sub-match to confirm validity.  If any validity check fails,
-	 * backtrack the last sub-match and try again.  And, when we next try for
-	 * a validity check, we need not recheck any successfully verified
-	 * sub-matches that we didn't move the endpoints of.  nverified remembers
-	 * how many sub-matches are currently known okay.
-	 */
-
-	/* initialize to consider first sub-match */
-	nverified = 0;
-	k = 1;
-	limit = begin;
-
-	/* iterate until satisfaction or failure */
-	while (k > 0)
-	{
-		/* disallow zero-length match unless necessary to achieve min */
-		if (limit == endpts[k - 1] &&
-			limit != end &&
-			(k >= min_matches || min_matches - k < end - limit))
-			limit++;
-
-		/* if this is the last allowed sub-match, it must reach to the end */
-		if (k >= max_matches)
-			limit = end;
-
-		/* try to find an endpoint for the k'th sub-match */
-		endpts[k] = shortest(v, d, endpts[k - 1], limit, end,
-							 (chr **) NULL, (int *) NULL);
-		if (ISERR())
-		{
-			FREE(endpts);
-			return v->err;
-		}
-		if (endpts[k] == NULL)
-		{
-			/* no match possible, so see if we can lengthen previous one */
-			k--;
-			goto backtrack;
-		}
-		MDEBUG(("%d: working endpoint %d: %ld\n",
-				t->id, k, LOFF(endpts[k])));
-
-		/* k'th sub-match can no longer be considered verified */
-		if (nverified >= k)
-			nverified = k - 1;
-
-		if (endpts[k] != end)
-		{
-			/* haven't reached end yet, try another iteration if allowed */
-			if (k >= max_matches)
-			{
-				/* must try to lengthen some previous match */
-				k--;
-				goto backtrack;
-			}
-
-			k++;
-			limit = endpts[k - 1];
-			continue;
-		}
-
-		/*
-		 * We've identified a way to divide the string into k sub-matches that
-		 * works so far as the child DFA can tell.  If k is an allowed number
-		 * of matches, start the slow part: recurse to verify each sub-match.
-		 * We always have k <= max_matches, needn't check that.
-		 */
-		if (k < min_matches)
-			goto backtrack;
-
-		MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k));
-
-		for (i = nverified + 1; i <= k; i++)
-		{
-			zaptreesubs(v, t->left);
-			er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
-			if (er == REG_OKAY)
-			{
-				nverified = i;
-				continue;
-			}
-			if (er == REG_NOMATCH)
-				break;
-			/* oops, something failed */
-			FREE(endpts);
-			return er;
-		}
-
-		if (i > k)
-		{
-			/* satisfaction */
-			MDEBUG(("%d successful\n", t->id));
-			FREE(endpts);
-			return REG_OKAY;
-		}
-
-		/* match failed to verify, so backtrack */
-
-backtrack:
-
-		/*
-		 * Must consider longer versions of the current sub-match.
-		 */
-		while (k > 0)
-		{
-			if (endpts[k] < end)
-			{
-				limit = endpts[k] + 1;
-				/* break out of backtrack loop, continue the outer one */
-				break;
-			}
-			/* can't lengthen k'th sub-match any more, consider previous one */
-			k--;
-		}
-	}
-
-	/* all possibilities exhausted */
-	MDEBUG(("%d failed\n", t->id));
-	FREE(endpts);
-	return REG_NOMATCH;
-}
-
-
-
-#include "rege_dfa.c"
diff --git a/src/backend/regex/regexport.c b/src/backend/regex/regexport.c
deleted file mode 100644
index 9134071..0000000
--- a/src/backend/regex/regexport.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * regexport.c
- *	  Functions for exporting info about a regex's NFA
- *
- * In this implementation, the NFA defines a necessary but not sufficient
- * condition for a string to match the regex: that is, there can be strings
- * that match the NFA but don't match the full regex, but not vice versa.
- * Thus, for example, it is okay for the functions below to ignore lookaround
- * constraints, which merely constrain the string some more.
- *
- * Notice that these functions return info into caller-provided arrays
- * rather than doing their own malloc's.  This simplifies the APIs by
- * eliminating a class of error conditions, and in the case of colors
- * allows the caller to decide how big is too big to bother with.
- *
- *
- * Portions Copyright (c) 2013-2015, PostgreSQL Global Development Group
- * Portions Copyright (c) 1998, 1999 Henry Spencer
- *
- * IDENTIFICATION
- *	  src/backend/regex/regexport.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "regex/regguts.h"
-
-#include "regex/regexport.h"
-
-static void scancolormap(struct colormap * cm, int co,
-			 union tree * t, int level, chr partial,
-			 pg_wchar **chars, int *chars_len);
-
-
-/*
- * Get total number of NFA states.
- */
-int
-pg_reg_getnumstates(const regex_t *regex)
-{
-	struct cnfa *cnfa;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	return cnfa->nstates;
-}
-
-/*
- * Get initial state of NFA.
- */
-int
-pg_reg_getinitialstate(const regex_t *regex)
-{
-	struct cnfa *cnfa;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	return cnfa->pre;
-}
-
-/*
- * Get final state of NFA.
- */
-int
-pg_reg_getfinalstate(const regex_t *regex)
-{
-	struct cnfa *cnfa;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	return cnfa->post;
-}
-
-/*
- * Get number of outgoing NFA arcs of state number "st".
- *
- * Note: LACON arcs are ignored, both here and in pg_reg_getoutarcs().
- */
-int
-pg_reg_getnumoutarcs(const regex_t *regex, int st)
-{
-	struct cnfa *cnfa;
-	struct carc *ca;
-	int			count;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	if (st < 0 || st >= cnfa->nstates)
-		return 0;
-	count = 0;
-	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-	{
-		if (ca->co < cnfa->ncolors)
-			count++;
-	}
-	return count;
-}
-
-/*
- * Write array of outgoing NFA arcs of state number "st" into arcs[],
- * whose length arcs_len must be at least as long as indicated by
- * pg_reg_getnumoutarcs(), else not all arcs will be returned.
- */
-void
-pg_reg_getoutarcs(const regex_t *regex, int st,
-				  regex_arc_t *arcs, int arcs_len)
-{
-	struct cnfa *cnfa;
-	struct carc *ca;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	if (st < 0 || st >= cnfa->nstates || arcs_len <= 0)
-		return;
-	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-	{
-		if (ca->co < cnfa->ncolors)
-		{
-			arcs->co = ca->co;
-			arcs->to = ca->to;
-			arcs++;
-			if (--arcs_len == 0)
-				break;
-		}
-	}
-}
-
-/*
- * Get total number of colors.
- */
-int
-pg_reg_getnumcolors(const regex_t *regex)
-{
-	struct colormap *cm;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cm = &((struct guts *) regex->re_guts)->cmap;
-
-	return cm->max + 1;
-}
-
-/*
- * Check if color is beginning of line/string.
- *
- * (We might at some point need to offer more refined handling of pseudocolors,
- * but this will do for now.)
- */
-int
-pg_reg_colorisbegin(const regex_t *regex, int co)
-{
-	struct cnfa *cnfa;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	if (co == cnfa->bos[0] || co == cnfa->bos[1])
-		return true;
-	else
-		return false;
-}
-
-/*
- * Check if color is end of line/string.
- */
-int
-pg_reg_colorisend(const regex_t *regex, int co)
-{
-	struct cnfa *cnfa;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cnfa = &((struct guts *) regex->re_guts)->search;
-
-	if (co == cnfa->eos[0] || co == cnfa->eos[1])
-		return true;
-	else
-		return false;
-}
-
-/*
- * Get number of member chrs of color number "co".
- *
- * Note: we return -1 if the color number is invalid, or if it is a special
- * color (WHITE or a pseudocolor), or if the number of members is uncertain.
- * The latter case cannot arise right now but is specified to allow for future
- * improvements (see musings about run-time handling of higher character codes
- * in regex/README).  Callers should not try to extract the members if -1 is
- * returned.
- */
-int
-pg_reg_getnumcharacters(const regex_t *regex, int co)
-{
-	struct colormap *cm;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cm = &((struct guts *) regex->re_guts)->cmap;
-
-	if (co <= 0 || co > cm->max)	/* we reject 0 which is WHITE */
-		return -1;
-	if (cm->cd[co].flags & PSEUDO)		/* also pseudocolors (BOS etc) */
-		return -1;
-
-	return cm->cd[co].nchrs;
-}
-
-/*
- * Write array of member chrs of color number "co" into chars[],
- * whose length chars_len must be at least as long as indicated by
- * pg_reg_getnumcharacters(), else not all chars will be returned.
- *
- * Fetching the members of WHITE or a pseudocolor is not supported.
- *
- * Caution: this is a relatively expensive operation.
- */
-void
-pg_reg_getcharacters(const regex_t *regex, int co,
-					 pg_wchar *chars, int chars_len)
-{
-	struct colormap *cm;
-
-	assert(regex != NULL && regex->re_magic == REMAGIC);
-	cm = &((struct guts *) regex->re_guts)->cmap;
-
-	if (co <= 0 || co > cm->max || chars_len <= 0)
-		return;
-	if (cm->cd[co].flags & PSEUDO)
-		return;
-
-	/* Recursively search the colormap tree */
-	scancolormap(cm, co, cm->tree, 0, 0, &chars, &chars_len);
-}
-
-/*
- * Recursively scan the colormap tree to find chrs belonging to color "co".
- * See regex/README for info about the tree structure.
- *
- * t: tree block to scan
- * level: level (from 0) of t
- * partial: partial chr code for chrs within t
- * chars, chars_len: output area
- */
-static void
-scancolormap(struct colormap * cm, int co,
-			 union tree * t, int level, chr partial,
-			 pg_wchar **chars, int *chars_len)
-{
-	int			i;
-
-	if (level < NBYTS - 1)
-	{
-		/* non-leaf node */
-		for (i = 0; i < BYTTAB; i++)
-		{
-			/*
-			 * We do not support search for chrs of color 0 (WHITE), so
-			 * all-white subtrees need not be searched.  These can be
-			 * recognized because they are represented by the fill blocks in
-			 * the colormap struct.  This typically allows us to avoid
-			 * scanning large regions of higher-numbered chrs.
-			 */
-			if (t->tptr[i] == &cm->tree[level + 1])
-				continue;
-
-			/* Recursively scan next level down */
-			scancolormap(cm, co,
-						 t->tptr[i], level + 1,
-						 (partial | (chr) i) << BYTBITS,
-						 chars, chars_len);
-		}
-	}
-	else
-	{
-		/* leaf node */
-		for (i = 0; i < BYTTAB; i++)
-		{
-			if (t->tcolor[i] == co)
-			{
-				if (*chars_len > 0)
-				{
-					**chars = partial | (chr) i;
-					(*chars)++;
-					(*chars_len)--;
-				}
-			}
-		}
-	}
-}
diff --git a/src/backend/regex/regfree.c b/src/backend/regex/regfree.c
deleted file mode 100644
index ae17ae7..0000000
--- a/src/backend/regex/regfree.c
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * regfree - free an RE
- *
- * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
- *
- * Development of this software was funded, in part, by Cray Research Inc.,
- * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
- * Corporation, none of whom are responsible for the results.  The author
- * thanks all of them.
- *
- * Redistribution and use in source and binary forms -- with or without
- * modification -- are permitted for any purpose, provided that
- * redistributions in source form retain this entire copyright notice and
- * indicate the origin and nature of any modifications.
- *
- * I'd appreciate being given credit for this package in the documentation
- * of software which uses it, but that is not a requirement.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
- * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * src/backend/regex/regfree.c
- *
- *
- * You might think that this could be incorporated into regcomp.c, and
- * that would be a reasonable idea... except that this is a generic
- * function (with a generic name), applicable to all compiled REs
- * regardless of the size of their characters, whereas the stuff in
- * regcomp.c gets compiled once per character size.
- */
-
-#include "regex/regguts.h"
-
-
-/*
- * pg_regfree - free an RE (generic function, punts to RE-specific function)
- *
- * Ignoring invocation with NULL is a convenience.
- */
-void
-pg_regfree(regex_t *re)
-{
-	if (re == NULL)
-		return;
-	(*((struct fns *) re->re_fns)->free) (re);
-}
diff --git a/src/backend/regex/regprefix.c b/src/backend/regex/regprefix.c
deleted file mode 100644
index 8692845..0000000
--- a/src/backend/regex/regprefix.c
+++ /dev/null
@@ -1,257 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * regprefix.c
- *	  Extract a common prefix, if any, from a compiled regex.
- *
- *
- * Portions Copyright (c) 2012-2015, PostgreSQL Global Development Group
- * Portions Copyright (c) 1998, 1999 Henry Spencer
- *
- * IDENTIFICATION
- *	  src/backend/regex/regprefix.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "regex/regguts.h"
-
-
-/*
- * forward declarations
- */
-static int findprefix(struct cnfa * cnfa, struct colormap * cm,
-		   chr *string, size_t *slength);
-
-
-/*
- * pg_regprefix - get common prefix for regular expression
- *
- * Returns one of:
- *	REG_NOMATCH: there is no common prefix of strings matching the regex
- *	REG_PREFIX: there is a common prefix of strings matching the regex
- *	REG_EXACT: all strings satisfying the regex must match the same string
- *	or a REG_XXX error code
- *
- * In the non-failure cases, *string is set to a malloc'd string containing
- * the common prefix or exact value, of length *slength (measured in chrs
- * not bytes!).
- *
- * This function does not analyze all complex cases (such as lookaround
- * constraints) exactly.  Therefore it is possible that some strings matching
- * the reported prefix or exact-match string do not satisfy the regex.  But
- * it should never be the case that a string satisfying the regex does not
- * match the reported prefix or exact-match string.
- */
-int
-pg_regprefix(regex_t *re,
-			 chr **string,
-			 size_t *slength)
-{
-	struct guts *g;
-	struct cnfa *cnfa;
-	int			st;
-
-	/* sanity checks */
-	if (string == NULL || slength == NULL)
-		return REG_INVARG;
-	*string = NULL;				/* initialize for failure cases */
-	*slength = 0;
-	if (re == NULL || re->re_magic != REMAGIC)
-		return REG_INVARG;
-	if (re->re_csize != sizeof(chr))
-		return REG_MIXED;
-
-	/* Initialize locale-dependent support */
-	pg_set_regex_collation(re->re_collation);
-
-	/* setup */
-	g = (struct guts *) re->re_guts;
-	if (g->info & REG_UIMPOSSIBLE)
-		return REG_NOMATCH;
-
-	/*
-	 * This implementation considers only the search NFA for the topmost regex
-	 * tree node.  Therefore, constraints such as backrefs are not fully
-	 * applied, which is allowed per the function's API spec.
-	 */
-	assert(g->tree != NULL);
-	cnfa = &g->tree->cnfa;
-
-	/*
-	 * Since a correct NFA should never contain any exit-free loops, it should
-	 * not be possible for our traversal to return to a previously visited NFA
-	 * state.  Hence we need at most nstates chrs in the output string.
-	 */
-	*string = (chr *) MALLOC(cnfa->nstates * sizeof(chr));
-	if (*string == NULL)
-		return REG_ESPACE;
-
-	/* do it */
-	st = findprefix(cnfa, &g->cmap, *string, slength);
-
-	assert(*slength <= cnfa->nstates);
-
-	/* clean up */
-	if (st != REG_PREFIX && st != REG_EXACT)
-	{
-		FREE(*string);
-		*string = NULL;
-		*slength = 0;
-	}
-
-	return st;
-}
-
-/*
- * findprefix - extract common prefix from cNFA
- *
- * Results are returned into the preallocated chr array string[], with
- * *slength (which must be preset to zero) incremented for each chr.
- */
-static int						/* regprefix return code */
-findprefix(struct cnfa * cnfa,
-		   struct colormap * cm,
-		   chr *string,
-		   size_t *slength)
-{
-	int			st;
-	int			nextst;
-	color		thiscolor;
-	chr			c;
-	struct carc *ca;
-
-	/*
-	 * The "pre" state must have only BOS/BOL outarcs, else pattern isn't
-	 * anchored left.  If we have both BOS and BOL, they must go to the same
-	 * next state.
-	 */
-	st = cnfa->pre;
-	nextst = -1;
-	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-	{
-		if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
-		{
-			if (nextst == -1)
-				nextst = ca->to;
-			else if (nextst != ca->to)
-				return REG_NOMATCH;
-		}
-		else
-			return REG_NOMATCH;
-	}
-	if (nextst == -1)
-		return REG_NOMATCH;
-
-	/*
-	 * Scan through successive states, stopping as soon as we find one with
-	 * more than one acceptable transition character (either multiple colors
-	 * on out-arcs, or a color with more than one member chr).
-	 *
-	 * We could find a state with multiple out-arcs that are all labeled with
-	 * the same singleton color; this comes from patterns like "^ab(cde|cxy)".
-	 * In that case we add the chr "c" to the output string but then exit the
-	 * loop with nextst == -1.  This leaves a little bit on the table: if the
-	 * pattern is like "^ab(cde|cdy)", we won't notice that "d" could be added
-	 * to the prefix.  But chasing multiple parallel state chains doesn't seem
-	 * worth the trouble.
-	 */
-	do
-	{
-		st = nextst;
-		nextst = -1;
-		thiscolor = COLORLESS;
-		for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-		{
-			/* We can ignore BOS/BOL arcs */
-			if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
-				continue;
-			/* ... but EOS/EOL arcs terminate the search, as do LACONs */
-			if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1] ||
-				ca->co >= cnfa->ncolors)
-			{
-				thiscolor = COLORLESS;
-				break;
-			}
-			if (thiscolor == COLORLESS)
-			{
-				/* First plain outarc */
-				thiscolor = ca->co;
-				nextst = ca->to;
-			}
-			else if (thiscolor == ca->co)
-			{
-				/* Another plain outarc for same color */
-				nextst = -1;
-			}
-			else
-			{
-				/* More than one plain outarc color terminates the search */
-				thiscolor = COLORLESS;
-				break;
-			}
-		}
-		/* Done if we didn't find exactly one color on plain outarcs */
-		if (thiscolor == COLORLESS)
-			break;
-		/* The color must be a singleton */
-		if (cm->cd[thiscolor].nchrs != 1)
-			break;
-
-		/*
-		 * Identify the color's sole member chr and add it to the prefix
-		 * string.  In general the colormap data structure doesn't provide a
-		 * way to find color member chrs, except by trying GETCOLOR() on each
-		 * possible chr value, which won't do at all.  However, for the cases
-		 * we care about it should be sufficient to test the "firstchr" value,
-		 * that is the first chr ever added to the color.  There are cases
-		 * where this might no longer be a member of the color (so we do need
-		 * to test), but none of them are likely to arise for a character that
-		 * is a member of a common prefix.  If we do hit such a corner case,
-		 * we just fall out without adding anything to the prefix string.
-		 */
-		c = cm->cd[thiscolor].firstchr;
-		if (GETCOLOR(cm, c) != thiscolor)
-			break;
-
-		string[(*slength)++] = c;
-
-		/* Advance to next state, but only if we have a unique next state */
-	} while (nextst != -1);
-
-	/*
-	 * If we ended at a state that only has EOS/EOL outarcs leading to the
-	 * "post" state, then we have an exact-match string.  Note this is true
-	 * even if the string is of zero length.
-	 */
-	nextst = -1;
-	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
-	{
-		if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1])
-		{
-			if (nextst == -1)
-				nextst = ca->to;
-			else if (nextst != ca->to)
-			{
-				nextst = -1;
-				break;
-			}
-		}
-		else
-		{
-			nextst = -1;
-			break;
-		}
-	}
-	if (nextst == cnfa->post)
-		return REG_EXACT;
-
-	/*
-	 * Otherwise, if we were unable to identify any prefix characters, say
-	 * NOMATCH --- the pattern is anchored left, but doesn't specify any
-	 * particular first character.
-	 */
-	if (*slength > 0)
-		return REG_PREFIX;
-
-	return REG_NOMATCH;
-}
diff --git a/src/backend/tsearch/spell.c b/src/backend/tsearch/spell.c
index 3af1904..ba476bb 100644
--- a/src/backend/tsearch/spell.c
+++ b/src/backend/tsearch/spell.c
@@ -428,7 +428,8 @@ NIAddAffix(IspellDict *Conf, int flag, char flagflags, const char *mask, const c
 
 		err = pg_regcomp(&(Affix->reg.regex), wmask, wmasklen,
 						 REG_ADVANCED | REG_NOSUB,
-						 DEFAULT_COLLATION_OID);
+						 DEFAULT_COLLATION_OID,
+						 NULL);
 		if (err)
 		{
 			char		errstr[100];
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 6a0fcc2..b9e666f 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -182,7 +182,8 @@ RE_compile_and_cache(text *text_re, int cflags, Oid collation)
 								pattern,
 								pattern_len,
 								cflags,
-								collation);
+								collation,
+								NULL);
 
 	pfree(pattern);
 
diff --git a/src/common/Makefile b/src/common/Makefile
index c47445e..5a03673 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -20,6 +20,8 @@ subdir = src/common
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
+SUBDIRS = regex
+
 override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
@@ -30,6 +32,13 @@ OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
 
 OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
 
+REGEX_OBJS_COMMON = regex/regerror.o regex/regexec.o regex/regfree.o \
+	regex/regprefix.o regex/regexport.o
+
+REGEX_OBJS_FE = regex/regcomp.o $(REGEX_OBJS_COMMON)
+
+REGEX_OBJS_SRV = regex/regcomp_srv.o $(REGEX_OBJS_COMMON)
+
 all: libpgcommon.a libpgcommon_srv.a
 
 # libpgcommon is needed by some contrib
@@ -44,7 +53,7 @@ uninstall:
 
 libpgcommon.a: $(OBJS_FRONTEND)
 	rm -f $@
-	$(AR) $(AROPT) $@ $^
+	$(AR) $(AROPT) $@ $^ $(REGEX_OBJS_FE)
 
 #
 # Server versions of object files
@@ -52,7 +61,10 @@ libpgcommon.a: $(OBJS_FRONTEND)
 
 libpgcommon_srv.a: $(OBJS_SRV)
 	rm -f $@
-	$(AR) $(AROPT) $@ $^
+	$(AR) $(AROPT) $@ $^ $(REGEX_OBJS_SRV)
+
+# These targets are to be generated in subdirectory
+$(REGEX_OBJS_FE) $(REGEX_OBJS_SRV):
 
 # Because this uses its own compilation rule, it doesn't use the
 # dependency tracking logic from Makefile.global.  To make sure that
@@ -72,3 +84,5 @@ submake-errcodes:
 
 clean distclean maintainer-clean:
 	rm -f libpgcommon.a libpgcommon_srv.a $(OBJS_FRONTEND) $(OBJS_SRV)
+
+$(call recurse)
diff --git a/src/common/regex/COPYRIGHT b/src/common/regex/COPYRIGHT
new file mode 100644
index 0000000..e50cfb1
--- /dev/null
+++ b/src/common/regex/COPYRIGHT
@@ -0,0 +1,84 @@
+This regular expression package was originally developed by Henry Spencer.
+It bears the following copyright notice:
+
+**********************************************************************
+
+Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+
+Development of this software was funded, in part, by Cray Research Inc.,
+UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+Corporation, none of whom are responsible for the results.  The author
+thanks all of them. 
+
+Redistribution and use in source and binary forms -- with or without
+modification -- are permitted for any purpose, provided that
+redistributions in source form retain this entire copyright notice and
+indicate the origin and nature of any modifications.
+
+I'd appreciate being given credit for this package in the documentation
+of software which uses it, but that is not a requirement.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+**********************************************************************
+
+PostgreSQL adopted the code out of Tcl 8.4.1.  Portions of regc_locale.c
+and re_syntax.n were developed by Tcl developers other than Henry; these
+files bear the Tcl copyright and license notice:
+
+**********************************************************************
+
+This software is copyrighted by the Regents of the University of
+California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+Corporation and other parties.  The following terms apply to all files
+associated with the software unless explicitly disclaimed in
+individual files.
+
+The authors hereby grant permission to use, copy, modify, distribute,
+and license this software and its documentation for any purpose, provided
+that existing copyright notices are retained in all copies and that this
+notice is included verbatim in any distributions. No written agreement,
+license, or royalty fee is required for any of the authorized uses.
+Modifications to this software may be copyrighted by their authors
+and need not follow the licensing terms described here, provided that
+the new terms are clearly indicated on the first page of each file where
+they apply.
+
+IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+MODIFICATIONS.
+
+GOVERNMENT USE: If you are acquiring this software on behalf of the
+U.S. government, the Government shall have only "Restricted Rights"
+in the software and related documentation as defined in the Federal 
+Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+are acquiring the software on behalf of the Department of Defense, the
+software shall be classified as "Commercial Computer Software" and the
+Government shall have only "Restricted Rights" as defined in Clause
+252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+authors grant the U.S. Government and others acting in its behalf
+permission to use and distribute the software in accordance with the
+terms specified in this license. 
+
+**********************************************************************
+
+Subsequent modifications to the code by the PostgreSQL project follow
+the same license terms as the rest of PostgreSQL.
diff --git a/src/common/regex/Makefile b/src/common/regex/Makefile
new file mode 100644
index 0000000..c462005
--- /dev/null
+++ b/src/common/regex/Makefile
@@ -0,0 +1,44 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/regex
+#
+# IDENTIFICATION
+#    src/backend/regex/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/common/regex
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS_COMMON = regerror.o regexec.o regfree.o regprefix.o regexport.o
+
+OBJS_FRONTEND = regcomp.o $(OBJS_COMMON)
+
+OBJS_SRV = regcomp_srv.o $(OBJS_COMMON)
+
+include $(top_srcdir)/src/backend/common.mk
+
+all: $(OBJS_FRONTEND) $(OBJS_SRV)
+
+# regcomp.c and regexec.c include other c files
+REGCOMP_SRC = regcomp.c regc_lex.c regc_color.c regc_nfa.c regc_cvec.c \
+        regc_locale.c regc_pg_locale.c
+
+REGEXEC_SRC = regexec.c rege_dfa.c
+
+# mark inclusion dependencies between .c files explicitly
+regcomp_srv.o regcomp.o: $(REGCOMP_SRC)
+
+regexec_srv.o regexec.o: $(REGEXEC_SRC)
+
+# Override rules of Makefile.global
+%_srv.o: %.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+%.o: %.c
+	$(CC) $(CFLAGS) $(CPPFLAGS) -DFRONTEND -c $< -o $@
+
+clean distclean maintainer-clean:
+	rm -f $(OBJS_FRONTEND) $(OBJS_SRV)
diff --git a/src/common/regex/README b/src/common/regex/README
new file mode 100644
index 0000000..6c9f483
--- /dev/null
+++ b/src/common/regex/README
@@ -0,0 +1,371 @@
+Implementation notes about Henry Spencer's regex library
+========================================================
+
+If Henry ever had any internals documentation, he didn't publish it.
+So this file is an attempt to reverse-engineer some docs.
+
+General source-file layout
+--------------------------
+
+There are six separately-compilable source files, five of which expose
+exactly one exported function apiece:
+	regcomp.c: pg_regcomp
+	regexec.c: pg_regexec
+	regerror.c: pg_regerror
+	regfree.c: pg_regfree
+	regprefix.c: pg_regprefix
+(The pg_ prefixes were added by the Postgres project to distinguish this
+library version from any similar one that might be present on a particular
+system.  They'd need to be removed or replaced in any standalone version
+of the library.)
+
+The sixth file, regexport.c, exposes multiple functions that allow extraction
+of info about a compiled regex (see regexport.h).
+
+There are additional source files regc_*.c that are #include'd in regcomp,
+and similarly additional source files rege_*.c that are #include'd in
+regexec.  This was done to avoid exposing internal symbols globally;
+all functions not meant to be part of the library API are static.
+
+(Actually the above is a lie in one respect: there is one more global
+symbol, pg_set_regex_collation in regcomp.  It is not meant to be part of
+the API, but it has to be global because both regcomp and regexec call it.
+It'd be better to get rid of that, as well as the static variables it
+sets, in favor of keeping the needed locale state in the regex structs.
+We have not done this yet for lack of a design for how to add
+application-specific state to the structs.)
+
+What's where in src/backend/regex/:
+
+regcomp.c		Top-level regex compilation code
+regc_color.c		Color map management
+regc_cvec.c		Character vector (cvec) management
+regc_lex.c		Lexer
+regc_nfa.c		NFA handling
+regc_locale.c		Application-specific locale code from Tcl project
+regc_pg_locale.c	Postgres-added application-specific locale code
+regexec.c		Top-level regex execution code
+rege_dfa.c		DFA creation and execution
+regerror.c		pg_regerror: generate text for a regex error code
+regfree.c		pg_regfree: API to free a no-longer-needed regex_t
+regexport.c		Functions for extracting info from a regex_t
+regprefix.c		Code for extracting a common prefix from a regex_t
+
+The locale-specific code is concerned primarily with case-folding and with
+expanding locale-specific character classes, such as [[:alnum:]].  It
+really needs refactoring if this is ever to become a standalone library.
+
+The header files for the library are in src/include/regex/:
+
+regcustom.h		Customizes library for particular application
+regerrs.h		Error message list
+regex.h			Exported API
+regexport.h		Exported API for regexport.c
+regguts.h		Internals declarations
+
+
+DFAs, NFAs, and all that
+------------------------
+
+This library is a hybrid DFA/NFA regex implementation.  (If you've never
+heard either of those terms, get thee to a first-year comp sci textbook.)
+It might not be clear at first glance what that really means and how it
+relates to what you'll see in the code.  Here's what really happens:
+
+* Initial parsing of a regex generates an NFA representation, with number
+of states approximately proportional to the length of the regexp.
+
+* The NFA is then optimized into a "compact NFA" representation, which is
+basically the same idea but without fields that are not going to be needed
+at runtime.  It is simplified too: the compact format only allows "plain"
+and "LACON" arc types.  The cNFA representation is what is passed from
+regcomp to regexec.
+
+* Unlike traditional NFA-based regex engines, we do not execute directly
+from the NFA representation, as that would require backtracking and so be
+very slow in some cases.  Rather, we execute a DFA, which ideally can
+process an input string in linear time (O(M) for M characters of input)
+without backtracking.  Each state of the DFA corresponds to a set of
+states of the NFA, that is all the states that the NFA might have been in
+upon reaching the current point in the input string.  Therefore, an NFA
+with N states might require as many as 2^N states in the corresponding
+DFA, which could easily require unreasonable amounts of memory.  We deal
+with this by materializing states of the DFA lazily (only when needed) and
+keeping them in a limited-size cache.  The possible need to build the same
+state of the DFA repeatedly makes this approach not truly O(M) time, but
+in the worst case as much as O(M*N).  That's still far better than the
+worst case for a backtracking NFA engine.
+
+If that were the end of it, we'd just say this is a DFA engine, with the
+use of NFAs being merely an implementation detail.  However, a DFA engine
+cannot handle some important regex features such as capturing parens and
+back-references.  If the parser finds that a regex uses these features
+(collectively called "messy cases" in the code), then we have to use
+NFA-style backtracking search after all.
+
+When using the NFA mode, the representation constructed by the parser
+consists of a tree of sub-expressions ("subre"s).  Leaf tree nodes are
+either plain regular expressions (which are executed as DFAs in the manner
+described above) or back-references (which try to match the input to some
+previous substring).  Non-leaf nodes are capture nodes (which save the
+location of the substring currently matching their child node),
+concatenation, alternation, or iteration nodes.  At execution time, the
+executor recursively scans the tree.  At concatenation, alternation, or
+iteration nodes, it considers each possible alternative way of matching the
+input string, that is each place where the string could be split for a
+concatenation or iteration, or each child node for an alternation.  It
+tries the next alternative if the match fails according to the child nodes.
+This is exactly the sort of backtracking search done by a traditional NFA
+regex engine.  If there are many tree levels it can get very slow.
+
+But all is not lost: we can still be smarter than the average pure NFA
+engine.  To do this, each subre node has an associated DFA, which
+represents what the node could possibly match insofar as a mathematically
+pure regex can describe that, which basically means "no backrefs".
+Before we perform any search of possible alternative sub-matches, we run
+the DFA to see if it thinks the proposed substring could possibly match.
+If not, we can reject the match immediately without iterating through many
+possibilities.
+
+As an example, consider the regex "(a[bc]+)\1".  The compiled
+representation will have a top-level concatenation subre node.  Its left
+child is a capture node, and the child of that is a plain DFA node for
+"a[bc]+".  The concatenation's right child is a backref node for \1.
+The DFA associated with the concatenation node will be "a[bc]+a[bc]+",
+where the backref has been replaced by a copy of the DFA for its referent
+expression.  When executed, the concatenation node will have to search for
+a possible division of the input string that allows its two child nodes to
+each match their part of the string (and although this specific case can
+only succeed when the division is at the middle, the code does not know
+that, nor would it be true in general).  However, we can first run the DFA
+and quickly reject any input that doesn't start with an "a" and contain
+one more "a" plus some number of b's and c's.  If the DFA doesn't match,
+there is no need to recurse to the two child nodes for each possible
+string division point.  In many cases, this prefiltering makes the search
+run much faster than a pure NFA engine could do.  It is this behavior that
+justifies using the phrase "hybrid DFA/NFA engine" to describe Spencer's
+library.
+
+
+Colors and colormapping
+-----------------------
+
+In many common regex patterns, there are large numbers of characters that
+can be treated alike by the execution engine.  A simple example is the
+pattern "[[:alpha:]][[:alnum:]]*" for an identifier.  Basically the engine
+only needs to care whether an input symbol is a letter, a digit, or other.
+We could build the NFA or DFA with a separate arc for each possible letter
+and digit, but that's very wasteful of space and not so cheap to execute
+either, especially when dealing with Unicode which can have thousands of
+letters.  Instead, the parser builds a "color map" that maps each possible
+input symbol to a "color", or equivalence class.  The NFA or DFA
+representation then has arcs labeled with colors, not specific input
+symbols.  At execution, the first thing the executor does with each input
+symbol is to look up its color in the color map, and then everything else
+works from the color only.
+
+To build the colormap, we start by assigning every possible input symbol
+the color WHITE, which means "other" (that is, at the end of parsing, the
+symbols that are still WHITE are those not explicitly referenced anywhere
+in the regex).  When we see a simple literal character or a bracket
+expression in the regex, we want to assign that character, or all the
+characters represented by the bracket expression, a unique new color that
+can be used to label the NFA arc corresponding to the state transition for
+matching this character or bracket expression.  The basic idea is:
+first, change the color assigned to a character to some new value;
+second, run through all the existing arcs in the partially-built NFA,
+and for each one referencing the character's old color, add a parallel
+arc referencing its new color (this keeps the reassignment from changing
+the semantics of what we already built); and third, add a new arc with
+the character's new color to the current pair of NFA states, denoting
+that seeing this character allows the state transition to be made.
+
+This is complicated a bit by not wanting to create more colors
+(equivalence classes) than absolutely necessary.  In particular, if a
+bracket expression mentions two characters that had the same color before,
+they should still share the same color after we process the bracket, since
+there is still not a need to distinguish them.  But we do need to
+distinguish them from other characters that previously had the same color
+yet are not listed in the bracket expression.  To mechanize this, the code
+has a concept of "parent colors" and "subcolors", where a color's subcolor
+is the new color that we are giving to any characters of that color while
+parsing the current atom.  (The word "parent" is a bit unfortunate here,
+because it suggests a long-lived relationship, but a subcolor link really
+only lasts for the duration of parsing a single atom.)  In other words,
+a subcolor link means that we are in process of splitting the parent color
+into two colors (equivalence classes), depending on whether or not each
+member character should be included by the current regex atom.
+
+As an example, suppose we have the regex "a\d\wx".  Initially all possible
+character codes are labeled WHITE (color 0).  To parse the atom "a", we
+create a new color (1), update "a"'s color map entry to 1, and create an
+arc labeled 1 between the first two states of the NFA.  Now we see \d,
+which is really a bracket expression containing the digits "0"-"9".
+First we process "0", which is currently WHITE, so we create a new color
+(2), update "0"'s color map entry to 2, and create an arc labeled 2
+between the second and third states of the NFA.  We also mark color WHITE
+as having the subcolor 2, which means that future relabelings of WHITE
+characters should also select 2 as the new color.  Thus, when we process
+"1", we won't create a new color but re-use 2.  We update "1"'s color map
+entry to 2, and then find that we don't need a new arc because there is
+already one labeled 2 between the second and third states of the NFA.
+Similarly for the other 8 digits, so there will be only one arc labeled 2
+between NFA states 2 and 3 for all members of this bracket expression.
+At completion of processing of the bracket expression, we call okcolors()
+which breaks all the existing parent/subcolor links; there is no longer a
+marker saying that WHITE characters should be relabeled 2.  (Note:
+actually, we did the same creation and clearing of a subcolor link for the
+primitive atom "a", but it didn't do anything very interesting.)  Now we
+come to the "\w" bracket expression, which for simplicity assume expands
+to just "[a-z0-9]".  We process "a", but observe that it is already the
+sole member of its color 1.  This means there is no need to subdivide that
+equivalence class more finely, so we do not create any new color.  We just
+make an arc labeled 1 between the third and fourth NFA states.  Next we
+process "b", which is WHITE and far from the only WHITE character, so we
+create a new color (3), link that as WHITE's subcolor, relabel "b" as
+color 3, and make an arc labeled 3.  As we process "c" through "z", each
+is relabeled from WHITE to 3, but no new arc is needed.  Now we come to
+"0", which is not the only member of its color 2, so we suppose that a new
+color is needed and create color 4.  We link 4 as subcolor of 2, relabel
+"0" as color 4 in the map, and add an arc for color 4.  Next "1" through
+"9" are similarly relabeled as color 4, with no additional arcs needed.
+Having finished the bracket expression, we call okcolors(), which breaks
+the subcolor links.  okcolors() further observes that we have removed
+every member of color 2 (the previous color of the digit characters).
+Therefore, it runs through the partial NFA built so far and relabels arcs
+labeled 2 to color 4; in particular the arc from NFA state 2 to state 3 is
+relabeled color 4.  Then it frees up color 2, since we have no more use
+for that color.  We now have an NFA in which transitions for digits are
+consistently labeled with color 4.  Last, we come to the atom "x".
+"x" is currently labeled with color 3, and it's not the only member of
+that color, so we realize that we now need to distinguish "x" from other
+letters when we did not before.  We create a new color, which might have
+been 5 but instead we recycle the unused color 2.  "x" is relabeled 2 in
+the color map and 2 is linked as the subcolor of 3, and we add an arc for
+2 between states 4 and 5 of the NFA.  Now we call okcolors(), which breaks
+the subcolor link between colors 3 and 2 and notices that both colors are
+nonempty.  Therefore, it also runs through the existing NFA arcs and adds
+an additional arc labeled 2 wherever there is an arc labeled 3; this
+action ensures that characters of color 2 (i.e., "x") will still be
+considered as allowing any transitions they did before.  We are now done
+parsing the regex, and we have these final color assignments:
+	color 1: "a"
+	color 2: "x"
+	color 3: other letters
+	color 4: digits
+and the NFA has these arcs:
+	states 1 -> 2 on color 1 (hence, "a" only)
+	states 2 -> 3 on color 4 (digits)
+	states 3 -> 4 on colors 1, 3, 4, and 2 (covering all \w characters)
+	states 4 -> 5 on color 2 ("x" only)
+which can be seen to be a correct representation of the regex.
+
+Given this summary, we can see we need the following operations for
+colors:
+
+* A fast way to look up the current color assignment for any character
+  code.  (This is needed during both parsing and execution, while the
+  remaining operations are needed only during parsing.)
+* A way to alter the color assignment for any given character code.
+* We must track the number of characters currently assigned to each
+  color, so that we can detect empty and singleton colors.
+* We must track all existing NFA arcs of a given color, so that we
+  can relabel them at need, or add parallel arcs of a new color when
+  an existing color has to be subdivided.
+
+The last two of these are handled with the "struct colordesc" array and
+the "colorchain" links in NFA arc structs.  The color map proper (that
+is, the per-character lookup array) is handled as a multi-level tree,
+with each tree level indexed by one byte of a character's value.  The
+code arranges to not have more than one copy of bottom-level tree pages
+that are all-the-same-color.
+
+Unfortunately, this design does not seem terribly efficient for common
+cases such as a tree in which all Unicode letters are colored the same,
+because there aren't that many places where we get a whole page all the
+same color, except at the end of the map.  (It also strikes me that given
+PG's current restrictions on the range of Unicode values, we could use a
+3-level rather than 4-level tree; but there's not provision for that in
+regguts.h at the moment.)
+
+A bigger problem is that it just doesn't seem very reasonable to have to
+consider each Unicode letter separately at regex parse time for a regex
+such as "\w"; more than likely, a huge percentage of those codes will
+never be seen at runtime.  We need to fix things so that locale-based
+character classes are somehow processed "symbolically" without making a
+full expansion of their contents at parse time.  This would mean that we'd
+have to be ready to call iswalpha() at runtime, but if that only happens
+for high-code-value characters, it shouldn't be a big performance hit.
+
+
+Detailed semantics of an NFA
+----------------------------
+
+When trying to read dumped-out NFAs, it's helpful to know these facts:
+
+State 0 (additionally marked with "@" in dumpnfa's output) is always the
+goal state, and state 1 (additionally marked with ">") is the start state.
+(The code refers to these as the post state and pre state respectively.)
+
+The possible arc types are:
+
+    PLAIN arcs, which specify matching of any character of a given "color"
+    (see above).  These are dumped as "[color_number]->to_state".
+
+    EMPTY arcs, which specify a no-op transition to another state.  These
+    are dumped as "->to_state".
+
+    AHEAD constraints, which represent a "next character must be of this
+    color" constraint.  AHEAD differs from a PLAIN arc in that the input
+    character is not consumed when crossing the arc.  These are dumped as
+    ">color_number>->to_state".
+
+    BEHIND constraints, which represent a "previous character must be of
+    this color" constraint, which likewise consumes no input.  These are
+    dumped as "<color_number<->to_state".
+
+    '^' arcs, which specify a beginning-of-input constraint.  These are
+    dumped as "^0->to_state" or "^1->to_state" for beginning-of-string and
+    beginning-of-line constraints respectively.
+
+    '$' arcs, which specify an end-of-input constraint.  These are dumped
+    as "$0->to_state" or "$1->to_state" for end-of-string and end-of-line
+    constraints respectively.
+
+    LACON constraints, which represent "(?=re)", "(?!re)", "(?<=re)", and
+    "(?<!re)" constraints, i.e. the input starting/ending at this point must
+    match (or not match) a given sub-RE, but the matching input is not
+    consumed.  These are dumped as ":subtree_number:->to_state".
+
+If you see anything else (especially any question marks) in the display of
+an arc, it's dumpnfa() trying to tell you that there's something fishy
+about the arc; see the source code.
+
+The regex executor can only handle PLAIN and LACON transitions.  The regex
+optimize() function is responsible for transforming the parser's output
+to get rid of all the other arc types.  In particular, ^ and $ arcs that
+are not dropped as impossible will always end up adjacent to the pre or
+post state respectively, and then will be converted into PLAIN arcs that
+mention the special "colors" for BOS, BOL, EOS, or EOL.
+
+To decide whether a thus-transformed NFA matches a given substring of the
+input string, the executor essentially follows these rules:
+1. Start the NFA "looking at" the character *before* the given substring,
+or if the substring is at the start of the input, prepend an imaginary BOS
+character instead.
+2. Run the NFA until it has consumed the character *after* the given
+substring, or an imaginary following EOS character if the substring is at
+the end of the input.
+3. If the NFA is (or can be) in the goal state at this point, it matches.
+
+So one can mentally execute an untransformed NFA by taking ^ and $ as
+ordinary constraints that match at start and end of input; but plain
+arcs out of the start state should be taken as matches for the character
+before the target substring, and similarly, plain arcs leading to the
+post state are matches for the character after the target substring.
+This definition is necessary to support regexes that begin or end with
+constraints such as \m and \M, which imply requirements on the adjacent
+character if any.  NFAs for simple unanchored patterns will usually have
+pre-state outarcs for all possible character colors as well as BOS and
+BOL, and post-state inarcs for all possible character colors as well as
+EOS and EOL, so that the executor's behavior will work.
diff --git a/src/common/regex/re_syntax.n b/src/common/regex/re_syntax.n
new file mode 100644
index 0000000..4621bfc
--- /dev/null
+++ b/src/common/regex/re_syntax.n
@@ -0,0 +1,979 @@
+'\"
+'\" Copyright (c) 1998 Sun Microsystems, Inc.
+'\" Copyright (c) 1999 Scriptics Corporation
+'\"
+'\" This software is copyrighted by the Regents of the University of
+'\" California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+'\" Corporation and other parties.  The following terms apply to all files
+'\" associated with the software unless explicitly disclaimed in
+'\" individual files.
+'\" 
+'\" The authors hereby grant permission to use, copy, modify, distribute,
+'\" and license this software and its documentation for any purpose, provided
+'\" that existing copyright notices are retained in all copies and that this
+'\" notice is included verbatim in any distributions. No written agreement,
+'\" license, or royalty fee is required for any of the authorized uses.
+'\" Modifications to this software may be copyrighted by their authors
+'\" and need not follow the licensing terms described here, provided that
+'\" the new terms are clearly indicated on the first page of each file where
+'\" they apply.
+'\" 
+'\" IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+'\" FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+'\" ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+'\" DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+'\" POSSIBILITY OF SUCH DAMAGE.
+'\" 
+'\" THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+'\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+'\" FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+'\" IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+'\" NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+'\" MODIFICATIONS.
+'\" 
+'\" GOVERNMENT USE: If you are acquiring this software on behalf of the
+'\" U.S. government, the Government shall have only "Restricted Rights"
+'\" in the software and related documentation as defined in the Federal 
+'\" Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+'\" are acquiring the software on behalf of the Department of Defense, the
+'\" software shall be classified as "Commercial Computer Software" and the
+'\" Government shall have only "Restricted Rights" as defined in Clause
+'\" 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+'\" authors grant the U.S. Government and others acting in its behalf
+'\" permission to use and distribute the software in accordance with the
+'\" terms specified in this license. 
+'\" 
+'\" RCS: @(#) Id: re_syntax.n,v 1.3 1999/07/14 19:09:36 jpeek Exp 
+'\"
+.so man.macros
+.TH re_syntax n "8.1" Tcl "Tcl Built-In Commands"
+.BS
+.SH NAME
+re_syntax \- Syntax of Tcl regular expressions.
+.BE
+
+.SH DESCRIPTION
+.PP
+A \fIregular expression\fR describes strings of characters.
+It's a pattern that matches certain strings and doesn't match others.
+
+.SH "DIFFERENT FLAVORS OF REs"
+Regular expressions (``RE''s), as defined by POSIX, come in two
+flavors: \fIextended\fR REs (``EREs'') and \fIbasic\fR REs (``BREs'').
+EREs are roughly those of the traditional \fIegrep\fR, while BREs are
+roughly those of the traditional \fIed\fR.  This implementation adds
+a third flavor, \fIadvanced\fR REs (``AREs''), basically EREs with
+some significant extensions.
+.PP
+This manual page primarily describes AREs.  BREs mostly exist for
+backward compatibility in some old programs; they will be discussed at
+the end.  POSIX EREs are almost an exact subset of AREs.  Features of
+AREs that are not present in EREs will be indicated.
+
+.SH "REGULAR EXPRESSION SYNTAX"
+.PP
+Tcl regular expressions are implemented using the package written by
+Henry Spencer, based on the 1003.2 spec and some (not quite all) of
+the Perl5 extensions (thanks, Henry!).  Much of the description of
+regular expressions below is copied verbatim from his manual entry.
+.PP
+An ARE is one or more \fIbranches\fR,
+separated by `\fB|\fR',
+matching anything that matches any of the branches.
+.PP
+A branch is zero or more \fIconstraints\fR or \fIquantified atoms\fR,
+concatenated.
+It matches a match for the first, followed by a match for the second, etc;
+an empty branch matches the empty string.
+.PP
+A quantified atom is an \fIatom\fR possibly followed
+by a single \fIquantifier\fR.
+Without a quantifier, it matches a match for the atom.
+The quantifiers,
+and what a so-quantified atom matches, are:
+.RS 2
+.TP 6
+\fB*\fR
+a sequence of 0 or more matches of the atom
+.TP
+\fB+\fR
+a sequence of 1 or more matches of the atom
+.TP
+\fB?\fR
+a sequence of 0 or 1 matches of the atom
+.TP
+\fB{\fIm\fB}\fR
+a sequence of exactly \fIm\fR matches of the atom
+.TP
+\fB{\fIm\fB,}\fR
+a sequence of \fIm\fR or more matches of the atom
+.TP
+\fB{\fIm\fB,\fIn\fB}\fR
+a sequence of \fIm\fR through \fIn\fR (inclusive) matches of the atom;
+\fIm\fR may not exceed \fIn\fR
+.TP
+\fB*?  +?  ??  {\fIm\fB}?  {\fIm\fB,}?  {\fIm\fB,\fIn\fB}?\fR
+\fInon-greedy\fR quantifiers,
+which match the same possibilities,
+but prefer the smallest number rather than the largest number
+of matches (see MATCHING)
+.RE
+.PP
+The forms using
+\fB{\fR and \fB}\fR
+are known as \fIbound\fRs.
+The numbers
+\fIm\fR and \fIn\fR are unsigned decimal integers
+with permissible values from 0 to 255 inclusive.
+.PP
+An atom is one of:
+.RS 2
+.TP 6
+\fB(\fIre\fB)\fR
+(where \fIre\fR is any regular expression)
+matches a match for
+\fIre\fR, with the match noted for possible reporting
+.TP
+\fB(?:\fIre\fB)\fR
+as previous,
+but does no reporting
+(a ``non-capturing'' set of parentheses)
+.TP
+\fB()\fR
+matches an empty string,
+noted for possible reporting
+.TP
+\fB(?:)\fR
+matches an empty string,
+without reporting
+.TP
+\fB[\fIchars\fB]\fR
+a \fIbracket expression\fR,
+matching any one of the \fIchars\fR (see BRACKET EXPRESSIONS for more detail)
+.TP
+ \fB.\fR
+matches any single character
+.TP
+\fB\e\fIk\fR
+(where \fIk\fR is a non-alphanumeric character)
+matches that character taken as an ordinary character,
+e.g. \e\e matches a backslash character
+.TP
+\fB\e\fIc\fR
+where \fIc\fR is alphanumeric
+(possibly followed by other characters),
+an \fIescape\fR (AREs only),
+see ESCAPES below
+.TP
+\fB{\fR
+when followed by a character other than a digit,
+matches the left-brace character `\fB{\fR';
+when followed by a digit, it is the beginning of a
+\fIbound\fR (see above)
+.TP
+\fIx\fR
+where \fIx\fR is
+a single character with no other significance, matches that character.
+.RE
+.PP
+A \fIconstraint\fR matches an empty string when specific conditions
+are met.
+A constraint may not be followed by a quantifier.
+The simple constraints are as follows; some more constraints are
+described later, under ESCAPES.
+.RS 2
+.TP 8
+\fB^\fR
+matches at the beginning of a line
+.TP
+\fB$\fR
+matches at the end of a line
+.TP
+\fB(?=\fIre\fB)\fR
+\fIpositive lookahead\fR (AREs only), matches at any point
+where a substring matching \fIre\fR begins
+.TP
+\fB(?!\fIre\fB)\fR
+\fInegative lookahead\fR (AREs only), matches at any point
+where no substring matching \fIre\fR begins
+.TP
+\fB(?<=\fIre\fB)\fR
+\fIpositive lookbehind\fR (AREs only), matches at any point
+where a substring matching \fIre\fR ends
+.TP
+\fB(?<!\fIre\fB)\fR
+\fInegative lookbehind\fR (AREs only), matches at any point
+where no substring matching \fIre\fR ends
+.RE
+.PP
+Lookahead and lookbehind constraints may not contain back references
+(see later), and all parentheses within them are considered non-capturing.
+.PP
+An RE may not end with `\fB\e\fR'.
+
+.SH "BRACKET EXPRESSIONS"
+A \fIbracket expression\fR is a list of characters enclosed in `\fB[\|]\fR'.
+It normally matches any single character from the list (but see below).
+If the list begins with `\fB^\fR',
+it matches any single character
+(but see below) \fInot\fR from the rest of the list.
+.PP
+If two characters in the list are separated by `\fB\-\fR',
+this is shorthand
+for the full \fIrange\fR of characters between those two (inclusive) in the
+collating sequence,
+e.g.
+\fB[0\-9]\fR
+in ASCII matches any decimal digit.
+Two ranges may not share an
+endpoint, so e.g.
+\fBa\-c\-e\fR
+is illegal.
+Ranges are very collating-sequence-dependent,
+and portable programs should avoid relying on them.
+.PP
+To include a literal
+\fB]\fR
+or
+\fB\-\fR
+in the list,
+the simplest method is to
+enclose it in
+\fB[.\fR and \fB.]\fR
+to make it a collating element (see below).
+Alternatively,
+make it the first character
+(following a possible `\fB^\fR'),
+or (AREs only) precede it with `\fB\e\fR'.
+Alternatively, for `\fB\-\fR',
+make it the last character,
+or the second endpoint of a range.
+To use a literal
+\fB\-\fR
+as the first endpoint of a range,
+make it a collating element
+or (AREs only) precede it with `\fB\e\fR'.
+With the exception of these, some combinations using
+\fB[\fR
+(see next
+paragraphs), and escapes,
+all other special characters lose their
+special significance within a bracket expression.
+.PP
+Within a bracket expression, a collating element (a character,
+a multi-character sequence that collates as if it were a single character,
+or a collating-sequence name for either)
+enclosed in
+\fB[.\fR and \fB.]\fR
+stands for the
+sequence of characters of that collating element.
+The sequence is a single element of the bracket expression's list.
+A bracket expression in a locale that has
+multi-character collating elements
+can thus match more than one character.
+.VS 8.2
+So (insidiously), a bracket expression that starts with \fB^\fR
+can match multi-character collating elements even if none of them
+appear in the bracket expression!
+(\fINote:\fR Tcl currently has no multi-character collating elements.
+This information is only for illustration.)
+.PP
+For example, assume the collating sequence includes a \fBch\fR
+multi-character collating element.
+Then the RE \fB[[.ch.]]*c\fR (zero or more \fBch\fP's followed by \fBc\fP)
+matches the first five characters of `\fBchchcc\fR'.
+Also, the RE \fB[^c]b\fR matches all of `\fBchb\fR'
+(because \fB[^c]\fR matches the multi-character \fBch\fR).
+.VE 8.2
+.PP
+Within a bracket expression, a collating element enclosed in
+\fB[=\fR
+and
+\fB=]\fR
+is an equivalence class, standing for the sequences of characters
+of all collating elements equivalent to that one, including itself.
+(If there are no other equivalent collating elements,
+the treatment is as if the enclosing delimiters were `\fB[.\fR'\&
+and `\fB.]\fR'.)
+For example, if
+\fBo\fR
+and
+\fB\o'o^'\fR
+are the members of an equivalence class,
+then `\fB[[=o=]]\fR', `\fB[[=\o'o^'=]]\fR',
+and `\fB[o\o'o^']\fR'\&
+are all synonymous.
+An equivalence class may not be an endpoint
+of a range.
+.VS 8.2
+(\fINote:\fR 
+Tcl currently implements only the Unicode locale.
+It doesn't define any equivalence classes.
+The examples above are just illustrations.)
+.VE 8.2
+.PP
+Within a bracket expression, the name of a \fIcharacter class\fR enclosed
+in
+\fB[:\fR
+and
+\fB:]\fR
+stands for the list of all characters
+(not all collating elements!)
+belonging to that
+class.
+Standard character classes are:
+.PP
+.RS
+.ne 5
+.nf
+.ta 3c
+\fBalpha\fR	A letter. 
+\fBupper\fR	An upper-case letter. 
+\fBlower\fR	A lower-case letter. 
+\fBdigit\fR	A decimal digit. 
+\fBxdigit\fR	A hexadecimal digit. 
+\fBalnum\fR	An alphanumeric (letter or digit). 
+\fBprint\fR	An alphanumeric (same as alnum).
+\fBblank\fR	A space or tab character.
+\fBspace\fR	A character producing white space in displayed text. 
+\fBpunct\fR	A punctuation character. 
+\fBgraph\fR	A character with a visible representation. 
+\fBcntrl\fR	A control character. 
+.fi
+.RE
+.PP
+A locale may provide others.
+.VS 8.2
+(Note that the current Tcl implementation has only one locale:
+the Unicode locale.)
+.VE 8.2
+A character class may not be used as an endpoint of a range.
+.PP
+There are two special cases of bracket expressions:
+the bracket expressions
+\fB[[:<:]]\fR
+and
+\fB[[:>:]]\fR
+are constraints, matching empty strings at
+the beginning and end of a word respectively.
+'\" note, discussion of escapes below references this definition of word
+A word is defined as a sequence of
+word characters
+that is neither preceded nor followed by
+word characters.
+A word character is an
+\fIalnum\fR
+character
+or an underscore
+(\fB_\fR).
+These special bracket expressions are deprecated;
+users of AREs should use constraint escapes instead (see below).
+.SH ESCAPES
+Escapes (AREs only), which begin with a
+\fB\e\fR
+followed by an alphanumeric character,
+come in several varieties:
+character entry, class shorthands, constraint escapes, and back references.
+A
+\fB\e\fR
+followed by an alphanumeric character but not constituting
+a valid escape is illegal in AREs.
+In EREs, there are no escapes:
+outside a bracket expression,
+a
+\fB\e\fR
+followed by an alphanumeric character merely stands for that
+character as an ordinary character,
+and inside a bracket expression,
+\fB\e\fR
+is an ordinary character.
+(The latter is the one actual incompatibility between EREs and AREs.)
+.PP
+Character-entry escapes (AREs only) exist to make it easier to specify
+non-printing and otherwise inconvenient characters in REs:
+.RS 2
+.TP 5
+\fB\ea\fR
+alert (bell) character, as in C
+.TP
+\fB\eb\fR
+backspace, as in C
+.TP
+\fB\eB\fR
+synonym for
+\fB\e\fR
+to help reduce backslash doubling in some
+applications where there are multiple levels of backslash processing
+.TP
+\fB\ec\fIX\fR
+(where X is any character) the character whose
+low-order 5 bits are the same as those of
+\fIX\fR,
+and whose other bits are all zero
+.TP
+\fB\ee\fR
+the character whose collating-sequence name
+is `\fBESC\fR',
+or failing that, the character with octal value 033
+.TP
+\fB\ef\fR
+formfeed, as in C
+.TP
+\fB\en\fR
+newline, as in C
+.TP
+\fB\er\fR
+carriage return, as in C
+.TP
+\fB\et\fR
+horizontal tab, as in C
+.TP
+\fB\eu\fIwxyz\fR
+(where
+\fIwxyz\fR
+is exactly four hexadecimal digits)
+the Unicode character
+\fBU+\fIwxyz\fR
+in the local byte ordering
+.TP
+\fB\eU\fIstuvwxyz\fR
+(where
+\fIstuvwxyz\fR
+is exactly eight hexadecimal digits)
+reserved for a somewhat-hypothetical Unicode extension to 32 bits
+.TP
+\fB\ev\fR
+vertical tab, as in C
+are all available.
+.TP
+\fB\ex\fIhhh\fR
+(where
+\fIhhh\fR
+is any sequence of hexadecimal digits)
+the character whose hexadecimal value is
+\fB0x\fIhhh\fR
+(a single character no matter how many hexadecimal digits are used).
+.TP
+\fB\e0\fR
+the character whose value is
+\fB0\fR
+.TP
+\fB\e\fIxy\fR
+(where
+\fIxy\fR
+is exactly two octal digits,
+and is not a
+\fIback reference\fR (see below))
+the character whose octal value is
+\fB0\fIxy\fR
+.TP
+\fB\e\fIxyz\fR
+(where
+\fIxyz\fR
+is exactly three octal digits,
+and is not a
+back reference (see below))
+the character whose octal value is
+\fB0\fIxyz\fR
+.RE
+.PP
+Hexadecimal digits are `\fB0\fR'-`\fB9\fR', `\fBa\fR'-`\fBf\fR',
+and `\fBA\fR'-`\fBF\fR'.
+Octal digits are `\fB0\fR'-`\fB7\fR'.
+.PP
+The character-entry escapes are always taken as ordinary characters.
+For example,
+\fB\e135\fR
+is
+\fB]\fR
+in ASCII,
+but
+\fB\e135\fR
+does not terminate a bracket expression.
+Beware, however, that some applications (e.g., C compilers) interpret 
+such sequences themselves before the regular-expression package
+gets to see them, which may require doubling (quadrupling, etc.) the `\fB\e\fR'.
+.PP
+Class-shorthand escapes (AREs only) provide shorthands for certain commonly-used
+character classes:
+.RS 2
+.TP 10
+\fB\ed\fR
+\fB[[:digit:]]\fR
+.TP
+\fB\es\fR
+\fB[[:space:]]\fR
+.TP
+\fB\ew\fR
+\fB[[:alnum:]_]\fR
+(note underscore)
+.TP
+\fB\eD\fR
+\fB[^[:digit:]]\fR
+.TP
+\fB\eS\fR
+\fB[^[:space:]]\fR
+.TP
+\fB\eW\fR
+\fB[^[:alnum:]_]\fR
+(note underscore)
+.RE
+.PP
+Within bracket expressions, `\fB\ed\fR', `\fB\es\fR',
+and `\fB\ew\fR'\&
+lose their outer brackets,
+and `\fB\eD\fR', `\fB\eS\fR',
+and `\fB\eW\fR'\&
+are illegal.
+.VS 8.2
+(So, for example, \fB[a-c\ed]\fR is equivalent to \fB[a-c[:digit:]]\fR.
+Also, \fB[a-c\eD]\fR, which is equivalent to \fB[a-c^[:digit:]]\fR, is illegal.)
+.VE 8.2
+.PP
+A constraint escape (AREs only) is a constraint,
+matching the empty string if specific conditions are met,
+written as an escape:
+.RS 2
+.TP 6
+\fB\eA\fR
+matches only at the beginning of the string
+(see MATCHING, below, for how this differs from `\fB^\fR')
+.TP
+\fB\em\fR
+matches only at the beginning of a word
+.TP
+\fB\eM\fR
+matches only at the end of a word
+.TP
+\fB\ey\fR
+matches only at the beginning or end of a word
+.TP
+\fB\eY\fR
+matches only at a point that is not the beginning or end of a word
+.TP
+\fB\eZ\fR
+matches only at the end of the string
+(see MATCHING, below, for how this differs from `\fB$\fR')
+.TP
+\fB\e\fIm\fR
+(where
+\fIm\fR
+is a nonzero digit) a \fIback reference\fR, see below
+.TP
+\fB\e\fImnn\fR
+(where
+\fIm\fR
+is a nonzero digit, and
+\fInn\fR
+is some more digits,
+and the decimal value
+\fImnn\fR
+is not greater than the number of closing capturing parentheses seen so far)
+a \fIback reference\fR, see below
+.RE
+.PP
+A word is defined as in the specification of
+\fB[[:<:]]\fR
+and
+\fB[[:>:]]\fR
+above.
+Constraint escapes are illegal within bracket expressions.
+.PP
+A back reference (AREs only) matches the same string matched by the parenthesized
+subexpression specified by the number,
+so that (e.g.)
+\fB([bc])\e1\fR
+matches
+\fBbb\fR
+or
+\fBcc\fR
+but not `\fBbc\fR'.
+The subexpression must entirely precede the back reference in the RE.
+Subexpressions are numbered in the order of their leading parentheses.
+Non-capturing parentheses do not define subexpressions.
+.PP
+There is an inherent historical ambiguity between octal character-entry 
+escapes and back references, which is resolved by heuristics,
+as hinted at above.
+A leading zero always indicates an octal escape.
+A single non-zero digit, not followed by another digit,
+is always taken as a back reference.
+A multi-digit sequence not starting with a zero is taken as a back 
+reference if it comes after a suitable subexpression
+(i.e. the number is in the legal range for a back reference),
+and otherwise is taken as octal.
+.SH "METASYNTAX"
+In addition to the main syntax described above, there are some special
+forms and miscellaneous syntactic facilities available.
+.PP
+Normally the flavor of RE being used is specified by
+application-dependent means.
+However, this can be overridden by a \fIdirector\fR.
+If an RE of any flavor begins with `\fB***:\fR',
+the rest of the RE is an ARE.
+If an RE of any flavor begins with `\fB***=\fR',
+the rest of the RE is taken to be a literal string,
+with all characters considered ordinary characters.
+.PP
+An ARE may begin with \fIembedded options\fR:
+a sequence
+\fB(?\fIxyz\fB)\fR
+(where
+\fIxyz\fR
+is one or more alphabetic characters)
+specifies options affecting the rest of the RE.
+These supplement, and can override,
+any options specified by the application.
+The available option letters are:
+.RS 2
+.TP 3
+\fBb\fR
+rest of RE is a BRE
+.TP 3
+\fBc\fR
+case-sensitive matching (usual default)
+.TP 3
+\fBe\fR
+rest of RE is an ERE
+.TP 3
+\fBi\fR
+case-insensitive matching (see MATCHING, below)
+.TP 3
+\fBm\fR
+historical synonym for
+\fBn\fR
+.TP 3
+\fBn\fR
+newline-sensitive matching (see MATCHING, below)
+.TP 3
+\fBp\fR
+partial newline-sensitive matching (see MATCHING, below)
+.TP 3
+\fBq\fR
+rest of RE is a literal (``quoted'') string, all ordinary characters
+.TP 3
+\fBs\fR
+non-newline-sensitive matching (usual default)
+.TP 3
+\fBt\fR
+tight syntax (usual default; see below)
+.TP 3
+\fBw\fR
+inverse partial newline-sensitive (``weird'') matching (see MATCHING, below)
+.TP 3
+\fBx\fR
+expanded syntax (see below)
+.RE
+.PP
+Embedded options take effect at the
+\fB)\fR
+terminating the sequence.
+They are available only at the start of an ARE,
+and may not be used later within it.
+.PP
+In addition to the usual (\fItight\fR) RE syntax, in which all characters are
+significant, there is an \fIexpanded\fR syntax,
+available in all flavors of RE
+with the \fB-expanded\fR switch, or in AREs with the embedded x option.
+In the expanded syntax,
+white-space characters are ignored
+and all characters between a
+\fB#\fR
+and the following newline (or the end of the RE) are ignored,
+permitting paragraphing and commenting a complex RE.
+There are three exceptions to that basic rule:
+.RS 2
+.PP
+a white-space character or `\fB#\fR' preceded by `\fB\e\fR' is retained
+.PP
+white space or `\fB#\fR' within a bracket expression is retained
+.PP
+white space and comments are illegal within multi-character symbols
+like the ARE `\fB(?:\fR' or the BRE `\fB\e(\fR'
+.RE
+.PP
+Expanded-syntax white-space characters are blank, tab, newline, and
+.VS 8.2
+any character that belongs to the \fIspace\fR character class.
+.VE 8.2
+.PP
+Finally, in an ARE,
+outside bracket expressions, the sequence `\fB(?#\fIttt\fB)\fR'
+(where
+\fIttt\fR
+is any text not containing a `\fB)\fR')
+is a comment,
+completely ignored.
+Again, this is not allowed between the characters of
+multi-character symbols like `\fB(?:\fR'.
+Such comments are more a historical artifact than a useful facility,
+and their use is deprecated;
+use the expanded syntax instead.
+.PP
+\fINone\fR of these metasyntax extensions is available if the application
+(or an initial
+\fB***=\fR
+director)
+has specified that the user's input be treated as a literal string
+rather than as an RE.
+.SH MATCHING
+In the event that an RE could match more than one substring of a given
+string,
+the RE matches the one starting earliest in the string.
+If the RE could match more than one substring starting at that point,
+its choice is determined by its \fIpreference\fR:
+either the longest substring, or the shortest.
+.PP
+Most atoms, and all constraints, have no preference.
+A parenthesized RE has the same preference (possibly none) as the RE.
+A quantified atom with quantifier
+\fB{\fIm\fB}\fR
+or
+\fB{\fIm\fB}?\fR
+has the same preference (possibly none) as the atom itself.
+A quantified atom with other normal quantifiers (including
+\fB{\fIm\fB,\fIn\fB}\fR
+with
+\fIm\fR
+equal to
+\fIn\fR)
+prefers longest match.
+A quantified atom with other non-greedy quantifiers (including
+\fB{\fIm\fB,\fIn\fB}?\fR
+with
+\fIm\fR
+equal to
+\fIn\fR)
+prefers shortest match.
+A branch has the same preference as the first quantified atom in it
+which has a preference.
+An RE consisting of two or more branches connected by the
+\fB|\fR
+operator prefers longest match.
+.PP
+Subject to the constraints imposed by the rules for matching the whole RE,
+subexpressions also match the longest or shortest possible substrings,
+based on their preferences,
+with subexpressions starting earlier in the RE taking priority over
+ones starting later.
+Note that outer subexpressions thus take priority over
+their component subexpressions.
+.PP
+Note that the quantifiers
+\fB{1,1}\fR
+and
+\fB{1,1}?\fR
+can be used to force longest and shortest preference, respectively,
+on a subexpression or a whole RE.
+.PP
+Match lengths are measured in characters, not collating elements.
+An empty string is considered longer than no match at all.
+For example,
+\fBbb*\fR
+matches the three middle characters of `\fBabbbc\fR',
+\fB(week|wee)(night|knights)\fR
+matches all ten characters of `\fBweeknights\fR',
+when
+\fB(.*).*\fR
+is matched against
+\fBabc\fR
+the parenthesized subexpression
+matches all three characters, and
+when
+\fB(a*)*\fR
+is matched against
+\fBbc\fR
+both the whole RE and the parenthesized
+subexpression match an empty string.
+.PP
+If case-independent matching is specified,
+the effect is much as if all case distinctions had vanished from the
+alphabet.
+When an alphabetic that exists in multiple cases appears as an
+ordinary character outside a bracket expression, it is effectively
+transformed into a bracket expression containing both cases,
+so that
+\fBx\fR
+becomes `\fB[xX]\fR'.
+When it appears inside a bracket expression, all case counterparts
+of it are added to the bracket expression, so that
+\fB[x]\fR
+becomes
+\fB[xX]\fR
+and
+\fB[^x]\fR
+becomes `\fB[^xX]\fR'.
+.PP
+If newline-sensitive matching is specified, \fB.\fR
+and bracket expressions using
+\fB^\fR
+will never match the newline character
+(so that matches will never cross newlines unless the RE
+explicitly arranges it)
+and
+\fB^\fR
+and
+\fB$\fR
+will match the empty string after and before a newline
+respectively, in addition to matching at beginning and end of string
+respectively.
+ARE
+\fB\eA\fR
+and
+\fB\eZ\fR
+continue to match beginning or end of string \fIonly\fR.
+.PP
+If partial newline-sensitive matching is specified,
+this affects \fB.\fR
+and bracket expressions
+as with newline-sensitive matching, but not
+\fB^\fR
+and `\fB$\fR'.
+.PP
+If inverse partial newline-sensitive matching is specified,
+this affects
+\fB^\fR
+and
+\fB$\fR
+as with
+newline-sensitive matching,
+but not \fB.\fR
+and bracket expressions.
+This isn't very useful but is provided for symmetry.
+.SH "LIMITS AND COMPATIBILITY"
+No particular limit is imposed on the length of REs.
+Programs intended to be highly portable should not employ REs longer
+than 256 bytes,
+as a POSIX-compliant implementation can refuse to accept such REs.
+.PP
+The only feature of AREs that is actually incompatible with
+POSIX EREs is that
+\fB\e\fR
+does not lose its special
+significance inside bracket expressions.
+All other ARE features use syntax which is illegal or has
+undefined or unspecified effects in POSIX EREs;
+the
+\fB***\fR
+syntax of directors likewise is outside the POSIX
+syntax for both BREs and EREs.
+.PP
+Many of the ARE extensions are borrowed from Perl, but some have
+been changed to clean them up, and a few Perl extensions are not present.
+Incompatibilities of note include `\fB\eb\fR', `\fB\eB\fR',
+the lack of special treatment for a trailing newline,
+the addition of complemented bracket expressions to the things
+affected by newline-sensitive matching,
+the restrictions on parentheses and back references in lookahead/lookbehind
+constraints,
+and the longest/shortest-match (rather than first-match) matching semantics.
+.PP
+The matching rules for REs containing both normal and non-greedy quantifiers
+have changed since early beta-test versions of this package.
+(The new rules are much simpler and cleaner,
+but don't work as hard at guessing the user's real intentions.)
+.PP
+Henry Spencer's original 1986 \fIregexp\fR package,
+still in widespread use (e.g., in pre-8.1 releases of Tcl),
+implemented an early version of today's EREs.
+There are four incompatibilities between \fIregexp\fR's near-EREs
+(`RREs' for short) and AREs.
+In roughly increasing order of significance:
+.PP
+.RS
+In AREs,
+\fB\e\fR
+followed by an alphanumeric character is either an
+escape or an error,
+while in RREs, it was just another way of writing the 
+alphanumeric.
+This should not be a problem because there was no reason to write
+such a sequence in RREs.
+.PP
+\fB{\fR
+followed by a digit in an ARE is the beginning of a bound,
+while in RREs,
+\fB{\fR
+was always an ordinary character.
+Such sequences should be rare,
+and will often result in an error because following characters
+will not look like a valid bound.
+.PP
+In AREs,
+\fB\e\fR
+remains a special character within `\fB[\|]\fR',
+so a literal
+\fB\e\fR
+within
+\fB[\|]\fR
+must be written `\fB\e\e\fR'.
+\fB\e\e\fR
+also gives a literal
+\fB\e\fR
+within
+\fB[\|]\fR
+in RREs,
+but only truly paranoid programmers routinely doubled the backslash.
+.PP
+AREs report the longest/shortest match for the RE,
+rather than the first found in a specified search order.
+This may affect some RREs which were written in the expectation that
+the first match would be reported.
+(The careful crafting of RREs to optimize the search order for fast
+matching is obsolete (AREs examine all possible matches
+in parallel, and their performance is largely insensitive to their
+complexity) but cases where the search order was exploited to deliberately 
+find a match which was \fInot\fR the longest/shortest will need rewriting.)
+.RE
+
+.SH "BASIC REGULAR EXPRESSIONS"
+BREs differ from EREs in several respects.  `\fB|\fR', `\fB+\fR',
+and
+\fB?\fR
+are ordinary characters and there is no equivalent
+for their functionality.
+The delimiters for bounds are
+\fB\e{\fR
+and `\fB\e}\fR',
+with
+\fB{\fR
+and
+\fB}\fR
+by themselves ordinary characters.
+The parentheses for nested subexpressions are
+\fB\e(\fR
+and `\fB\e)\fR',
+with
+\fB(\fR
+and
+\fB)\fR
+by themselves ordinary characters.
+\fB^\fR
+is an ordinary character except at the beginning of the
+RE or the beginning of a parenthesized subexpression,
+\fB$\fR
+is an ordinary character except at the end of the
+RE or the end of a parenthesized subexpression,
+and
+\fB*\fR
+is an ordinary character if it appears at the beginning of the
+RE or the beginning of a parenthesized subexpression
+(after a possible leading `\fB^\fR').
+Finally,
+single-digit back references are available,
+and
+\fB\e<\fR
+and
+\fB\e>\fR
+are synonyms for
+\fB[[:<:]]\fR
+and
+\fB[[:>:]]\fR
+respectively;
+no other escapes are available.
+
+.SH "SEE ALSO"
+RegExp(3), regexp(n), regsub(n), lsearch(n), switch(n), text(n)
+
+.SH KEYWORDS
+match, regular expression, string
diff --git a/src/common/regex/regc_color.c b/src/common/regex/regc_color.c
new file mode 100644
index 0000000..c495cee
--- /dev/null
+++ b/src/common/regex/regc_color.c
@@ -0,0 +1,790 @@
+/*
+ * colorings of characters
+ * This file is #included by regcomp.c.
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regc_color.c
+ *
+ *
+ * Note that there are some incestuous relationships between this code and
+ * NFA arc maintenance, which perhaps ought to be cleaned up sometime.
+ */
+
+
+
+#define CISERR()	VISERR(cm->v)
+#define CERR(e)		VERR(cm->v, (e))
+
+
+
+/*
+ * initcm - set up new colormap
+ */
+static void
+initcm(struct vars * v,
+	   struct colormap * cm)
+{
+	int			i;
+	int			j;
+	union tree *t;
+	union tree *nextt;
+	struct colordesc *cd;
+
+	cm->magic = CMMAGIC;
+	cm->v = v;
+
+	cm->ncds = NINLINECDS;
+	cm->cd = cm->cdspace;
+	cm->max = 0;
+	cm->free = 0;
+
+	cd = cm->cd;				/* cm->cd[WHITE] */
+	cd->sub = NOSUB;
+	cd->arcs = NULL;
+	cd->firstchr = CHR_MIN;
+	cd->nchrs = CHR_MAX - CHR_MIN + 1;
+	cd->flags = 0;
+
+	/* upper levels of tree */
+	for (t = &cm->tree[0], j = NBYTS - 1; j > 0; t = nextt, j--)
+	{
+		nextt = t + 1;
+		for (i = BYTTAB - 1; i >= 0; i--)
+			t->tptr[i] = nextt;
+	}
+	/* bottom level is solid white */
+	t = &cm->tree[NBYTS - 1];
+	for (i = BYTTAB - 1; i >= 0; i--)
+		t->tcolor[i] = WHITE;
+	cd->block = t;
+}
+
+/*
+ * freecm - free dynamically-allocated things in a colormap
+ */
+static void
+freecm(struct colormap * cm)
+{
+	size_t		i;
+	union tree *cb;
+
+	cm->magic = 0;
+	if (NBYTS > 1)
+		cmtreefree(cm, cm->tree, 0);
+	for (i = 1; i <= cm->max; i++)		/* skip WHITE */
+		if (!UNUSEDCOLOR(&cm->cd[i]))
+		{
+			cb = cm->cd[i].block;
+			if (cb != NULL)
+				FREE(cb);
+		}
+	if (cm->cd != cm->cdspace)
+		FREE(cm->cd);
+}
+
+/*
+ * cmtreefree - free a non-terminal part of a colormap tree
+ */
+static void
+cmtreefree(struct colormap * cm,
+		   union tree * tree,
+		   int level)			/* level number (top == 0) of this block */
+{
+	int			i;
+	union tree *t;
+	union tree *fillt = &cm->tree[level + 1];
+	union tree *cb;
+
+	assert(level < NBYTS - 1);	/* this level has pointers */
+	for (i = BYTTAB - 1; i >= 0; i--)
+	{
+		t = tree->tptr[i];
+		assert(t != NULL);
+		if (t != fillt)
+		{
+			if (level < NBYTS - 2)
+			{					/* more pointer blocks below */
+				cmtreefree(cm, t, level + 1);
+				FREE(t);
+			}
+			else
+			{					/* color block below */
+				cb = cm->cd[t->tcolor[0]].block;
+				if (t != cb)	/* not a solid block */
+					FREE(t);
+			}
+		}
+	}
+}
+
+/*
+ * setcolor - set the color of a character in a colormap
+ */
+static color					/* previous color */
+setcolor(struct colormap * cm,
+		 chr c,
+		 pcolor co)
+{
+	uchr		uc = c;
+	int			shift;
+	int			level;
+	int			b;
+	int			bottom;
+	union tree *t;
+	union tree *newt;
+	union tree *fillt;
+	union tree *lastt;
+	union tree *cb;
+	color		prev;
+
+	assert(cm->magic == CMMAGIC);
+	if (CISERR() || co == COLORLESS)
+		return COLORLESS;
+
+	t = cm->tree;
+	for (level = 0, shift = BYTBITS * (NBYTS - 1); shift > 0;
+		 level++, shift -= BYTBITS)
+	{
+		b = (uc >> shift) & BYTMASK;
+		lastt = t;
+		t = lastt->tptr[b];
+		assert(t != NULL);
+		fillt = &cm->tree[level + 1];
+		bottom = (shift <= BYTBITS) ? 1 : 0;
+		cb = (bottom) ? cm->cd[t->tcolor[0]].block : fillt;
+		if (t == fillt || t == cb)
+		{						/* must allocate a new block */
+			newt = (union tree *) MALLOC((bottom) ?
+								sizeof(struct colors) : sizeof(struct ptrs));
+			if (newt == NULL)
+			{
+				CERR(REG_ESPACE);
+				return COLORLESS;
+			}
+			if (bottom)
+				memcpy(VS(newt->tcolor), VS(t->tcolor),
+					   BYTTAB * sizeof(color));
+			else
+				memcpy(VS(newt->tptr), VS(t->tptr),
+					   BYTTAB * sizeof(union tree *));
+			t = newt;
+			lastt->tptr[b] = t;
+		}
+	}
+
+	b = uc & BYTMASK;
+	prev = t->tcolor[b];
+	t->tcolor[b] = (color) co;
+	return prev;
+}
+
+/*
+ * maxcolor - report largest color number in use
+ */
+static color
+maxcolor(struct colormap * cm)
+{
+	if (CISERR())
+		return COLORLESS;
+
+	return (color) cm->max;
+}
+
+/*
+ * newcolor - find a new color (must be subject of setcolor at once)
+ * Beware:	may relocate the colordescs.
+ */
+static color					/* COLORLESS for error */
+newcolor(struct colormap * cm)
+{
+	struct colordesc *cd;
+	size_t		n;
+
+	if (CISERR())
+		return COLORLESS;
+
+	if (cm->free != 0)
+	{
+		assert(cm->free > 0);
+		assert((size_t) cm->free < cm->ncds);
+		cd = &cm->cd[cm->free];
+		assert(UNUSEDCOLOR(cd));
+		assert(cd->arcs == NULL);
+		cm->free = cd->sub;
+	}
+	else if (cm->max < cm->ncds - 1)
+	{
+		cm->max++;
+		cd = &cm->cd[cm->max];
+	}
+	else
+	{
+		/* oops, must allocate more */
+		struct colordesc *newCd;
+
+		if (cm->max == MAX_COLOR)
+		{
+			CERR(REG_ECOLORS);
+			return COLORLESS;	/* too many colors */
+		}
+
+		n = cm->ncds * 2;
+		if (n > MAX_COLOR + 1)
+			n = MAX_COLOR + 1;
+		if (cm->cd == cm->cdspace)
+		{
+			newCd = (struct colordesc *) MALLOC(n * sizeof(struct colordesc));
+			if (newCd != NULL)
+				memcpy(VS(newCd), VS(cm->cdspace), cm->ncds *
+					   sizeof(struct colordesc));
+		}
+		else
+			newCd = (struct colordesc *)
+				REALLOC(cm->cd, n * sizeof(struct colordesc));
+		if (newCd == NULL)
+		{
+			CERR(REG_ESPACE);
+			return COLORLESS;
+		}
+		cm->cd = newCd;
+		cm->ncds = n;
+		assert(cm->max < cm->ncds - 1);
+		cm->max++;
+		cd = &cm->cd[cm->max];
+	}
+
+	cd->nchrs = 0;
+	cd->sub = NOSUB;
+	cd->arcs = NULL;
+	cd->firstchr = CHR_MIN;		/* in case never set otherwise */
+	cd->flags = 0;
+	cd->block = NULL;
+
+	return (color) (cd - cm->cd);
+}
+
+/*
+ * freecolor - free a color (must have no arcs or subcolor)
+ */
+static void
+freecolor(struct colormap * cm,
+		  pcolor co)
+{
+	struct colordesc *cd = &cm->cd[co];
+	color		pco,
+				nco;			/* for freelist scan */
+
+	assert(co >= 0);
+	if (co == WHITE)
+		return;
+
+	assert(cd->arcs == NULL);
+	assert(cd->sub == NOSUB);
+	assert(cd->nchrs == 0);
+	cd->flags = FREECOL;
+	if (cd->block != NULL)
+	{
+		FREE(cd->block);
+		cd->block = NULL;		/* just paranoia */
+	}
+
+	if ((size_t) co == cm->max)
+	{
+		while (cm->max > WHITE && UNUSEDCOLOR(&cm->cd[cm->max]))
+			cm->max--;
+		assert(cm->free >= 0);
+		while ((size_t) cm->free > cm->max)
+			cm->free = cm->cd[cm->free].sub;
+		if (cm->free > 0)
+		{
+			assert(cm->free < cm->max);
+			pco = cm->free;
+			nco = cm->cd[pco].sub;
+			while (nco > 0)
+				if ((size_t) nco > cm->max)
+				{
+					/* take this one out of freelist */
+					nco = cm->cd[nco].sub;
+					cm->cd[pco].sub = nco;
+				}
+				else
+				{
+					assert(nco < cm->max);
+					pco = nco;
+					nco = cm->cd[pco].sub;
+				}
+		}
+	}
+	else
+	{
+		cd->sub = cm->free;
+		cm->free = (color) (cd - cm->cd);
+	}
+}
+
+/*
+ * pseudocolor - allocate a false color, to be managed by other means
+ */
+static color
+pseudocolor(struct colormap * cm)
+{
+	color		co;
+
+	co = newcolor(cm);
+	if (CISERR())
+		return COLORLESS;
+	cm->cd[co].nchrs = 1;
+	cm->cd[co].flags = PSEUDO;
+	return co;
+}
+
+/*
+ * subcolor - allocate a new subcolor (if necessary) to this chr
+ */
+static color
+subcolor(struct colormap * cm, chr c)
+{
+	color		co;				/* current color of c */
+	color		sco;			/* new subcolor */
+
+	co = GETCOLOR(cm, c);
+	sco = newsub(cm, co);
+	if (CISERR())
+		return COLORLESS;
+	assert(sco != COLORLESS);
+
+	if (co == sco)				/* already in an open subcolor */
+		return co;				/* rest is redundant */
+	cm->cd[co].nchrs--;
+	if (cm->cd[sco].nchrs == 0)
+		cm->cd[sco].firstchr = c;
+	cm->cd[sco].nchrs++;
+	setcolor(cm, c, sco);
+	return sco;
+}
+
+/*
+ * newsub - allocate a new subcolor (if necessary) for a color
+ */
+static color
+newsub(struct colormap * cm,
+	   pcolor co)
+{
+	color		sco;			/* new subcolor */
+
+	sco = cm->cd[co].sub;
+	if (sco == NOSUB)
+	{							/* color has no open subcolor */
+		if (cm->cd[co].nchrs == 1)		/* optimization */
+			return co;
+		sco = newcolor(cm);		/* must create subcolor */
+		if (sco == COLORLESS)
+		{
+			assert(CISERR());
+			return COLORLESS;
+		}
+		cm->cd[co].sub = sco;
+		cm->cd[sco].sub = sco;	/* open subcolor points to self */
+	}
+	assert(sco != NOSUB);
+
+	return sco;
+}
+
+/*
+ * subrange - allocate new subcolors to this range of chrs, fill in arcs
+ */
+static void
+subrange(struct vars * v,
+		 chr from,
+		 chr to,
+		 struct state * lp,
+		 struct state * rp)
+{
+	uchr		uf;
+	int			i;
+
+	assert(from <= to);
+
+	/* first, align "from" on a tree-block boundary */
+	uf = (uchr) from;
+	i = (int) (((uf + BYTTAB - 1) & (uchr) ~BYTMASK) - uf);
+	for (; from <= to && i > 0; i--, from++)
+		newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp);
+	if (from > to)				/* didn't reach a boundary */
+		return;
+
+	/* deal with whole blocks */
+	for (; to - from >= BYTTAB; from += BYTTAB)
+		subblock(v, from, lp, rp);
+
+	/* clean up any remaining partial table */
+	for (; from <= to; from++)
+		newarc(v->nfa, PLAIN, subcolor(v->cm, from), lp, rp);
+}
+
+/*
+ * subblock - allocate new subcolors for one tree block of chrs, fill in arcs
+ *
+ * Note: subcolors that are created during execution of this function
+ * will not be given a useful value of firstchr; it'll be left as CHR_MIN.
+ * For the current usage of firstchr in pg_regprefix, this does not matter
+ * because such subcolors won't occur in the common prefix of a regex.
+ */
+static void
+subblock(struct vars * v,
+		 chr start,				/* first of BYTTAB chrs */
+		 struct state * lp,
+		 struct state * rp)
+{
+	uchr		uc = start;
+	struct colormap *cm = v->cm;
+	int			shift;
+	int			level;
+	int			i;
+	int			b;
+	union tree *t;
+	union tree *cb;
+	union tree *fillt;
+	union tree *lastt;
+	int			previ;
+	int			ndone;
+	color		co;
+	color		sco;
+
+	assert((uc % BYTTAB) == 0);
+
+	/* find its color block, making new pointer blocks as needed */
+	t = cm->tree;
+	fillt = NULL;
+	for (level = 0, shift = BYTBITS * (NBYTS - 1); shift > 0;
+		 level++, shift -= BYTBITS)
+	{
+		b = (uc >> shift) & BYTMASK;
+		lastt = t;
+		t = lastt->tptr[b];
+		assert(t != NULL);
+		fillt = &cm->tree[level + 1];
+		if (t == fillt && shift > BYTBITS)
+		{						/* need new ptr block */
+			t = (union tree *) MALLOC(sizeof(struct ptrs));
+			if (t == NULL)
+			{
+				CERR(REG_ESPACE);
+				return;
+			}
+			memcpy(VS(t->tptr), VS(fillt->tptr),
+				   BYTTAB * sizeof(union tree *));
+			lastt->tptr[b] = t;
+		}
+	}
+
+	/* special cases:  fill block or solid block */
+	co = t->tcolor[0];
+	cb = cm->cd[co].block;
+	if (t == fillt || t == cb)
+	{
+		/* either way, we want a subcolor solid block */
+		sco = newsub(cm, co);
+		t = cm->cd[sco].block;
+		if (t == NULL)
+		{						/* must set it up */
+			t = (union tree *) MALLOC(sizeof(struct colors));
+			if (t == NULL)
+			{
+				CERR(REG_ESPACE);
+				return;
+			}
+			for (i = 0; i < BYTTAB; i++)
+				t->tcolor[i] = sco;
+			cm->cd[sco].block = t;
+		}
+		/* find loop must have run at least once */
+		lastt->tptr[b] = t;
+		newarc(v->nfa, PLAIN, sco, lp, rp);
+		cm->cd[co].nchrs -= BYTTAB;
+		cm->cd[sco].nchrs += BYTTAB;
+		return;
+	}
+
+	/* general case, a mixed block to be altered */
+	i = 0;
+	while (i < BYTTAB)
+	{
+		co = t->tcolor[i];
+		sco = newsub(cm, co);
+		newarc(v->nfa, PLAIN, sco, lp, rp);
+		previ = i;
+		do
+		{
+			t->tcolor[i++] = sco;
+		} while (i < BYTTAB && t->tcolor[i] == co);
+		ndone = i - previ;
+		cm->cd[co].nchrs -= ndone;
+		cm->cd[sco].nchrs += ndone;
+	}
+}
+
+/*
+ * okcolors - promote subcolors to full colors
+ */
+static void
+okcolors(struct nfa * nfa,
+		 struct colormap * cm)
+{
+	struct colordesc *cd;
+	struct colordesc *end = CDEND(cm);
+	struct colordesc *scd;
+	struct arc *a;
+	color		co;
+	color		sco;
+
+	for (cd = cm->cd, co = 0; cd < end; cd++, co++)
+	{
+		sco = cd->sub;
+		if (UNUSEDCOLOR(cd) || sco == NOSUB)
+		{
+			/* has no subcolor, no further action */
+		}
+		else if (sco == co)
+		{
+			/* is subcolor, let parent deal with it */
+		}
+		else if (cd->nchrs == 0)
+		{
+			/* parent empty, its arcs change color to subcolor */
+			cd->sub = NOSUB;
+			scd = &cm->cd[sco];
+			assert(scd->nchrs > 0);
+			assert(scd->sub == sco);
+			scd->sub = NOSUB;
+			while ((a = cd->arcs) != NULL)
+			{
+				assert(a->co == co);
+				uncolorchain(cm, a);
+				a->co = sco;
+				colorchain(cm, a);
+			}
+			freecolor(cm, co);
+		}
+		else
+		{
+			/* parent's arcs must gain parallel subcolor arcs */
+			cd->sub = NOSUB;
+			scd = &cm->cd[sco];
+			assert(scd->nchrs > 0);
+			assert(scd->sub == sco);
+			scd->sub = NOSUB;
+			for (a = cd->arcs; a != NULL; a = a->colorchain)
+			{
+				assert(a->co == co);
+				newarc(nfa, a->type, sco, a->from, a->to);
+			}
+		}
+	}
+}
+
+/*
+ * colorchain - add this arc to the color chain of its color
+ */
+static void
+colorchain(struct colormap * cm,
+		   struct arc * a)
+{
+	struct colordesc *cd = &cm->cd[a->co];
+
+	if (cd->arcs != NULL)
+		cd->arcs->colorchainRev = a;
+	a->colorchain = cd->arcs;
+	a->colorchainRev = NULL;
+	cd->arcs = a;
+}
+
+/*
+ * uncolorchain - delete this arc from the color chain of its color
+ */
+static void
+uncolorchain(struct colormap * cm,
+			 struct arc * a)
+{
+	struct colordesc *cd = &cm->cd[a->co];
+	struct arc *aa = a->colorchainRev;
+
+	if (aa == NULL)
+	{
+		assert(cd->arcs == a);
+		cd->arcs = a->colorchain;
+	}
+	else
+	{
+		assert(aa->colorchain == a);
+		aa->colorchain = a->colorchain;
+	}
+	if (a->colorchain != NULL)
+		a->colorchain->colorchainRev = aa;
+	a->colorchain = NULL;		/* paranoia */
+	a->colorchainRev = NULL;
+}
+
+/*
+ * rainbow - add arcs of all full colors (but one) between specified states
+ */
+static void
+rainbow(struct nfa * nfa,
+		struct colormap * cm,
+		int type,
+		pcolor but,				/* COLORLESS if no exceptions */
+		struct state * from,
+		struct state * to)
+{
+	struct colordesc *cd;
+	struct colordesc *end = CDEND(cm);
+	color		co;
+
+	for (cd = cm->cd, co = 0; cd < end && !CISERR(); cd++, co++)
+		if (!UNUSEDCOLOR(cd) && cd->sub != co && co != but &&
+			!(cd->flags & PSEUDO))
+			newarc(nfa, type, co, from, to);
+}
+
+/*
+ * colorcomplement - add arcs of complementary colors
+ *
+ * The calling sequence ought to be reconciled with cloneouts().
+ */
+static void
+colorcomplement(struct nfa * nfa,
+				struct colormap * cm,
+				int type,
+				struct state * of,		/* complements of this guy's PLAIN
+										 * outarcs */
+				struct state * from,
+				struct state * to)
+{
+	struct colordesc *cd;
+	struct colordesc *end = CDEND(cm);
+	color		co;
+
+	assert(of != from);
+	for (cd = cm->cd, co = 0; cd < end && !CISERR(); cd++, co++)
+		if (!UNUSEDCOLOR(cd) && !(cd->flags & PSEUDO))
+			if (findarc(of, PLAIN, co) == NULL)
+				newarc(nfa, type, co, from, to);
+}
+
+
+#ifdef REG_DEBUG
+
+/*
+ * dumpcolors - debugging output
+ */
+static void
+dumpcolors(struct colormap * cm,
+		   FILE *f)
+{
+	struct colordesc *cd;
+	struct colordesc *end;
+	color		co;
+	chr			c;
+	char	   *has;
+
+	fprintf(f, "max %ld\n", (long) cm->max);
+	if (NBYTS > 1)
+		fillcheck(cm, cm->tree, 0, f);
+	end = CDEND(cm);
+	for (cd = cm->cd + 1, co = 1; cd < end; cd++, co++) /* skip 0 */
+		if (!UNUSEDCOLOR(cd))
+		{
+			assert(cd->nchrs > 0);
+			has = (cd->block != NULL) ? "#" : "";
+			if (cd->flags & PSEUDO)
+				fprintf(f, "#%2ld%s(ps): ", (long) co, has);
+			else
+				fprintf(f, "#%2ld%s(%2d): ", (long) co,
+						has, cd->nchrs);
+
+			/*
+			 * Unfortunately, it's hard to do this next bit more efficiently.
+			 *
+			 * Spencer's original coding has the loop iterating from CHR_MIN
+			 * to CHR_MAX, but that's utterly unusable for 32-bit chr. For
+			 * debugging purposes it seems fine to print only chr codes up to
+			 * 1000 or so.
+			 */
+			for (c = CHR_MIN; c < 1000; c++)
+				if (GETCOLOR(cm, c) == co)
+					dumpchr(c, f);
+			fprintf(f, "\n");
+		}
+}
+
+/*
+ * fillcheck - check proper filling of a tree
+ */
+static void
+fillcheck(struct colormap * cm,
+		  union tree * tree,
+		  int level,			/* level number (top == 0) of this block */
+		  FILE *f)
+{
+	int			i;
+	union tree *t;
+	union tree *fillt = &cm->tree[level + 1];
+
+	assert(level < NBYTS - 1);	/* this level has pointers */
+	for (i = BYTTAB - 1; i >= 0; i--)
+	{
+		t = tree->tptr[i];
+		if (t == NULL)
+			fprintf(f, "NULL found in filled tree!\n");
+		else if (t == fillt)
+		{
+		}
+		else if (level < NBYTS - 2)		/* more pointer blocks below */
+			fillcheck(cm, t, level + 1, f);
+	}
+}
+
+/*
+ * dumpchr - print a chr
+ *
+ * Kind of char-centric but works well enough for debug use.
+ */
+static void
+dumpchr(chr c,
+		FILE *f)
+{
+	if (c == '\\')
+		fprintf(f, "\\\\");
+	else if (c > ' ' && c <= '~')
+		putc((char) c, f);
+	else
+		fprintf(f, "\\u%04lx", (long) c);
+}
+
+#endif   /* REG_DEBUG */
diff --git a/src/common/regex/regc_cvec.c b/src/common/regex/regc_cvec.c
new file mode 100644
index 0000000..921a7d7
--- /dev/null
+++ b/src/common/regex/regc_cvec.c
@@ -0,0 +1,136 @@
+/*
+ * Utility functions for handling cvecs
+ * This file is #included by regcomp.c.
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regc_cvec.c
+ *
+ */
+
+/*
+ * Notes:
+ * Only (selected) functions in _this_ file should treat chr* as non-constant.
+ */
+
+/*
+ * newcvec - allocate a new cvec
+ */
+static struct cvec *
+newcvec(int nchrs,				/* to hold this many chrs... */
+		int nranges)			/* ... and this many ranges */
+{
+	size_t		nc = (size_t) nchrs + (size_t) nranges * 2;
+	size_t		n = sizeof(struct cvec) + nc * sizeof(chr);
+	struct cvec *cv = (struct cvec *) MALLOC(n);
+
+	if (cv == NULL)
+		return NULL;
+	cv->chrspace = nchrs;
+	cv->chrs = (chr *) (((char *) cv) + sizeof(struct cvec));
+	cv->ranges = cv->chrs + nchrs;
+	cv->rangespace = nranges;
+	return clearcvec(cv);
+}
+
+/*
+ * clearcvec - clear a possibly-new cvec
+ * Returns pointer as convenience.
+ */
+static struct cvec *
+clearcvec(struct cvec * cv)
+{
+	assert(cv != NULL);
+	cv->nchrs = 0;
+	cv->nranges = 0;
+	return cv;
+}
+
+/*
+ * addchr - add a chr to a cvec
+ */
+static void
+addchr(struct cvec * cv,		/* character vector */
+	   chr c)					/* character to add */
+{
+	assert(cv->nchrs < cv->chrspace);
+	cv->chrs[cv->nchrs++] = (chr) c;
+}
+
+/*
+ * addrange - add a range to a cvec
+ */
+static void
+addrange(struct cvec * cv,		/* character vector */
+		 chr from,				/* first character of range */
+		 chr to)				/* last character of range */
+{
+	assert(cv->nranges < cv->rangespace);
+	cv->ranges[cv->nranges * 2] = (chr) from;
+	cv->ranges[cv->nranges * 2 + 1] = (chr) to;
+	cv->nranges++;
+}
+
+/*
+ * getcvec - get a transient cvec, initialized to empty
+ *
+ * The returned cvec is valid only until the next call of getcvec, which
+ * typically will recycle the space.  Callers should *not* free the cvec
+ * explicitly; it will be cleaned up when the struct vars is destroyed.
+ *
+ * This is typically used while interpreting bracket expressions.  In that
+ * usage the cvec is only needed momentarily until we build arcs from it,
+ * so transientness is a convenient behavior.
+ */
+static struct cvec *
+getcvec(struct vars * v,		/* context */
+		int nchrs,				/* to hold this many chrs... */
+		int nranges)			/* ... and this many ranges */
+{
+	/* recycle existing transient cvec if large enough */
+	if (v->cv != NULL && nchrs <= v->cv->chrspace &&
+		nranges <= v->cv->rangespace)
+		return clearcvec(v->cv);
+
+	/* nope, make a new one */
+	if (v->cv != NULL)
+		freecvec(v->cv);
+	v->cv = newcvec(nchrs, nranges);
+	if (v->cv == NULL)
+		ERR(REG_ESPACE);
+
+	return v->cv;
+}
+
+/*
+ * freecvec - free a cvec
+ */
+static void
+freecvec(struct cvec * cv)
+{
+	FREE(cv);
+}
diff --git a/src/common/regex/regc_lex.c b/src/common/regex/regc_lex.c
new file mode 100644
index 0000000..bfd9dcd
--- /dev/null
+++ b/src/common/regex/regc_lex.c
@@ -0,0 +1,1171 @@
+/*
+ * lexical analyzer
+ * This file is #included by regcomp.c.
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regc_lex.c
+ *
+ */
+
+/* scanning macros (know about v) */
+#define ATEOS()		(v->now >= v->stop)
+#define HAVE(n)		(v->stop - v->now >= (n))
+#define NEXT1(c)	(!ATEOS() && *v->now == CHR(c))
+#define NEXT2(a,b)	(HAVE(2) && *v->now == CHR(a) && *(v->now+1) == CHR(b))
+#define NEXT3(a,b,c)	(HAVE(3) && *v->now == CHR(a) && \
+						*(v->now+1) == CHR(b) && \
+						*(v->now+2) == CHR(c))
+#define SET(c)		(v->nexttype = (c))
+#define SETV(c, n)	(v->nexttype = (c), v->nextvalue = (n))
+#define RET(c)		return (SET(c), 1)
+#define RETV(c, n)	return (SETV(c, n), 1)
+#define FAILW(e)	return (ERR(e), 0)	/* ERR does SET(EOS) */
+#define LASTTYPE(t) (v->lasttype == (t))
+
+/* lexical contexts */
+#define L_ERE	1				/* mainline ERE/ARE */
+#define L_BRE	2				/* mainline BRE */
+#define L_Q 3					/* REG_QUOTE */
+#define L_EBND	4				/* ERE/ARE bound */
+#define L_BBND	5				/* BRE bound */
+#define L_BRACK 6				/* brackets */
+#define L_CEL	7				/* collating element */
+#define L_ECL	8				/* equivalence class */
+#define L_CCL	9				/* character class */
+#define INTOCON(c)	(v->lexcon = (c))
+#define INCON(con)	(v->lexcon == (con))
+
+/* construct pointer past end of chr array */
+#define ENDOF(array)	((array) + sizeof(array)/sizeof(chr))
+
+/*
+ * lexstart - set up lexical stuff, scan leading options
+ */
+static void
+lexstart(struct vars * v)
+{
+	prefixes(v);				/* may turn on new type bits etc. */
+	NOERR();
+
+	if (v->cflags & REG_QUOTE)
+	{
+		assert(!(v->cflags & (REG_ADVANCED | REG_EXPANDED | REG_NEWLINE)));
+		INTOCON(L_Q);
+	}
+	else if (v->cflags & REG_EXTENDED)
+	{
+		assert(!(v->cflags & REG_QUOTE));
+		INTOCON(L_ERE);
+	}
+	else
+	{
+		assert(!(v->cflags & (REG_QUOTE | REG_ADVF)));
+		INTOCON(L_BRE);
+	}
+
+	v->nexttype = EMPTY;		/* remember we were at the start */
+	next(v);					/* set up the first token */
+}
+
+/*
+ * prefixes - implement various special prefixes
+ */
+static void
+prefixes(struct vars * v)
+{
+	/* literal string doesn't get any of this stuff */
+	if (v->cflags & REG_QUOTE)
+		return;
+
+	/* initial "***" gets special things */
+	if (HAVE(4) && NEXT3('*', '*', '*'))
+		switch (*(v->now + 3))
+		{
+			case CHR('?'):		/* "***?" error, msg shows version */
+				ERR(REG_BADPAT);
+				return;			/* proceed no further */
+				break;
+			case CHR('='):		/* "***=" shifts to literal string */
+				NOTE(REG_UNONPOSIX);
+				v->cflags |= REG_QUOTE;
+				v->cflags &= ~(REG_ADVANCED | REG_EXPANDED | REG_NEWLINE);
+				v->now += 4;
+				return;			/* and there can be no more prefixes */
+				break;
+			case CHR(':'):		/* "***:" shifts to AREs */
+				NOTE(REG_UNONPOSIX);
+				v->cflags |= REG_ADVANCED;
+				v->now += 4;
+				break;
+			default:			/* otherwise *** is just an error */
+				ERR(REG_BADRPT);
+				return;
+				break;
+		}
+
+	/* BREs and EREs don't get embedded options */
+	if ((v->cflags & REG_ADVANCED) != REG_ADVANCED)
+		return;
+
+	/* embedded options (AREs only) */
+	if (HAVE(3) && NEXT2('(', '?') && iscalpha(*(v->now + 2)))
+	{
+		NOTE(REG_UNONPOSIX);
+		v->now += 2;
+		for (; !ATEOS() && iscalpha(*v->now); v->now++)
+			switch (*v->now)
+			{
+				case CHR('b'):	/* BREs (but why???) */
+					v->cflags &= ~(REG_ADVANCED | REG_QUOTE);
+					break;
+				case CHR('c'):	/* case sensitive */
+					v->cflags &= ~REG_ICASE;
+					break;
+				case CHR('e'):	/* plain EREs */
+					v->cflags |= REG_EXTENDED;
+					v->cflags &= ~(REG_ADVF | REG_QUOTE);
+					break;
+				case CHR('i'):	/* case insensitive */
+					v->cflags |= REG_ICASE;
+					break;
+				case CHR('m'):	/* Perloid synonym for n */
+				case CHR('n'):	/* \n affects ^ $ . [^ */
+					v->cflags |= REG_NEWLINE;
+					break;
+				case CHR('p'):	/* ~Perl, \n affects . [^ */
+					v->cflags |= REG_NLSTOP;
+					v->cflags &= ~REG_NLANCH;
+					break;
+				case CHR('q'):	/* literal string */
+					v->cflags |= REG_QUOTE;
+					v->cflags &= ~REG_ADVANCED;
+					break;
+				case CHR('s'):	/* single line, \n ordinary */
+					v->cflags &= ~REG_NEWLINE;
+					break;
+				case CHR('t'):	/* tight syntax */
+					v->cflags &= ~REG_EXPANDED;
+					break;
+				case CHR('w'):	/* weird, \n affects ^ $ only */
+					v->cflags &= ~REG_NLSTOP;
+					v->cflags |= REG_NLANCH;
+					break;
+				case CHR('x'):	/* expanded syntax */
+					v->cflags |= REG_EXPANDED;
+					break;
+				default:
+					ERR(REG_BADOPT);
+					return;
+			}
+		if (!NEXT1(')'))
+		{
+			ERR(REG_BADOPT);
+			return;
+		}
+		v->now++;
+		if (v->cflags & REG_QUOTE)
+			v->cflags &= ~(REG_EXPANDED | REG_NEWLINE);
+	}
+}
+
+/*
+ * lexnest - "call a subroutine", interpolating string at the lexical level
+ *
+ * Note, this is not a very general facility.  There are a number of
+ * implicit assumptions about what sorts of strings can be subroutines.
+ */
+static void
+lexnest(struct vars * v,
+		const chr *beginp,		/* start of interpolation */
+		const chr *endp)		/* one past end of interpolation */
+{
+	assert(v->savenow == NULL); /* only one level of nesting */
+	v->savenow = v->now;
+	v->savestop = v->stop;
+	v->now = beginp;
+	v->stop = endp;
+}
+
+/*
+ * string constants to interpolate as expansions of things like \d
+ */
+static const chr backd[] = {	/* \d */
+	CHR('['), CHR('['), CHR(':'),
+	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
+	CHR(':'), CHR(']'), CHR(']')
+};
+static const chr backD[] = {	/* \D */
+	CHR('['), CHR('^'), CHR('['), CHR(':'),
+	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
+	CHR(':'), CHR(']'), CHR(']')
+};
+static const chr brbackd[] = {	/* \d within brackets */
+	CHR('['), CHR(':'),
+	CHR('d'), CHR('i'), CHR('g'), CHR('i'), CHR('t'),
+	CHR(':'), CHR(']')
+};
+static const chr backs[] = {	/* \s */
+	CHR('['), CHR('['), CHR(':'),
+	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
+	CHR(':'), CHR(']'), CHR(']')
+};
+static const chr backS[] = {	/* \S */
+	CHR('['), CHR('^'), CHR('['), CHR(':'),
+	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
+	CHR(':'), CHR(']'), CHR(']')
+};
+static const chr brbacks[] = {	/* \s within brackets */
+	CHR('['), CHR(':'),
+	CHR('s'), CHR('p'), CHR('a'), CHR('c'), CHR('e'),
+	CHR(':'), CHR(']')
+};
+static const chr backw[] = {	/* \w */
+	CHR('['), CHR('['), CHR(':'),
+	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
+	CHR(':'), CHR(']'), CHR('_'), CHR(']')
+};
+static const chr backW[] = {	/* \W */
+	CHR('['), CHR('^'), CHR('['), CHR(':'),
+	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
+	CHR(':'), CHR(']'), CHR('_'), CHR(']')
+};
+static const chr brbackw[] = {	/* \w within brackets */
+	CHR('['), CHR(':'),
+	CHR('a'), CHR('l'), CHR('n'), CHR('u'), CHR('m'),
+	CHR(':'), CHR(']'), CHR('_')
+};
+
+/*
+ * lexword - interpolate a bracket expression for word characters
+ * Possibly ought to inquire whether there is a "word" character class.
+ */
+static void
+lexword(struct vars * v)
+{
+	lexnest(v, backw, ENDOF(backw));
+}
+
+/*
+ * next - get next token
+ */
+static int						/* 1 normal, 0 failure */
+next(struct vars * v)
+{
+	chr			c;
+
+	/* errors yield an infinite sequence of failures */
+	if (ISERR())
+		return 0;				/* the error has set nexttype to EOS */
+
+	/* remember flavor of last token */
+	v->lasttype = v->nexttype;
+
+	/* REG_BOSONLY */
+	if (v->nexttype == EMPTY && (v->cflags & REG_BOSONLY))
+	{
+		/* at start of a REG_BOSONLY RE */
+		RETV(SBEGIN, 0);		/* same as \A */
+	}
+
+	/* if we're nested and we've hit end, return to outer level */
+	if (v->savenow != NULL && ATEOS())
+	{
+		v->now = v->savenow;
+		v->stop = v->savestop;
+		v->savenow = v->savestop = NULL;
+	}
+
+	/* skip white space etc. if appropriate (not in literal or []) */
+	if (v->cflags & REG_EXPANDED)
+		switch (v->lexcon)
+		{
+			case L_ERE:
+			case L_BRE:
+			case L_EBND:
+			case L_BBND:
+				skip(v);
+				break;
+		}
+
+	/* handle EOS, depending on context */
+	if (ATEOS())
+	{
+		switch (v->lexcon)
+		{
+			case L_ERE:
+			case L_BRE:
+			case L_Q:
+				RET(EOS);
+				break;
+			case L_EBND:
+			case L_BBND:
+				FAILW(REG_EBRACE);
+				break;
+			case L_BRACK:
+			case L_CEL:
+			case L_ECL:
+			case L_CCL:
+				FAILW(REG_EBRACK);
+				break;
+		}
+		assert(NOTREACHED);
+	}
+
+	/* okay, time to actually get a character */
+	c = *v->now++;
+
+	/* deal with the easy contexts, punt EREs to code below */
+	switch (v->lexcon)
+	{
+		case L_BRE:				/* punt BREs to separate function */
+			return brenext(v, c);
+			break;
+		case L_ERE:				/* see below */
+			break;
+		case L_Q:				/* literal strings are easy */
+			RETV(PLAIN, c);
+			break;
+		case L_BBND:			/* bounds are fairly simple */
+		case L_EBND:
+			switch (c)
+			{
+				case CHR('0'):
+				case CHR('1'):
+				case CHR('2'):
+				case CHR('3'):
+				case CHR('4'):
+				case CHR('5'):
+				case CHR('6'):
+				case CHR('7'):
+				case CHR('8'):
+				case CHR('9'):
+					RETV(DIGIT, (chr) DIGITVAL(c));
+					break;
+				case CHR(','):
+					RET(',');
+					break;
+				case CHR('}'):	/* ERE bound ends with } */
+					if (INCON(L_EBND))
+					{
+						INTOCON(L_ERE);
+						if ((v->cflags & REG_ADVF) && NEXT1('?'))
+						{
+							v->now++;
+							NOTE(REG_UNONPOSIX);
+							RETV('}', 0);
+						}
+						RETV('}', 1);
+					}
+					else
+						FAILW(REG_BADBR);
+					break;
+				case CHR('\\'):	/* BRE bound ends with \} */
+					if (INCON(L_BBND) && NEXT1('}'))
+					{
+						v->now++;
+						INTOCON(L_BRE);
+						RET('}');
+					}
+					else
+						FAILW(REG_BADBR);
+					break;
+				default:
+					FAILW(REG_BADBR);
+					break;
+			}
+			assert(NOTREACHED);
+			break;
+		case L_BRACK:			/* brackets are not too hard */
+			switch (c)
+			{
+				case CHR(']'):
+					if (LASTTYPE('['))
+						RETV(PLAIN, c);
+					else
+					{
+						INTOCON((v->cflags & REG_EXTENDED) ?
+								L_ERE : L_BRE);
+						RET(']');
+					}
+					break;
+				case CHR('\\'):
+					NOTE(REG_UBBS);
+					if (!(v->cflags & REG_ADVF))
+						RETV(PLAIN, c);
+					NOTE(REG_UNONPOSIX);
+					if (ATEOS())
+						FAILW(REG_EESCAPE);
+					(DISCARD) lexescape(v);
+					switch (v->nexttype)
+					{			/* not all escapes okay here */
+						case PLAIN:
+							return 1;
+							break;
+						case CCLASS:
+							switch (v->nextvalue)
+							{
+								case 'd':
+									lexnest(v, brbackd, ENDOF(brbackd));
+									break;
+								case 's':
+									lexnest(v, brbacks, ENDOF(brbacks));
+									break;
+								case 'w':
+									lexnest(v, brbackw, ENDOF(brbackw));
+									break;
+								default:
+									FAILW(REG_EESCAPE);
+									break;
+							}
+							/* lexnest done, back up and try again */
+							v->nexttype = v->lasttype;
+							return next(v);
+							break;
+					}
+					/* not one of the acceptable escapes */
+					FAILW(REG_EESCAPE);
+					break;
+				case CHR('-'):
+					if (LASTTYPE('[') || NEXT1(']'))
+						RETV(PLAIN, c);
+					else
+						RETV(RANGE, c);
+					break;
+				case CHR('['):
+					if (ATEOS())
+						FAILW(REG_EBRACK);
+					switch (*v->now++)
+					{
+						case CHR('.'):
+							INTOCON(L_CEL);
+							/* might or might not be locale-specific */
+							RET(COLLEL);
+							break;
+						case CHR('='):
+							INTOCON(L_ECL);
+							NOTE(REG_ULOCALE);
+							RET(ECLASS);
+							break;
+						case CHR(':'):
+							INTOCON(L_CCL);
+							NOTE(REG_ULOCALE);
+							RET(CCLASS);
+							break;
+						default:		/* oops */
+							v->now--;
+							RETV(PLAIN, c);
+							break;
+					}
+					assert(NOTREACHED);
+					break;
+				default:
+					RETV(PLAIN, c);
+					break;
+			}
+			assert(NOTREACHED);
+			break;
+		case L_CEL:				/* collating elements are easy */
+			if (c == CHR('.') && NEXT1(']'))
+			{
+				v->now++;
+				INTOCON(L_BRACK);
+				RETV(END, '.');
+			}
+			else
+				RETV(PLAIN, c);
+			break;
+		case L_ECL:				/* ditto equivalence classes */
+			if (c == CHR('=') && NEXT1(']'))
+			{
+				v->now++;
+				INTOCON(L_BRACK);
+				RETV(END, '=');
+			}
+			else
+				RETV(PLAIN, c);
+			break;
+		case L_CCL:				/* ditto character classes */
+			if (c == CHR(':') && NEXT1(']'))
+			{
+				v->now++;
+				INTOCON(L_BRACK);
+				RETV(END, ':');
+			}
+			else
+				RETV(PLAIN, c);
+			break;
+		default:
+			assert(NOTREACHED);
+			break;
+	}
+
+	/* that got rid of everything except EREs and AREs */
+	assert(INCON(L_ERE));
+
+	/* deal with EREs and AREs, except for backslashes */
+	switch (c)
+	{
+		case CHR('|'):
+			RET('|');
+			break;
+		case CHR('*'):
+			if ((v->cflags & REG_ADVF) && NEXT1('?'))
+			{
+				v->now++;
+				NOTE(REG_UNONPOSIX);
+				RETV('*', 0);
+			}
+			RETV('*', 1);
+			break;
+		case CHR('+'):
+			if ((v->cflags & REG_ADVF) && NEXT1('?'))
+			{
+				v->now++;
+				NOTE(REG_UNONPOSIX);
+				RETV('+', 0);
+			}
+			RETV('+', 1);
+			break;
+		case CHR('?'):
+			if ((v->cflags & REG_ADVF) && NEXT1('?'))
+			{
+				v->now++;
+				NOTE(REG_UNONPOSIX);
+				RETV('?', 0);
+			}
+			RETV('?', 1);
+			break;
+		case CHR('{'):			/* bounds start or plain character */
+			if (v->cflags & REG_EXPANDED)
+				skip(v);
+			if (ATEOS() || !iscdigit(*v->now))
+			{
+				NOTE(REG_UBRACES);
+				NOTE(REG_UUNSPEC);
+				RETV(PLAIN, c);
+			}
+			else
+			{
+				NOTE(REG_UBOUNDS);
+				INTOCON(L_EBND);
+				RET('{');
+			}
+			assert(NOTREACHED);
+			break;
+		case CHR('('):			/* parenthesis, or advanced extension */
+			if ((v->cflags & REG_ADVF) && NEXT1('?'))
+			{
+				NOTE(REG_UNONPOSIX);
+				v->now++;
+				if (ATEOS())
+					FAILW(REG_BADRPT);
+				switch (*v->now++)
+				{
+					case CHR(':'):		/* non-capturing paren */
+						RETV('(', 0);
+						break;
+					case CHR('#'):		/* comment */
+						while (!ATEOS() && *v->now != CHR(')'))
+							v->now++;
+						if (!ATEOS())
+							v->now++;
+						assert(v->nexttype == v->lasttype);
+						return next(v);
+						break;
+					case CHR('='):		/* positive lookahead */
+						NOTE(REG_ULOOKAROUND);
+						RETV(LACON, LATYPE_AHEAD_POS);
+						break;
+					case CHR('!'):		/* negative lookahead */
+						NOTE(REG_ULOOKAROUND);
+						RETV(LACON, LATYPE_AHEAD_NEG);
+						break;
+					case CHR('<'):
+						if (ATEOS())
+							FAILW(REG_BADRPT);
+						switch (*v->now++)
+						{
+							case CHR('='):		/* positive lookbehind */
+								NOTE(REG_ULOOKAROUND);
+								RETV(LACON, LATYPE_BEHIND_POS);
+								break;
+							case CHR('!'):		/* negative lookbehind */
+								NOTE(REG_ULOOKAROUND);
+								RETV(LACON, LATYPE_BEHIND_NEG);
+								break;
+							default:
+								FAILW(REG_BADRPT);
+								break;
+						}
+						assert(NOTREACHED);
+						break;
+					default:
+						FAILW(REG_BADRPT);
+						break;
+				}
+				assert(NOTREACHED);
+			}
+			if (v->cflags & REG_NOSUB)
+				RETV('(', 0);	/* all parens non-capturing */
+			else
+				RETV('(', 1);
+			break;
+		case CHR(')'):
+			if (LASTTYPE('('))
+				NOTE(REG_UUNSPEC);
+			RETV(')', c);
+			break;
+		case CHR('['):			/* easy except for [[:<:]] and [[:>:]] */
+			if (HAVE(6) && *(v->now + 0) == CHR('[') &&
+				*(v->now + 1) == CHR(':') &&
+				(*(v->now + 2) == CHR('<') ||
+				 *(v->now + 2) == CHR('>')) &&
+				*(v->now + 3) == CHR(':') &&
+				*(v->now + 4) == CHR(']') &&
+				*(v->now + 5) == CHR(']'))
+			{
+				c = *(v->now + 2);
+				v->now += 6;
+				NOTE(REG_UNONPOSIX);
+				RET((c == CHR('<')) ? '<' : '>');
+			}
+			INTOCON(L_BRACK);
+			if (NEXT1('^'))
+			{
+				v->now++;
+				RETV('[', 0);
+			}
+			RETV('[', 1);
+			break;
+		case CHR('.'):
+			RET('.');
+			break;
+		case CHR('^'):
+			RET('^');
+			break;
+		case CHR('$'):
+			RET('$');
+			break;
+		case CHR('\\'): /* mostly punt backslashes to code below */
+			if (ATEOS())
+				FAILW(REG_EESCAPE);
+			break;
+		default:				/* ordinary character */
+			RETV(PLAIN, c);
+			break;
+	}
+
+	/* ERE/ARE backslash handling; backslash already eaten */
+	assert(!ATEOS());
+	if (!(v->cflags & REG_ADVF))
+	{							/* only AREs have non-trivial escapes */
+		if (iscalnum(*v->now))
+		{
+			NOTE(REG_UBSALNUM);
+			NOTE(REG_UUNSPEC);
+		}
+		RETV(PLAIN, *v->now++);
+	}
+	(DISCARD) lexescape(v);
+	if (ISERR())
+		FAILW(REG_EESCAPE);
+	if (v->nexttype == CCLASS)
+	{							/* fudge at lexical level */
+		switch (v->nextvalue)
+		{
+			case 'd':
+				lexnest(v, backd, ENDOF(backd));
+				break;
+			case 'D':
+				lexnest(v, backD, ENDOF(backD));
+				break;
+			case 's':
+				lexnest(v, backs, ENDOF(backs));
+				break;
+			case 'S':
+				lexnest(v, backS, ENDOF(backS));
+				break;
+			case 'w':
+				lexnest(v, backw, ENDOF(backw));
+				break;
+			case 'W':
+				lexnest(v, backW, ENDOF(backW));
+				break;
+			default:
+				assert(NOTREACHED);
+				FAILW(REG_ASSERT);
+				break;
+		}
+		/* lexnest done, back up and try again */
+		v->nexttype = v->lasttype;
+		return next(v);
+	}
+	/* otherwise, lexescape has already done the work */
+	return !ISERR();
+}
+
+/*
+ * lexescape - parse an ARE backslash escape (backslash already eaten)
+ * Note slightly nonstandard use of the CCLASS type code.
+ */
+static int						/* not actually used, but convenient for RETV */
+lexescape(struct vars * v)
+{
+	chr			c;
+	static const chr alert[] = {
+		CHR('a'), CHR('l'), CHR('e'), CHR('r'), CHR('t')
+	};
+	static const chr esc[] = {
+		CHR('E'), CHR('S'), CHR('C')
+	};
+	const chr  *save;
+
+	assert(v->cflags & REG_ADVF);
+
+	assert(!ATEOS());
+	c = *v->now++;
+	if (!iscalnum(c))
+		RETV(PLAIN, c);
+
+	NOTE(REG_UNONPOSIX);
+	switch (c)
+	{
+		case CHR('a'):
+			RETV(PLAIN, chrnamed(v, alert, ENDOF(alert), CHR('\007')));
+			break;
+		case CHR('A'):
+			RETV(SBEGIN, 0);
+			break;
+		case CHR('b'):
+			RETV(PLAIN, CHR('\b'));
+			break;
+		case CHR('B'):
+			RETV(PLAIN, CHR('\\'));
+			break;
+		case CHR('c'):
+			NOTE(REG_UUNPORT);
+			if (ATEOS())
+				FAILW(REG_EESCAPE);
+			RETV(PLAIN, (chr) (*v->now++ & 037));
+			break;
+		case CHR('d'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 'd');
+			break;
+		case CHR('D'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 'D');
+			break;
+		case CHR('e'):
+			NOTE(REG_UUNPORT);
+			RETV(PLAIN, chrnamed(v, esc, ENDOF(esc), CHR('\033')));
+			break;
+		case CHR('f'):
+			RETV(PLAIN, CHR('\f'));
+			break;
+		case CHR('m'):
+			RET('<');
+			break;
+		case CHR('M'):
+			RET('>');
+			break;
+		case CHR('n'):
+			RETV(PLAIN, CHR('\n'));
+			break;
+		case CHR('r'):
+			RETV(PLAIN, CHR('\r'));
+			break;
+		case CHR('s'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 's');
+			break;
+		case CHR('S'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 'S');
+			break;
+		case CHR('t'):
+			RETV(PLAIN, CHR('\t'));
+			break;
+		case CHR('u'):
+			c = lexdigits(v, 16, 4, 4);
+			if (ISERR())
+				FAILW(REG_EESCAPE);
+			RETV(PLAIN, c);
+			break;
+		case CHR('U'):
+			c = lexdigits(v, 16, 8, 8);
+			if (ISERR())
+				FAILW(REG_EESCAPE);
+			RETV(PLAIN, c);
+			break;
+		case CHR('v'):
+			RETV(PLAIN, CHR('\v'));
+			break;
+		case CHR('w'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 'w');
+			break;
+		case CHR('W'):
+			NOTE(REG_ULOCALE);
+			RETV(CCLASS, 'W');
+			break;
+		case CHR('x'):
+			NOTE(REG_UUNPORT);
+			c = lexdigits(v, 16, 1, 255);		/* REs >255 long outside spec */
+			if (ISERR())
+				FAILW(REG_EESCAPE);
+			RETV(PLAIN, c);
+			break;
+		case CHR('y'):
+			NOTE(REG_ULOCALE);
+			RETV(WBDRY, 0);
+			break;
+		case CHR('Y'):
+			NOTE(REG_ULOCALE);
+			RETV(NWBDRY, 0);
+			break;
+		case CHR('Z'):
+			RETV(SEND, 0);
+			break;
+		case CHR('1'):
+		case CHR('2'):
+		case CHR('3'):
+		case CHR('4'):
+		case CHR('5'):
+		case CHR('6'):
+		case CHR('7'):
+		case CHR('8'):
+		case CHR('9'):
+			save = v->now;
+			v->now--;			/* put first digit back */
+			c = lexdigits(v, 10, 1, 255);		/* REs >255 long outside spec */
+			if (ISERR())
+				FAILW(REG_EESCAPE);
+			/* ugly heuristic (first test is "exactly 1 digit?") */
+			if (v->now == save || ((int) c > 0 && (int) c <= v->nsubexp))
+			{
+				NOTE(REG_UBACKREF);
+				RETV(BACKREF, (chr) c);
+			}
+			/* oops, doesn't look like it's a backref after all... */
+			v->now = save;
+			/* and fall through into octal number */
+		case CHR('0'):
+			NOTE(REG_UUNPORT);
+			v->now--;			/* put first digit back */
+			c = lexdigits(v, 8, 1, 3);
+			if (ISERR())
+				FAILW(REG_EESCAPE);
+			if (c > 0xff)
+			{
+				/* out of range, so we handled one digit too much */
+				v->now--;
+				c >>= 3;
+			}
+			RETV(PLAIN, c);
+			break;
+		default:
+			assert(iscalpha(c));
+			FAILW(REG_EESCAPE); /* unknown alphabetic escape */
+			break;
+	}
+	assert(NOTREACHED);
+}
+
+/*
+ * lexdigits - slurp up digits and return chr value
+ */
+static chr						/* chr value; errors signalled via ERR */
+lexdigits(struct vars * v,
+		  int base,
+		  int minlen,
+		  int maxlen)
+{
+	uchr		n;				/* unsigned to avoid overflow misbehavior */
+	int			len;
+	chr			c;
+	int			d;
+	const uchr	ub = (uchr) base;
+
+	n = 0;
+	for (len = 0; len < maxlen && !ATEOS(); len++)
+	{
+		c = *v->now++;
+		switch (c)
+		{
+			case CHR('0'):
+			case CHR('1'):
+			case CHR('2'):
+			case CHR('3'):
+			case CHR('4'):
+			case CHR('5'):
+			case CHR('6'):
+			case CHR('7'):
+			case CHR('8'):
+			case CHR('9'):
+				d = DIGITVAL(c);
+				break;
+			case CHR('a'):
+			case CHR('A'):
+				d = 10;
+				break;
+			case CHR('b'):
+			case CHR('B'):
+				d = 11;
+				break;
+			case CHR('c'):
+			case CHR('C'):
+				d = 12;
+				break;
+			case CHR('d'):
+			case CHR('D'):
+				d = 13;
+				break;
+			case CHR('e'):
+			case CHR('E'):
+				d = 14;
+				break;
+			case CHR('f'):
+			case CHR('F'):
+				d = 15;
+				break;
+			default:
+				v->now--;		/* oops, not a digit at all */
+				d = -1;
+				break;
+		}
+
+		if (d >= base)
+		{						/* not a plausible digit */
+			v->now--;
+			d = -1;
+		}
+		if (d < 0)
+			break;				/* NOTE BREAK OUT */
+		n = n * ub + (uchr) d;
+	}
+	if (len < minlen)
+		ERR(REG_EESCAPE);
+
+	return (chr) n;
+}
+
+/*
+ * brenext - get next BRE token
+ *
+ * This is much like EREs except for all the stupid backslashes and the
+ * context-dependency of some things.
+ */
+static int						/* 1 normal, 0 failure */
+brenext(struct vars * v,
+		chr pc)
+{
+	chr			c = (chr) pc;
+
+	switch (c)
+	{
+		case CHR('*'):
+			if (LASTTYPE(EMPTY) || LASTTYPE('(') || LASTTYPE('^'))
+				RETV(PLAIN, c);
+			RET('*');
+			break;
+		case CHR('['):
+			if (HAVE(6) && *(v->now + 0) == CHR('[') &&
+				*(v->now + 1) == CHR(':') &&
+				(*(v->now + 2) == CHR('<') ||
+				 *(v->now + 2) == CHR('>')) &&
+				*(v->now + 3) == CHR(':') &&
+				*(v->now + 4) == CHR(']') &&
+				*(v->now + 5) == CHR(']'))
+			{
+				c = *(v->now + 2);
+				v->now += 6;
+				NOTE(REG_UNONPOSIX);
+				RET((c == CHR('<')) ? '<' : '>');
+			}
+			INTOCON(L_BRACK);
+			if (NEXT1('^'))
+			{
+				v->now++;
+				RETV('[', 0);
+			}
+			RETV('[', 1);
+			break;
+		case CHR('.'):
+			RET('.');
+			break;
+		case CHR('^'):
+			if (LASTTYPE(EMPTY))
+				RET('^');
+			if (LASTTYPE('('))
+			{
+				NOTE(REG_UUNSPEC);
+				RET('^');
+			}
+			RETV(PLAIN, c);
+			break;
+		case CHR('$'):
+			if (v->cflags & REG_EXPANDED)
+				skip(v);
+			if (ATEOS())
+				RET('$');
+			if (NEXT2('\\', ')'))
+			{
+				NOTE(REG_UUNSPEC);
+				RET('$');
+			}
+			RETV(PLAIN, c);
+			break;
+		case CHR('\\'):
+			break;				/* see below */
+		default:
+			RETV(PLAIN, c);
+			break;
+	}
+
+	assert(c == CHR('\\'));
+
+	if (ATEOS())
+		FAILW(REG_EESCAPE);
+
+	c = *v->now++;
+	switch (c)
+	{
+		case CHR('{'):
+			INTOCON(L_BBND);
+			NOTE(REG_UBOUNDS);
+			RET('{');
+			break;
+		case CHR('('):
+			RETV('(', 1);
+			break;
+		case CHR(')'):
+			RETV(')', c);
+			break;
+		case CHR('<'):
+			NOTE(REG_UNONPOSIX);
+			RET('<');
+			break;
+		case CHR('>'):
+			NOTE(REG_UNONPOSIX);
+			RET('>');
+			break;
+		case CHR('1'):
+		case CHR('2'):
+		case CHR('3'):
+		case CHR('4'):
+		case CHR('5'):
+		case CHR('6'):
+		case CHR('7'):
+		case CHR('8'):
+		case CHR('9'):
+			NOTE(REG_UBACKREF);
+			RETV(BACKREF, (chr) DIGITVAL(c));
+			break;
+		default:
+			if (iscalnum(c))
+			{
+				NOTE(REG_UBSALNUM);
+				NOTE(REG_UUNSPEC);
+			}
+			RETV(PLAIN, c);
+			break;
+	}
+
+	assert(NOTREACHED);
+	return 0;
+}
+
+/*
+ * skip - skip white space and comments in expanded form
+ */
+static void
+skip(struct vars * v)
+{
+	const chr  *start = v->now;
+
+	assert(v->cflags & REG_EXPANDED);
+
+	for (;;)
+	{
+		while (!ATEOS() && iscspace(*v->now))
+			v->now++;
+		if (ATEOS() || *v->now != CHR('#'))
+			break;				/* NOTE BREAK OUT */
+		assert(NEXT1('#'));
+		while (!ATEOS() && *v->now != CHR('\n'))
+			v->now++;
+		/* leave the newline to be picked up by the iscspace loop */
+	}
+
+	if (v->now != start)
+		NOTE(REG_UNONPOSIX);
+}
+
+/*
+ * newline - return the chr for a newline
+ *
+ * This helps confine use of CHR to this source file.
+ */
+static chr
+newline(void)
+{
+	return CHR('\n');
+}
+
+/*
+ * chrnamed - return the chr known by a given (chr string) name
+ *
+ * The code is a bit clumsy, but this routine gets only such specialized
+ * use that it hardly matters.
+ */
+static chr
+chrnamed(struct vars * v,
+		 const chr *startp,		/* start of name */
+		 const chr *endp,		/* just past end of name */
+		 chr lastresort)		/* what to return if name lookup fails */
+{
+	celt		c;
+	int			errsave;
+	int			e;
+	struct cvec *cv;
+
+	errsave = v->err;
+	v->err = 0;
+	c = element(v, startp, endp);
+	e = v->err;
+	v->err = errsave;
+
+	if (e != 0)
+		return (chr) lastresort;
+
+	cv = range(v, c, c, 0);
+	if (cv->nchrs == 0)
+		return (chr) lastresort;
+	return cv->chrs[0];
+}
diff --git a/src/common/regex/regc_locale.c b/src/common/regex/regc_locale.c
new file mode 100644
index 0000000..e7bbb50
--- /dev/null
+++ b/src/common/regex/regc_locale.c
@@ -0,0 +1,700 @@
+/*
+ * regc_locale.c --
+ *
+ *	This file contains locale-specific regexp routines.
+ *	This file is #included by regcomp.c.
+ *
+ * Copyright (c) 1998 by Scriptics Corporation.
+ *
+ * This software is copyrighted by the Regents of the University of
+ * California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
+ * Corporation and other parties.  The following terms apply to all files
+ * associated with the software unless explicitly disclaimed in
+ * individual files.
+ *
+ * The authors hereby grant permission to use, copy, modify, distribute,
+ * and license this software and its documentation for any purpose, provided
+ * that existing copyright notices are retained in all copies and that this
+ * notice is included verbatim in any distributions. No written agreement,
+ * license, or royalty fee is required for any of the authorized uses.
+ * Modifications to this software may be copyrighted by their authors
+ * and need not follow the licensing terms described here, provided that
+ * the new terms are clearly indicated on the first page of each file where
+ * they apply.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+ * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+ * DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
+ * IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+ * NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+ * MODIFICATIONS.
+ *
+ * GOVERNMENT USE: If you are acquiring this software on behalf of the
+ * U.S. government, the Government shall have only "Restricted Rights"
+ * in the software and related documentation as defined in the Federal
+ * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
+ * are acquiring the software on behalf of the Department of Defense, the
+ * software shall be classified as "Commercial Computer Software" and the
+ * Government shall have only "Restricted Rights" as defined in Clause
+ * 252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
+ * authors grant the U.S. Government and others acting in its behalf
+ * permission to use and distribute the software in accordance with the
+ * terms specified in this license.
+ *
+ * src/backend/regex/regc_locale.c
+ */
+
+/* ASCII character-name table */
+
+static const struct cname
+{
+	const char *name;
+	const char	code;
+}	cnames[] =
+
+{
+	{
+		"NUL", '\0'
+	},
+	{
+		"SOH", '\001'
+	},
+	{
+		"STX", '\002'
+	},
+	{
+		"ETX", '\003'
+	},
+	{
+		"EOT", '\004'
+	},
+	{
+		"ENQ", '\005'
+	},
+	{
+		"ACK", '\006'
+	},
+	{
+		"BEL", '\007'
+	},
+	{
+		"alert", '\007'
+	},
+	{
+		"BS", '\010'
+	},
+	{
+		"backspace", '\b'
+	},
+	{
+		"HT", '\011'
+	},
+	{
+		"tab", '\t'
+	},
+	{
+		"LF", '\012'
+	},
+	{
+		"newline", '\n'
+	},
+	{
+		"VT", '\013'
+	},
+	{
+		"vertical-tab", '\v'
+	},
+	{
+		"FF", '\014'
+	},
+	{
+		"form-feed", '\f'
+	},
+	{
+		"CR", '\015'
+	},
+	{
+		"carriage-return", '\r'
+	},
+	{
+		"SO", '\016'
+	},
+	{
+		"SI", '\017'
+	},
+	{
+		"DLE", '\020'
+	},
+	{
+		"DC1", '\021'
+	},
+	{
+		"DC2", '\022'
+	},
+	{
+		"DC3", '\023'
+	},
+	{
+		"DC4", '\024'
+	},
+	{
+		"NAK", '\025'
+	},
+	{
+		"SYN", '\026'
+	},
+	{
+		"ETB", '\027'
+	},
+	{
+		"CAN", '\030'
+	},
+	{
+		"EM", '\031'
+	},
+	{
+		"SUB", '\032'
+	},
+	{
+		"ESC", '\033'
+	},
+	{
+		"IS4", '\034'
+	},
+	{
+		"FS", '\034'
+	},
+	{
+		"IS3", '\035'
+	},
+	{
+		"GS", '\035'
+	},
+	{
+		"IS2", '\036'
+	},
+	{
+		"RS", '\036'
+	},
+	{
+		"IS1", '\037'
+	},
+	{
+		"US", '\037'
+	},
+	{
+		"space", ' '
+	},
+	{
+		"exclamation-mark", '!'
+	},
+	{
+		"quotation-mark", '"'
+	},
+	{
+		"number-sign", '#'
+	},
+	{
+		"dollar-sign", '$'
+	},
+	{
+		"percent-sign", '%'
+	},
+	{
+		"ampersand", '&'
+	},
+	{
+		"apostrophe", '\''
+	},
+	{
+		"left-parenthesis", '('
+	},
+	{
+		"right-parenthesis", ')'
+	},
+	{
+		"asterisk", '*'
+	},
+	{
+		"plus-sign", '+'
+	},
+	{
+		"comma", ','
+	},
+	{
+		"hyphen", '-'
+	},
+	{
+		"hyphen-minus", '-'
+	},
+	{
+		"period", '.'
+	},
+	{
+		"full-stop", '.'
+	},
+	{
+		"slash", '/'
+	},
+	{
+		"solidus", '/'
+	},
+	{
+		"zero", '0'
+	},
+	{
+		"one", '1'
+	},
+	{
+		"two", '2'
+	},
+	{
+		"three", '3'
+	},
+	{
+		"four", '4'
+	},
+	{
+		"five", '5'
+	},
+	{
+		"six", '6'
+	},
+	{
+		"seven", '7'
+	},
+	{
+		"eight", '8'
+	},
+	{
+		"nine", '9'
+	},
+	{
+		"colon", ':'
+	},
+	{
+		"semicolon", ';'
+	},
+	{
+		"less-than-sign", '<'
+	},
+	{
+		"equals-sign", '='
+	},
+	{
+		"greater-than-sign", '>'
+	},
+	{
+		"question-mark", '?'
+	},
+	{
+		"commercial-at", '@'
+	},
+	{
+		"left-square-bracket", '['
+	},
+	{
+		"backslash", '\\'
+	},
+	{
+		"reverse-solidus", '\\'
+	},
+	{
+		"right-square-bracket", ']'
+	},
+	{
+		"circumflex", '^'
+	},
+	{
+		"circumflex-accent", '^'
+	},
+	{
+		"underscore", '_'
+	},
+	{
+		"low-line", '_'
+	},
+	{
+		"grave-accent", '`'
+	},
+	{
+		"left-brace", '{'
+	},
+	{
+		"left-curly-bracket", '{'
+	},
+	{
+		"vertical-line", '|'
+	},
+	{
+		"right-brace", '}'
+	},
+	{
+		"right-curly-bracket", '}'
+	},
+	{
+		"tilde", '~'
+	},
+	{
+		"DEL", '\177'
+	},
+	{
+		NULL, 0
+	}
+};
+
+
+/*
+ * We do not use the hard-wired Unicode classification tables that Tcl does.
+ * This is because (a) we need to deal with other encodings besides Unicode,
+ * and (b) we want to track the behavior of the libc locale routines as
+ * closely as possible.  For example, it wouldn't be unreasonable for a
+ * locale to not consider every Unicode letter as a letter.  So we build
+ * character classification cvecs by asking libc, even for Unicode.
+ */
+
+
+/*
+ * element - map collating-element name to celt
+ */
+static celt
+element(struct vars * v,		/* context */
+		const chr *startp,		/* points to start of name */
+		const chr *endp)		/* points just past end of name */
+{
+	const struct cname *cn;
+	size_t		len;
+
+	/* generic:  one-chr names stand for themselves */
+	assert(startp < endp);
+	len = endp - startp;
+	if (len == 1)
+		return *startp;
+
+	NOTE(REG_ULOCALE);
+
+	/* search table */
+	for (cn = cnames; cn->name != NULL; cn++)
+	{
+		if (strlen(cn->name) == len &&
+			pg_char_and_wchar_strncmp(cn->name, startp, len) == 0)
+		{
+			break;				/* NOTE BREAK OUT */
+		}
+	}
+	if (cn->name != NULL)
+		return CHR(cn->code);
+
+	/* couldn't find it */
+	ERR(REG_ECOLLATE);
+	return 0;
+}
+
+/*
+ * range - supply cvec for a range, including legality check
+ */
+static struct cvec *
+range(struct vars * v,			/* context */
+	  celt a,					/* range start */
+	  celt b,					/* range end, might equal a */
+	  int cases)				/* case-independent? */
+{
+	int			nchrs;
+	struct cvec *cv;
+	celt		c,
+				lc,
+				uc;
+
+	if (a != b && !before(a, b))
+	{
+		ERR(REG_ERANGE);
+		return NULL;
+	}
+
+	if (!cases)
+	{							/* easy version */
+		cv = getcvec(v, 0, 1);
+		NOERRN();
+		addrange(cv, a, b);
+		return cv;
+	}
+
+	/*
+	 * When case-independent, it's hard to decide when cvec ranges are usable,
+	 * so for now at least, we won't try.  We allocate enough space for two
+	 * case variants plus a little extra for the two title case variants.
+	 */
+
+	nchrs = (b - a + 1) * 2 + 4;
+
+	cv = getcvec(v, nchrs, 0);
+	NOERRN();
+
+	for (c = a; c <= b; c++)
+	{
+		addchr(cv, c);
+		lc = pg_wc_tolower((chr) c);
+		if (c != lc)
+			addchr(cv, lc);
+		uc = pg_wc_toupper((chr) c);
+		if (c != uc)
+			addchr(cv, uc);
+	}
+
+	return cv;
+}
+
+/*
+ * before - is celt x before celt y, for purposes of range legality?
+ */
+static int						/* predicate */
+before(celt x, celt y)
+{
+	if (x < y)
+		return 1;
+	return 0;
+}
+
+/*
+ * eclass - supply cvec for an equivalence class
+ * Must include case counterparts on request.
+ */
+static struct cvec *
+eclass(struct vars * v,			/* context */
+	   celt c,					/* Collating element representing the
+								 * equivalence class. */
+	   int cases)				/* all cases? */
+{
+	struct cvec *cv;
+
+	/* crude fake equivalence class for testing */
+	if ((v->cflags & REG_FAKE) && c == 'x')
+	{
+		cv = getcvec(v, 4, 0);
+		addchr(cv, (chr) 'x');
+		addchr(cv, (chr) 'y');
+		if (cases)
+		{
+			addchr(cv, (chr) 'X');
+			addchr(cv, (chr) 'Y');
+		}
+		return cv;
+	}
+
+	/* otherwise, none */
+	if (cases)
+		return allcases(v, c);
+	cv = getcvec(v, 1, 0);
+	assert(cv != NULL);
+	addchr(cv, (chr) c);
+	return cv;
+}
+
+/*
+ * cclass - supply cvec for a character class
+ *
+ * Must include case counterparts if "cases" is true.
+ *
+ * The returned cvec might be either a transient cvec gotten from getcvec(),
+ * or a permanently cached one from pg_ctype_get_cache().  This is okay
+ * because callers are not supposed to explicitly free the result either way.
+ */
+static struct cvec *
+cclass(struct vars * v,			/* context */
+	   const chr *startp,		/* where the name starts */
+	   const chr *endp,			/* just past the end of the name */
+	   int cases)				/* case-independent? */
+{
+	size_t		len;
+	struct cvec *cv = NULL;
+	const char *const * namePtr;
+	int			i,
+				index;
+
+	/*
+	 * The following arrays define the valid character class names.
+	 */
+
+	static const char *const classNames[] = {
+		"alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
+		"lower", "print", "punct", "space", "upper", "xdigit", NULL
+	};
+
+	enum classes
+	{
+		CC_ALNUM, CC_ALPHA, CC_ASCII, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH,
+		CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_XDIGIT
+	};
+
+	/*
+	 * Map the name to the corresponding enumerated value.
+	 */
+	len = endp - startp;
+	index = -1;
+	for (namePtr = classNames, i = 0; *namePtr != NULL; namePtr++, i++)
+	{
+		if (strlen(*namePtr) == len &&
+			pg_char_and_wchar_strncmp(*namePtr, startp, len) == 0)
+		{
+			index = i;
+			break;
+		}
+	}
+	if (index == -1)
+	{
+		ERR(REG_ECTYPE);
+		return NULL;
+	}
+
+	/*
+	 * Remap lower and upper to alpha if the match is case insensitive.
+	 */
+
+	if (cases &&
+		((enum classes) index == CC_LOWER ||
+		 (enum classes) index == CC_UPPER))
+		index = (int) CC_ALPHA;
+
+	/*
+	 * Now compute the character class contents.  For classes that are based
+	 * on the behavior of a <wctype.h> or <ctype.h> function, we use
+	 * pg_ctype_get_cache so that we can cache the results.  Other classes
+	 * have definitions that are hard-wired here, and for those we just
+	 * construct a transient cvec on the fly.
+	 */
+
+	switch ((enum classes) index)
+	{
+		case CC_PRINT:
+			cv = pg_ctype_get_cache(pg_wc_isprint);
+			break;
+		case CC_ALNUM:
+			cv = pg_ctype_get_cache(pg_wc_isalnum);
+			break;
+		case CC_ALPHA:
+			cv = pg_ctype_get_cache(pg_wc_isalpha);
+			break;
+		case CC_ASCII:
+			/* hard-wired meaning */
+			cv = getcvec(v, 0, 1);
+			if (cv)
+				addrange(cv, 0, 0x7f);
+			break;
+		case CC_BLANK:
+			/* hard-wired meaning */
+			cv = getcvec(v, 2, 0);
+			addchr(cv, '\t');
+			addchr(cv, ' ');
+			break;
+		case CC_CNTRL:
+			/* hard-wired meaning */
+			cv = getcvec(v, 0, 2);
+			addrange(cv, 0x0, 0x1f);
+			addrange(cv, 0x7f, 0x9f);
+			break;
+		case CC_DIGIT:
+			cv = pg_ctype_get_cache(pg_wc_isdigit);
+			break;
+		case CC_PUNCT:
+			cv = pg_ctype_get_cache(pg_wc_ispunct);
+			break;
+		case CC_XDIGIT:
+
+			/*
+			 * It's not clear how to define this in non-western locales, and
+			 * even less clear that there's any particular use in trying. So
+			 * just hard-wire the meaning.
+			 */
+			cv = getcvec(v, 0, 3);
+			if (cv)
+			{
+				addrange(cv, '0', '9');
+				addrange(cv, 'a', 'f');
+				addrange(cv, 'A', 'F');
+			}
+			break;
+		case CC_SPACE:
+			cv = pg_ctype_get_cache(pg_wc_isspace);
+			break;
+		case CC_LOWER:
+			cv = pg_ctype_get_cache(pg_wc_islower);
+			break;
+		case CC_UPPER:
+			cv = pg_ctype_get_cache(pg_wc_isupper);
+			break;
+		case CC_GRAPH:
+			cv = pg_ctype_get_cache(pg_wc_isgraph);
+			break;
+	}
+
+	/* If cv is NULL now, the reason must be "out of memory" */
+	if (cv == NULL)
+		ERR(REG_ESPACE);
+	return cv;
+}
+
+/*
+ * allcases - supply cvec for all case counterparts of a chr (including itself)
+ *
+ * This is a shortcut, preferably an efficient one, for simple characters;
+ * messy cases are done via range().
+ */
+static struct cvec *
+allcases(struct vars * v,		/* context */
+		 chr pc)				/* character to get case equivs of */
+{
+	struct cvec *cv;
+	chr			c = (chr) pc;
+	chr			lc,
+				uc;
+
+	lc = pg_wc_tolower((chr) c);
+	uc = pg_wc_toupper((chr) c);
+
+	cv = getcvec(v, 2, 0);
+	addchr(cv, lc);
+	if (lc != uc)
+		addchr(cv, uc);
+	return cv;
+}
+
+/*
+ * cmp - chr-substring compare
+ *
+ * Backrefs need this.  It should preferably be efficient.
+ * Note that it does not need to report anything except equal/unequal.
+ * Note also that the length is exact, and the comparison should not
+ * stop at embedded NULs!
+ */
+static int						/* 0 for equal, nonzero for unequal */
+cmp(const chr *x, const chr *y, /* strings to compare */
+	size_t len)					/* exact length of comparison */
+{
+	return memcmp(VS(x), VS(y), len * sizeof(chr));
+}
+
+/*
+ * casecmp - case-independent chr-substring compare
+ *
+ * REG_ICASE backrefs need this.  It should preferably be efficient.
+ * Note that it does not need to report anything except equal/unequal.
+ * Note also that the length is exact, and the comparison should not
+ * stop at embedded NULs!
+ */
+static int						/* 0 for equal, nonzero for unequal */
+casecmp(const chr *x, const chr *y,		/* strings to compare */
+		size_t len)				/* exact length of comparison */
+{
+	for (; len > 0; len--, x++, y++)
+	{
+		if ((*x != *y) && (pg_wc_tolower(*x) != pg_wc_tolower(*y)))
+			return 1;
+	}
+	return 0;
+}
diff --git a/src/common/regex/regc_nfa.c b/src/common/regex/regc_nfa.c
new file mode 100644
index 0000000..cd9a323
--- /dev/null
+++ b/src/common/regex/regc_nfa.c
@@ -0,0 +1,3181 @@
+/*
+ * NFA utilities.
+ * This file is #included by regcomp.c.
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regc_nfa.c
+ *
+ *
+ * One or two things that technically ought to be in here
+ * are actually in color.c, thanks to some incestuous relationships in
+ * the color chains.
+ */
+
+#define NISERR()	VISERR(nfa->v)
+#define NERR(e)		VERR(nfa->v, (e))
+
+
+/*
+ * newnfa - set up an NFA
+ */
+static struct nfa *				/* the NFA, or NULL */
+newnfa(struct vars * v,
+	   struct colormap * cm,
+	   struct nfa * parent)		/* NULL if primary NFA */
+{
+	struct nfa *nfa;
+
+	nfa = (struct nfa *) MALLOC(sizeof(struct nfa));
+	if (nfa == NULL)
+	{
+		ERR(REG_ESPACE);
+		return NULL;
+	}
+
+	nfa->states = NULL;
+	nfa->slast = NULL;
+	nfa->free = NULL;
+	nfa->nstates = 0;
+	nfa->cm = cm;
+	nfa->v = v;
+	nfa->bos[0] = nfa->bos[1] = COLORLESS;
+	nfa->eos[0] = nfa->eos[1] = COLORLESS;
+	nfa->parent = parent;		/* Precedes newfstate so parent is valid. */
+	nfa->post = newfstate(nfa, '@');	/* number 0 */
+	nfa->pre = newfstate(nfa, '>');		/* number 1 */
+
+	nfa->init = newstate(nfa);	/* may become invalid later */
+	nfa->final = newstate(nfa);
+	if (ISERR())
+	{
+		freenfa(nfa);
+		return NULL;
+	}
+	rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->pre, nfa->init);
+	newarc(nfa, '^', 1, nfa->pre, nfa->init);
+	newarc(nfa, '^', 0, nfa->pre, nfa->init);
+	rainbow(nfa, nfa->cm, PLAIN, COLORLESS, nfa->final, nfa->post);
+	newarc(nfa, '$', 1, nfa->final, nfa->post);
+	newarc(nfa, '$', 0, nfa->final, nfa->post);
+
+	if (ISERR())
+	{
+		freenfa(nfa);
+		return NULL;
+	}
+	return nfa;
+}
+
+/*
+ * freenfa - free an entire NFA
+ */
+static void
+freenfa(struct nfa * nfa)
+{
+	struct state *s;
+
+	while ((s = nfa->states) != NULL)
+	{
+		s->nins = s->nouts = 0; /* don't worry about arcs */
+		freestate(nfa, s);
+	}
+	while ((s = nfa->free) != NULL)
+	{
+		nfa->free = s->next;
+		destroystate(nfa, s);
+	}
+
+	nfa->slast = NULL;
+	nfa->nstates = -1;
+	nfa->pre = NULL;
+	nfa->post = NULL;
+	FREE(nfa);
+}
+
+/*
+ * newstate - allocate an NFA state, with zero flag value
+ */
+static struct state *			/* NULL on error */
+newstate(struct nfa * nfa)
+{
+	struct state *s;
+
+	/*
+	 * This is a handy place to check for operation cancel during regex
+	 * compilation, since no code path will go very long without making a new
+	 * state or arc.
+	 */
+	if (CANCEL_REQUESTED(nfa->v->re))
+	{
+		NERR(REG_CANCEL);
+		return NULL;
+	}
+
+	if (nfa->free != NULL)
+	{
+		s = nfa->free;
+		nfa->free = s->next;
+	}
+	else
+	{
+		if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE)
+		{
+			NERR(REG_ETOOBIG);
+			return NULL;
+		}
+		s = (struct state *) MALLOC(sizeof(struct state));
+		if (s == NULL)
+		{
+			NERR(REG_ESPACE);
+			return NULL;
+		}
+		nfa->v->spaceused += sizeof(struct state);
+		s->oas.next = NULL;
+		s->free = NULL;
+		s->noas = 0;
+	}
+
+	assert(nfa->nstates >= 0);
+	s->no = nfa->nstates++;
+	s->flag = 0;
+	if (nfa->states == NULL)
+		nfa->states = s;
+	s->nins = 0;
+	s->ins = NULL;
+	s->nouts = 0;
+	s->outs = NULL;
+	s->tmp = NULL;
+	s->next = NULL;
+	if (nfa->slast != NULL)
+	{
+		assert(nfa->slast->next == NULL);
+		nfa->slast->next = s;
+	}
+	s->prev = nfa->slast;
+	nfa->slast = s;
+	return s;
+}
+
+/*
+ * newfstate - allocate an NFA state with a specified flag value
+ */
+static struct state *			/* NULL on error */
+newfstate(struct nfa * nfa, int flag)
+{
+	struct state *s;
+
+	s = newstate(nfa);
+	if (s != NULL)
+		s->flag = (char) flag;
+	return s;
+}
+
+/*
+ * dropstate - delete a state's inarcs and outarcs and free it
+ */
+static void
+dropstate(struct nfa * nfa,
+		  struct state * s)
+{
+	struct arc *a;
+
+	while ((a = s->ins) != NULL)
+		freearc(nfa, a);
+	while ((a = s->outs) != NULL)
+		freearc(nfa, a);
+	freestate(nfa, s);
+}
+
+/*
+ * freestate - free a state, which has no in-arcs or out-arcs
+ */
+static void
+freestate(struct nfa * nfa,
+		  struct state * s)
+{
+	assert(s != NULL);
+	assert(s->nins == 0 && s->nouts == 0);
+
+	s->no = FREESTATE;
+	s->flag = 0;
+	if (s->next != NULL)
+		s->next->prev = s->prev;
+	else
+	{
+		assert(s == nfa->slast);
+		nfa->slast = s->prev;
+	}
+	if (s->prev != NULL)
+		s->prev->next = s->next;
+	else
+	{
+		assert(s == nfa->states);
+		nfa->states = s->next;
+	}
+	s->prev = NULL;
+	s->next = nfa->free;		/* don't delete it, put it on the free list */
+	nfa->free = s;
+}
+
+/*
+ * destroystate - really get rid of an already-freed state
+ */
+static void
+destroystate(struct nfa * nfa,
+			 struct state * s)
+{
+	struct arcbatch *ab;
+	struct arcbatch *abnext;
+
+	assert(s->no == FREESTATE);
+	for (ab = s->oas.next; ab != NULL; ab = abnext)
+	{
+		abnext = ab->next;
+		FREE(ab);
+		nfa->v->spaceused -= sizeof(struct arcbatch);
+	}
+	s->ins = NULL;
+	s->outs = NULL;
+	s->next = NULL;
+	FREE(s);
+	nfa->v->spaceused -= sizeof(struct state);
+}
+
+/*
+ * newarc - set up a new arc within an NFA
+ *
+ * This function checks to make sure that no duplicate arcs are created.
+ * In general we never want duplicates.
+ */
+static void
+newarc(struct nfa * nfa,
+	   int t,
+	   pcolor co,
+	   struct state * from,
+	   struct state * to)
+{
+	struct arc *a;
+
+	assert(from != NULL && to != NULL);
+
+	/*
+	 * This is a handy place to check for operation cancel during regex
+	 * compilation, since no code path will go very long without making a new
+	 * state or arc.
+	 */
+	if (CANCEL_REQUESTED(nfa->v->re))
+	{
+		NERR(REG_CANCEL);
+		return;
+	}
+
+	/* check for duplicate arc, using whichever chain is shorter */
+	if (from->nouts <= to->nins)
+	{
+		for (a = from->outs; a != NULL; a = a->outchain)
+			if (a->to == to && a->co == co && a->type == t)
+				return;
+	}
+	else
+	{
+		for (a = to->ins; a != NULL; a = a->inchain)
+			if (a->from == from && a->co == co && a->type == t)
+				return;
+	}
+
+	/* no dup, so create the arc */
+	createarc(nfa, t, co, from, to);
+}
+
+/*
+ * createarc - create a new arc within an NFA
+ *
+ * This function must *only* be used after verifying that there is no existing
+ * identical arc (same type/color/from/to).
+ */
+static void
+createarc(struct nfa * nfa,
+		  int t,
+		  pcolor co,
+		  struct state * from,
+		  struct state * to)
+{
+	struct arc *a;
+
+	/* the arc is physically allocated within its from-state */
+	a = allocarc(nfa, from);
+	if (NISERR())
+		return;
+	assert(a != NULL);
+
+	a->type = t;
+	a->co = (color) co;
+	a->to = to;
+	a->from = from;
+
+	/*
+	 * Put the new arc on the beginning, not the end, of the chains; it's
+	 * simpler here, and freearc() is the same cost either way.  See also the
+	 * logic in moveins() and its cohorts, as well as fixempties().
+	 */
+	a->inchain = to->ins;
+	a->inchainRev = NULL;
+	if (to->ins)
+		to->ins->inchainRev = a;
+	to->ins = a;
+	a->outchain = from->outs;
+	a->outchainRev = NULL;
+	if (from->outs)
+		from->outs->outchainRev = a;
+	from->outs = a;
+
+	from->nouts++;
+	to->nins++;
+
+	if (COLORED(a) && nfa->parent == NULL)
+		colorchain(nfa->cm, a);
+}
+
+/*
+ * allocarc - allocate a new out-arc within a state
+ */
+static struct arc *				/* NULL for failure */
+allocarc(struct nfa * nfa,
+		 struct state * s)
+{
+	struct arc *a;
+
+	/* shortcut */
+	if (s->free == NULL && s->noas < ABSIZE)
+	{
+		a = &s->oas.a[s->noas];
+		s->noas++;
+		return a;
+	}
+
+	/* if none at hand, get more */
+	if (s->free == NULL)
+	{
+		struct arcbatch *newAb;
+		int			i;
+
+		if (nfa->v->spaceused >= REG_MAX_COMPILE_SPACE)
+		{
+			NERR(REG_ETOOBIG);
+			return NULL;
+		}
+		newAb = (struct arcbatch *) MALLOC(sizeof(struct arcbatch));
+		if (newAb == NULL)
+		{
+			NERR(REG_ESPACE);
+			return NULL;
+		}
+		nfa->v->spaceused += sizeof(struct arcbatch);
+		newAb->next = s->oas.next;
+		s->oas.next = newAb;
+
+		for (i = 0; i < ABSIZE; i++)
+		{
+			newAb->a[i].type = 0;
+			newAb->a[i].freechain = &newAb->a[i + 1];
+		}
+		newAb->a[ABSIZE - 1].freechain = NULL;
+		s->free = &newAb->a[0];
+	}
+	assert(s->free != NULL);
+
+	a = s->free;
+	s->free = a->freechain;
+	return a;
+}
+
+/*
+ * freearc - free an arc
+ */
+static void
+freearc(struct nfa * nfa,
+		struct arc * victim)
+{
+	struct state *from = victim->from;
+	struct state *to = victim->to;
+	struct arc *predecessor;
+
+	assert(victim->type != 0);
+
+	/* take it off color chain if necessary */
+	if (COLORED(victim) && nfa->parent == NULL)
+		uncolorchain(nfa->cm, victim);
+
+	/* take it off source's out-chain */
+	assert(from != NULL);
+	predecessor = victim->outchainRev;
+	if (predecessor == NULL)
+	{
+		assert(from->outs == victim);
+		from->outs = victim->outchain;
+	}
+	else
+	{
+		assert(predecessor->outchain == victim);
+		predecessor->outchain = victim->outchain;
+	}
+	if (victim->outchain != NULL)
+	{
+		assert(victim->outchain->outchainRev == victim);
+		victim->outchain->outchainRev = predecessor;
+	}
+	from->nouts--;
+
+	/* take it off target's in-chain */
+	assert(to != NULL);
+	predecessor = victim->inchainRev;
+	if (predecessor == NULL)
+	{
+		assert(to->ins == victim);
+		to->ins = victim->inchain;
+	}
+	else
+	{
+		assert(predecessor->inchain == victim);
+		predecessor->inchain = victim->inchain;
+	}
+	if (victim->inchain != NULL)
+	{
+		assert(victim->inchain->inchainRev == victim);
+		victim->inchain->inchainRev = predecessor;
+	}
+	to->nins--;
+
+	/* clean up and place on from-state's free list */
+	victim->type = 0;
+	victim->from = NULL;		/* precautions... */
+	victim->to = NULL;
+	victim->inchain = NULL;
+	victim->inchainRev = NULL;
+	victim->outchain = NULL;
+	victim->outchainRev = NULL;
+	victim->freechain = from->free;
+	from->free = victim;
+}
+
+/*
+ * changearctarget - flip an arc to have a different to state
+ *
+ * Caller must have verified that there is no pre-existing duplicate arc.
+ *
+ * Note that because we store arcs in their from state, we can't easily have
+ * a similar changearcsource function.
+ */
+static void
+changearctarget(struct arc * a, struct state * newto)
+{
+	struct state *oldto = a->to;
+	struct arc *predecessor;
+
+	assert(oldto != newto);
+
+	/* take it off old target's in-chain */
+	assert(oldto != NULL);
+	predecessor = a->inchainRev;
+	if (predecessor == NULL)
+	{
+		assert(oldto->ins == a);
+		oldto->ins = a->inchain;
+	}
+	else
+	{
+		assert(predecessor->inchain == a);
+		predecessor->inchain = a->inchain;
+	}
+	if (a->inchain != NULL)
+	{
+		assert(a->inchain->inchainRev == a);
+		a->inchain->inchainRev = predecessor;
+	}
+	oldto->nins--;
+
+	a->to = newto;
+
+	/* prepend it to new target's in-chain */
+	a->inchain = newto->ins;
+	a->inchainRev = NULL;
+	if (newto->ins)
+		newto->ins->inchainRev = a;
+	newto->ins = a;
+	newto->nins++;
+}
+
+/*
+ * hasnonemptyout - Does state have a non-EMPTY out arc?
+ */
+static int
+hasnonemptyout(struct state * s)
+{
+	struct arc *a;
+
+	for (a = s->outs; a != NULL; a = a->outchain)
+	{
+		if (a->type != EMPTY)
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * findarc - find arc, if any, from given source with given type and color
+ * If there is more than one such arc, the result is random.
+ */
+static struct arc *
+findarc(struct state * s,
+		int type,
+		pcolor co)
+{
+	struct arc *a;
+
+	for (a = s->outs; a != NULL; a = a->outchain)
+		if (a->type == type && a->co == co)
+			return a;
+	return NULL;
+}
+
+/*
+ * cparc - allocate a new arc within an NFA, copying details from old one
+ */
+static void
+cparc(struct nfa * nfa,
+	  struct arc * oa,
+	  struct state * from,
+	  struct state * to)
+{
+	newarc(nfa, oa->type, oa->co, from, to);
+}
+
+/*
+ * sortins - sort the in arcs of a state by from/color/type
+ */
+static void
+sortins(struct nfa * nfa,
+		struct state * s)
+{
+	struct arc **sortarray;
+	struct arc *a;
+	int			n = s->nins;
+	int			i;
+
+	if (n <= 1)
+		return;					/* nothing to do */
+	/* make an array of arc pointers ... */
+	sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *));
+	if (sortarray == NULL)
+	{
+		NERR(REG_ESPACE);
+		return;
+	}
+	i = 0;
+	for (a = s->ins; a != NULL; a = a->inchain)
+		sortarray[i++] = a;
+	assert(i == n);
+	/* ... sort the array */
+	qsort(sortarray, n, sizeof(struct arc *), sortins_cmp);
+	/* ... and rebuild arc list in order */
+	/* it seems worth special-casing first and last items to simplify loop */
+	a = sortarray[0];
+	s->ins = a;
+	a->inchain = sortarray[1];
+	a->inchainRev = NULL;
+	for (i = 1; i < n - 1; i++)
+	{
+		a = sortarray[i];
+		a->inchain = sortarray[i + 1];
+		a->inchainRev = sortarray[i - 1];
+	}
+	a = sortarray[i];
+	a->inchain = NULL;
+	a->inchainRev = sortarray[i - 1];
+	FREE(sortarray);
+}
+
+static int
+sortins_cmp(const void *a, const void *b)
+{
+	const struct arc *aa = *((const struct arc * const *) a);
+	const struct arc *bb = *((const struct arc * const *) b);
+
+	/* we check the fields in the order they are most likely to be different */
+	if (aa->from->no < bb->from->no)
+		return -1;
+	if (aa->from->no > bb->from->no)
+		return 1;
+	if (aa->co < bb->co)
+		return -1;
+	if (aa->co > bb->co)
+		return 1;
+	if (aa->type < bb->type)
+		return -1;
+	if (aa->type > bb->type)
+		return 1;
+	return 0;
+}
+
+/*
+ * sortouts - sort the out arcs of a state by to/color/type
+ */
+static void
+sortouts(struct nfa * nfa,
+		 struct state * s)
+{
+	struct arc **sortarray;
+	struct arc *a;
+	int			n = s->nouts;
+	int			i;
+
+	if (n <= 1)
+		return;					/* nothing to do */
+	/* make an array of arc pointers ... */
+	sortarray = (struct arc **) MALLOC(n * sizeof(struct arc *));
+	if (sortarray == NULL)
+	{
+		NERR(REG_ESPACE);
+		return;
+	}
+	i = 0;
+	for (a = s->outs; a != NULL; a = a->outchain)
+		sortarray[i++] = a;
+	assert(i == n);
+	/* ... sort the array */
+	qsort(sortarray, n, sizeof(struct arc *), sortouts_cmp);
+	/* ... and rebuild arc list in order */
+	/* it seems worth special-casing first and last items to simplify loop */
+	a = sortarray[0];
+	s->outs = a;
+	a->outchain = sortarray[1];
+	a->outchainRev = NULL;
+	for (i = 1; i < n - 1; i++)
+	{
+		a = sortarray[i];
+		a->outchain = sortarray[i + 1];
+		a->outchainRev = sortarray[i - 1];
+	}
+	a = sortarray[i];
+	a->outchain = NULL;
+	a->outchainRev = sortarray[i - 1];
+	FREE(sortarray);
+}
+
+static int
+sortouts_cmp(const void *a, const void *b)
+{
+	const struct arc *aa = *((const struct arc * const *) a);
+	const struct arc *bb = *((const struct arc * const *) b);
+
+	/* we check the fields in the order they are most likely to be different */
+	if (aa->to->no < bb->to->no)
+		return -1;
+	if (aa->to->no > bb->to->no)
+		return 1;
+	if (aa->co < bb->co)
+		return -1;
+	if (aa->co > bb->co)
+		return 1;
+	if (aa->type < bb->type)
+		return -1;
+	if (aa->type > bb->type)
+		return 1;
+	return 0;
+}
+
+/*
+ * Common decision logic about whether to use arc-by-arc operations or
+ * sort/merge.  If there's just a few source arcs we cannot recoup the
+ * cost of sorting the destination arc list, no matter how large it is.
+ * Otherwise, limit the number of arc-by-arc comparisons to about 1000
+ * (a somewhat arbitrary choice, but the breakeven point would probably
+ * be machine dependent anyway).
+ */
+#define BULK_ARC_OP_USE_SORT(nsrcarcs, ndestarcs) \
+	((nsrcarcs) < 4 ? 0 : ((nsrcarcs) > 32 || (ndestarcs) > 32))
+
+/*
+ * moveins - move all in arcs of a state to another state
+ *
+ * You might think this could be done better by just updating the
+ * existing arcs, and you would be right if it weren't for the need
+ * for duplicate suppression, which makes it easier to just make new
+ * ones to exploit the suppression built into newarc.
+ *
+ * However, if we have a whole lot of arcs to deal with, retail duplicate
+ * checks become too slow.  In that case we proceed by sorting and merging
+ * the arc lists, and then we can indeed just update the arcs in-place.
+ */
+static void
+moveins(struct nfa * nfa,
+		struct state * oldState,
+		struct state * newState)
+{
+	assert(oldState != newState);
+
+	if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins))
+	{
+		/* With not too many arcs, just do them one at a time */
+		struct arc *a;
+
+		while ((a = oldState->ins) != NULL)
+		{
+			cparc(nfa, a, a->from, newState);
+			freearc(nfa, a);
+		}
+	}
+	else
+	{
+		/*
+		 * With many arcs, use a sort-merge approach.  Note changearctarget()
+		 * will put the arc onto the front of newState's chain, so it does not
+		 * break our walk through the sorted part of the chain.
+		 */
+		struct arc *oa;
+		struct arc *na;
+
+		/*
+		 * Because we bypass newarc() in this code path, we'd better include a
+		 * cancel check.
+		 */
+		if (CANCEL_REQUESTED(nfa->v->re))
+		{
+			NERR(REG_CANCEL);
+			return;
+		}
+
+		sortins(nfa, oldState);
+		sortins(nfa, newState);
+		if (NISERR())
+			return;				/* might have failed to sort */
+		oa = oldState->ins;
+		na = newState->ins;
+		while (oa != NULL && na != NULL)
+		{
+			struct arc *a = oa;
+
+			switch (sortins_cmp(&oa, &na))
+			{
+				case -1:
+					/* newState does not have anything matching oa */
+					oa = oa->inchain;
+
+					/*
+					 * Rather than doing createarc+freearc, we can just unlink
+					 * and relink the existing arc struct.
+					 */
+					changearctarget(a, newState);
+					break;
+				case 0:
+					/* match, advance in both lists */
+					oa = oa->inchain;
+					na = na->inchain;
+					/* ... and drop duplicate arc from oldState */
+					freearc(nfa, a);
+					break;
+				case +1:
+					/* advance only na; oa might have a match later */
+					na = na->inchain;
+					break;
+				default:
+					assert(NOTREACHED);
+			}
+		}
+		while (oa != NULL)
+		{
+			/* newState does not have anything matching oa */
+			struct arc *a = oa;
+
+			oa = oa->inchain;
+			changearctarget(a, newState);
+		}
+	}
+
+	assert(oldState->nins == 0);
+	assert(oldState->ins == NULL);
+}
+
+/*
+ * copyins - copy in arcs of a state to another state
+ */
+static void
+copyins(struct nfa * nfa,
+		struct state * oldState,
+		struct state * newState)
+{
+	assert(oldState != newState);
+
+	if (!BULK_ARC_OP_USE_SORT(oldState->nins, newState->nins))
+	{
+		/* With not too many arcs, just do them one at a time */
+		struct arc *a;
+
+		for (a = oldState->ins; a != NULL; a = a->inchain)
+			cparc(nfa, a, a->from, newState);
+	}
+	else
+	{
+		/*
+		 * With many arcs, use a sort-merge approach.  Note that createarc()
+		 * will put new arcs onto the front of newState's chain, so it does
+		 * not break our walk through the sorted part of the chain.
+		 */
+		struct arc *oa;
+		struct arc *na;
+
+		/*
+		 * Because we bypass newarc() in this code path, we'd better include a
+		 * cancel check.
+		 */
+		if (CANCEL_REQUESTED(nfa->v->re))
+		{
+			NERR(REG_CANCEL);
+			return;
+		}
+
+		sortins(nfa, oldState);
+		sortins(nfa, newState);
+		if (NISERR())
+			return;				/* might have failed to sort */
+		oa = oldState->ins;
+		na = newState->ins;
+		while (oa != NULL && na != NULL)
+		{
+			struct arc *a = oa;
+
+			switch (sortins_cmp(&oa, &na))
+			{
+				case -1:
+					/* newState does not have anything matching oa */
+					oa = oa->inchain;
+					createarc(nfa, a->type, a->co, a->from, newState);
+					break;
+				case 0:
+					/* match, advance in both lists */
+					oa = oa->inchain;
+					na = na->inchain;
+					break;
+				case +1:
+					/* advance only na; oa might have a match later */
+					na = na->inchain;
+					break;
+				default:
+					assert(NOTREACHED);
+			}
+		}
+		while (oa != NULL)
+		{
+			/* newState does not have anything matching oa */
+			struct arc *a = oa;
+
+			oa = oa->inchain;
+			createarc(nfa, a->type, a->co, a->from, newState);
+		}
+	}
+}
+
+/*
+ * mergeins - merge a list of inarcs into a state
+ *
+ * This is much like copyins, but the source arcs are listed in an array,
+ * and are not guaranteed unique.  It's okay to clobber the array contents.
+ */
+static void
+mergeins(struct nfa * nfa,
+		 struct state * s,
+		 struct arc ** arcarray,
+		 int arccount)
+{
+	struct arc *na;
+	int			i;
+	int			j;
+
+	if (arccount <= 0)
+		return;
+
+	/*
+	 * Because we bypass newarc() in this code path, we'd better include a
+	 * cancel check.
+	 */
+	if (CANCEL_REQUESTED(nfa->v->re))
+	{
+		NERR(REG_CANCEL);
+		return;
+	}
+
+	/* Sort existing inarcs as well as proposed new ones */
+	sortins(nfa, s);
+	if (NISERR())
+		return;					/* might have failed to sort */
+
+	qsort(arcarray, arccount, sizeof(struct arc *), sortins_cmp);
+
+	/*
+	 * arcarray very likely includes dups, so we must eliminate them.  (This
+	 * could be folded into the next loop, but it's not worth the trouble.)
+	 */
+	j = 0;
+	for (i = 1; i < arccount; i++)
+	{
+		switch (sortins_cmp(&arcarray[j], &arcarray[i]))
+		{
+			case -1:
+				/* non-dup */
+				arcarray[++j] = arcarray[i];
+				break;
+			case 0:
+				/* dup */
+				break;
+			default:
+				/* trouble */
+				assert(NOTREACHED);
+		}
+	}
+	arccount = j + 1;
+
+	/*
+	 * Now merge into s' inchain.  Note that createarc() will put new arcs
+	 * onto the front of s's chain, so it does not break our walk through the
+	 * sorted part of the chain.
+	 */
+	i = 0;
+	na = s->ins;
+	while (i < arccount && na != NULL)
+	{
+		struct arc *a = arcarray[i];
+
+		switch (sortins_cmp(&a, &na))
+		{
+			case -1:
+				/* s does not have anything matching a */
+				createarc(nfa, a->type, a->co, a->from, s);
+				i++;
+				break;
+			case 0:
+				/* match, advance in both lists */
+				i++;
+				na = na->inchain;
+				break;
+			case +1:
+				/* advance only na; array might have a match later */
+				na = na->inchain;
+				break;
+			default:
+				assert(NOTREACHED);
+		}
+	}
+	while (i < arccount)
+	{
+		/* s does not have anything matching a */
+		struct arc *a = arcarray[i];
+
+		createarc(nfa, a->type, a->co, a->from, s);
+		i++;
+	}
+}
+
+/*
+ * moveouts - move all out arcs of a state to another state
+ */
+static void
+moveouts(struct nfa * nfa,
+		 struct state * oldState,
+		 struct state * newState)
+{
+	assert(oldState != newState);
+
+	if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts))
+	{
+		/* With not too many arcs, just do them one at a time */
+		struct arc *a;
+
+		while ((a = oldState->outs) != NULL)
+		{
+			cparc(nfa, a, newState, a->to);
+			freearc(nfa, a);
+		}
+	}
+	else
+	{
+		/*
+		 * With many arcs, use a sort-merge approach.  Note that createarc()
+		 * will put new arcs onto the front of newState's chain, so it does
+		 * not break our walk through the sorted part of the chain.
+		 */
+		struct arc *oa;
+		struct arc *na;
+
+		/*
+		 * Because we bypass newarc() in this code path, we'd better include a
+		 * cancel check.
+		 */
+		if (CANCEL_REQUESTED(nfa->v->re))
+		{
+			NERR(REG_CANCEL);
+			return;
+		}
+
+		sortouts(nfa, oldState);
+		sortouts(nfa, newState);
+		if (NISERR())
+			return;				/* might have failed to sort */
+		oa = oldState->outs;
+		na = newState->outs;
+		while (oa != NULL && na != NULL)
+		{
+			struct arc *a = oa;
+
+			switch (sortouts_cmp(&oa, &na))
+			{
+				case -1:
+					/* newState does not have anything matching oa */
+					oa = oa->outchain;
+					createarc(nfa, a->type, a->co, newState, a->to);
+					freearc(nfa, a);
+					break;
+				case 0:
+					/* match, advance in both lists */
+					oa = oa->outchain;
+					na = na->outchain;
+					/* ... and drop duplicate arc from oldState */
+					freearc(nfa, a);
+					break;
+				case +1:
+					/* advance only na; oa might have a match later */
+					na = na->outchain;
+					break;
+				default:
+					assert(NOTREACHED);
+			}
+		}
+		while (oa != NULL)
+		{
+			/* newState does not have anything matching oa */
+			struct arc *a = oa;
+
+			oa = oa->outchain;
+			createarc(nfa, a->type, a->co, newState, a->to);
+			freearc(nfa, a);
+		}
+	}
+
+	assert(oldState->nouts == 0);
+	assert(oldState->outs == NULL);
+}
+
+/*
+ * copyouts - copy out arcs of a state to another state
+ */
+static void
+copyouts(struct nfa * nfa,
+		 struct state * oldState,
+		 struct state * newState)
+{
+	assert(oldState != newState);
+
+	if (!BULK_ARC_OP_USE_SORT(oldState->nouts, newState->nouts))
+	{
+		/* With not too many arcs, just do them one at a time */
+		struct arc *a;
+
+		for (a = oldState->outs; a != NULL; a = a->outchain)
+			cparc(nfa, a, newState, a->to);
+	}
+	else
+	{
+		/*
+		 * With many arcs, use a sort-merge approach.  Note that createarc()
+		 * will put new arcs onto the front of newState's chain, so it does
+		 * not break our walk through the sorted part of the chain.
+		 */
+		struct arc *oa;
+		struct arc *na;
+
+		/*
+		 * Because we bypass newarc() in this code path, we'd better include a
+		 * cancel check.
+		 */
+		if (CANCEL_REQUESTED(nfa->v->re))
+		{
+			NERR(REG_CANCEL);
+			return;
+		}
+
+		sortouts(nfa, oldState);
+		sortouts(nfa, newState);
+		if (NISERR())
+			return;				/* might have failed to sort */
+		oa = oldState->outs;
+		na = newState->outs;
+		while (oa != NULL && na != NULL)
+		{
+			struct arc *a = oa;
+
+			switch (sortouts_cmp(&oa, &na))
+			{
+				case -1:
+					/* newState does not have anything matching oa */
+					oa = oa->outchain;
+					createarc(nfa, a->type, a->co, newState, a->to);
+					break;
+				case 0:
+					/* match, advance in both lists */
+					oa = oa->outchain;
+					na = na->outchain;
+					break;
+				case +1:
+					/* advance only na; oa might have a match later */
+					na = na->outchain;
+					break;
+				default:
+					assert(NOTREACHED);
+			}
+		}
+		while (oa != NULL)
+		{
+			/* newState does not have anything matching oa */
+			struct arc *a = oa;
+
+			oa = oa->outchain;
+			createarc(nfa, a->type, a->co, newState, a->to);
+		}
+	}
+}
+
+/*
+ * cloneouts - copy out arcs of a state to another state pair, modifying type
+ */
+static void
+cloneouts(struct nfa * nfa,
+		  struct state * old,
+		  struct state * from,
+		  struct state * to,
+		  int type)
+{
+	struct arc *a;
+
+	assert(old != from);
+
+	for (a = old->outs; a != NULL; a = a->outchain)
+		newarc(nfa, type, a->co, from, to);
+}
+
+/*
+ * delsub - delete a sub-NFA, updating subre pointers if necessary
+ *
+ * This uses a recursive traversal of the sub-NFA, marking already-seen
+ * states using their tmp pointer.
+ */
+static void
+delsub(struct nfa * nfa,
+	   struct state * lp,		/* the sub-NFA goes from here... */
+	   struct state * rp)		/* ...to here, *not* inclusive */
+{
+	assert(lp != rp);
+
+	rp->tmp = rp;				/* mark end */
+
+	deltraverse(nfa, lp, lp);
+	if (NISERR())
+		return;					/* asserts might not hold after failure */
+	assert(lp->nouts == 0 && rp->nins == 0);	/* did the job */
+	assert(lp->no != FREESTATE && rp->no != FREESTATE); /* no more */
+
+	rp->tmp = NULL;				/* unmark end */
+	lp->tmp = NULL;				/* and begin, marked by deltraverse */
+}
+
+/*
+ * deltraverse - the recursive heart of delsub
+ * This routine's basic job is to destroy all out-arcs of the state.
+ */
+static void
+deltraverse(struct nfa * nfa,
+			struct state * leftend,
+			struct state * s)
+{
+	struct arc *a;
+	struct state *to;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	if (s->nouts == 0)
+		return;					/* nothing to do */
+	if (s->tmp != NULL)
+		return;					/* already in progress */
+
+	s->tmp = s;					/* mark as in progress */
+
+	while ((a = s->outs) != NULL)
+	{
+		to = a->to;
+		deltraverse(nfa, leftend, to);
+		if (NISERR())
+			return;				/* asserts might not hold after failure */
+		assert(to->nouts == 0 || to->tmp != NULL);
+		freearc(nfa, a);
+		if (to->nins == 0 && to->tmp == NULL)
+		{
+			assert(to->nouts == 0);
+			freestate(nfa, to);
+		}
+	}
+
+	assert(s->no != FREESTATE); /* we're still here */
+	assert(s == leftend || s->nins != 0);		/* and still reachable */
+	assert(s->nouts == 0);		/* but have no outarcs */
+
+	s->tmp = NULL;				/* we're done here */
+}
+
+/*
+ * dupnfa - duplicate sub-NFA
+ *
+ * Another recursive traversal, this time using tmp to point to duplicates
+ * as well as mark already-seen states.  (You knew there was a reason why
+ * it's a state pointer, didn't you? :-))
+ */
+static void
+dupnfa(struct nfa * nfa,
+	   struct state * start,	/* duplicate of subNFA starting here */
+	   struct state * stop,		/* and stopping here */
+	   struct state * from,		/* stringing duplicate from here */
+	   struct state * to)		/* to here */
+{
+	if (start == stop)
+	{
+		newarc(nfa, EMPTY, 0, from, to);
+		return;
+	}
+
+	stop->tmp = to;
+	duptraverse(nfa, start, from);
+	/* done, except for clearing out the tmp pointers */
+
+	stop->tmp = NULL;
+	cleartraverse(nfa, start);
+}
+
+/*
+ * duptraverse - recursive heart of dupnfa
+ */
+static void
+duptraverse(struct nfa * nfa,
+			struct state * s,
+			struct state * stmp)	/* s's duplicate, or NULL */
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	if (s->tmp != NULL)
+		return;					/* already done */
+
+	s->tmp = (stmp == NULL) ? newstate(nfa) : stmp;
+	if (s->tmp == NULL)
+	{
+		assert(NISERR());
+		return;
+	}
+
+	for (a = s->outs; a != NULL && !NISERR(); a = a->outchain)
+	{
+		duptraverse(nfa, a->to, (struct state *) NULL);
+		if (NISERR())
+			break;
+		assert(a->to->tmp != NULL);
+		cparc(nfa, a, s->tmp, a->to->tmp);
+	}
+}
+
+/*
+ * cleartraverse - recursive cleanup for algorithms that leave tmp ptrs set
+ */
+static void
+cleartraverse(struct nfa * nfa,
+			  struct state * s)
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	if (s->tmp == NULL)
+		return;
+	s->tmp = NULL;
+
+	for (a = s->outs; a != NULL; a = a->outchain)
+		cleartraverse(nfa, a->to);
+}
+
+/*
+ * single_color_transition - does getting from s1 to s2 cross one PLAIN arc?
+ *
+ * If traversing from s1 to s2 requires a single PLAIN match (possibly of any
+ * of a set of colors), return a state whose outarc list contains only PLAIN
+ * arcs of those color(s).  Otherwise return NULL.
+ *
+ * This is used before optimizing the NFA, so there may be EMPTY arcs, which
+ * we should ignore; the possibility of an EMPTY is why the result state could
+ * be different from s1.
+ *
+ * It's worth troubling to handle multiple parallel PLAIN arcs here because a
+ * bracket construct such as [abc] might yield either one or several parallel
+ * PLAIN arcs depending on earlier atoms in the expression.  We'd rather that
+ * that implementation detail not create user-visible performance differences.
+ */
+static struct state *
+single_color_transition(struct state * s1, struct state * s2)
+{
+	struct arc *a;
+
+	/* Ignore leading EMPTY arc, if any */
+	if (s1->nouts == 1 && s1->outs->type == EMPTY)
+		s1 = s1->outs->to;
+	/* Likewise for any trailing EMPTY arc */
+	if (s2->nins == 1 && s2->ins->type == EMPTY)
+		s2 = s2->ins->from;
+	/* Perhaps we could have a single-state loop in between, if so reject */
+	if (s1 == s2)
+		return NULL;
+	/* s1 must have at least one outarc... */
+	if (s1->outs == NULL)
+		return NULL;
+	/* ... and they must all be PLAIN arcs to s2 */
+	for (a = s1->outs; a != NULL; a = a->outchain)
+	{
+		if (a->type != PLAIN || a->to != s2)
+			return NULL;
+	}
+	/* OK, return s1 as the possessor of the relevant outarcs */
+	return s1;
+}
+
+/*
+ * specialcolors - fill in special colors for an NFA
+ */
+static void
+specialcolors(struct nfa * nfa)
+{
+	/* false colors for BOS, BOL, EOS, EOL */
+	if (nfa->parent == NULL)
+	{
+		nfa->bos[0] = pseudocolor(nfa->cm);
+		nfa->bos[1] = pseudocolor(nfa->cm);
+		nfa->eos[0] = pseudocolor(nfa->cm);
+		nfa->eos[1] = pseudocolor(nfa->cm);
+	}
+	else
+	{
+		assert(nfa->parent->bos[0] != COLORLESS);
+		nfa->bos[0] = nfa->parent->bos[0];
+		assert(nfa->parent->bos[1] != COLORLESS);
+		nfa->bos[1] = nfa->parent->bos[1];
+		assert(nfa->parent->eos[0] != COLORLESS);
+		nfa->eos[0] = nfa->parent->eos[0];
+		assert(nfa->parent->eos[1] != COLORLESS);
+		nfa->eos[1] = nfa->parent->eos[1];
+	}
+}
+
+/*
+ * optimize - optimize an NFA
+ *
+ * The main goal of this function is not so much "optimization" (though it
+ * does try to get rid of useless NFA states) as reducing the NFA to a form
+ * the regex executor can handle.  The executor, and indeed the cNFA format
+ * that is its input, can only handle PLAIN and LACON arcs.  The output of
+ * the regex parser also includes EMPTY (do-nothing) arcs, as well as
+ * ^, $, AHEAD, and BEHIND constraint arcs, which we must get rid of here.
+ * We first get rid of EMPTY arcs and then deal with the constraint arcs.
+ * The hardest part of either job is to get rid of circular loops of the
+ * target arc type.  We would have to do that in any case, though, as such a
+ * loop would otherwise allow the executor to cycle through the loop endlessly
+ * without making any progress in the input string.
+ */
+static long						/* re_info bits */
+optimize(struct nfa * nfa,
+		 FILE *f)				/* for debug output; NULL none */
+{
+#ifdef REG_DEBUG
+	int			verbose = (f != NULL) ? 1 : 0;
+
+	if (verbose)
+		fprintf(f, "\ninitial cleanup:\n");
+#endif
+	cleanup(nfa);				/* may simplify situation */
+#ifdef REG_DEBUG
+	if (verbose)
+		dumpnfa(nfa, f);
+	if (verbose)
+		fprintf(f, "\nempties:\n");
+#endif
+	fixempties(nfa, f);			/* get rid of EMPTY arcs */
+#ifdef REG_DEBUG
+	if (verbose)
+		fprintf(f, "\nconstraints:\n");
+#endif
+	fixconstraintloops(nfa, f); /* get rid of constraint loops */
+	pullback(nfa, f);			/* pull back constraints backward */
+	pushfwd(nfa, f);			/* push fwd constraints forward */
+#ifdef REG_DEBUG
+	if (verbose)
+		fprintf(f, "\nfinal cleanup:\n");
+#endif
+	cleanup(nfa);				/* final tidying */
+#ifdef REG_DEBUG
+	if (verbose)
+		dumpnfa(nfa, f);
+#endif
+	return analyze(nfa);		/* and analysis */
+}
+
+/*
+ * pullback - pull back constraints backward to eliminate them
+ */
+static void
+pullback(struct nfa * nfa,
+		 FILE *f)				/* for debug output; NULL none */
+{
+	struct state *s;
+	struct state *nexts;
+	struct arc *a;
+	struct arc *nexta;
+	struct state *intermediates;
+	int			progress;
+
+	/* find and pull until there are no more */
+	do
+	{
+		progress = 0;
+		for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+		{
+			nexts = s->next;
+			intermediates = NULL;
+			for (a = s->outs; a != NULL && !NISERR(); a = nexta)
+			{
+				nexta = a->outchain;
+				if (a->type == '^' || a->type == BEHIND)
+					if (pull(nfa, a, &intermediates))
+						progress = 1;
+			}
+			/* clear tmp fields of intermediate states created here */
+			while (intermediates != NULL)
+			{
+				struct state *ns = intermediates->tmp;
+
+				intermediates->tmp = NULL;
+				intermediates = ns;
+			}
+			/* if s is now useless, get rid of it */
+			if ((s->nins == 0 || s->nouts == 0) && !s->flag)
+				dropstate(nfa, s);
+		}
+		if (progress && f != NULL)
+			dumpnfa(nfa, f);
+	} while (progress && !NISERR());
+	if (NISERR())
+		return;
+
+	/*
+	 * Any ^ constraints we were able to pull to the start state can now be
+	 * replaced by PLAIN arcs referencing the BOS or BOL colors.  There should
+	 * be no other ^ or BEHIND arcs left in the NFA, though we do not check
+	 * that here (compact() will fail if so).
+	 */
+	for (a = nfa->pre->outs; a != NULL; a = nexta)
+	{
+		nexta = a->outchain;
+		if (a->type == '^')
+		{
+			assert(a->co == 0 || a->co == 1);
+			newarc(nfa, PLAIN, nfa->bos[a->co], a->from, a->to);
+			freearc(nfa, a);
+		}
+	}
+}
+
+/*
+ * pull - pull a back constraint backward past its source state
+ *
+ * Returns 1 if successful (which it always is unless the source is the
+ * start state or we have an internal error), 0 if nothing happened.
+ *
+ * A significant property of this function is that it deletes no pre-existing
+ * states, and no outarcs of the constraint's from state other than the given
+ * constraint arc.  This makes the loops in pullback() safe, at the cost that
+ * we may leave useless states behind.  Therefore, we leave it to pullback()
+ * to delete such states.
+ *
+ * If the from state has multiple back-constraint outarcs, and/or multiple
+ * compatible constraint inarcs, we only need to create one new intermediate
+ * state per combination of predecessor and successor states.  *intermediates
+ * points to a list of such intermediate states for this from state (chained
+ * through their tmp fields).
+ */
+static int
+pull(struct nfa * nfa,
+	 struct arc * con,
+	 struct state ** intermediates)
+{
+	struct state *from = con->from;
+	struct state *to = con->to;
+	struct arc *a;
+	struct arc *nexta;
+	struct state *s;
+
+	assert(from != to);			/* should have gotten rid of this earlier */
+	if (from->flag)				/* can't pull back beyond start */
+		return 0;
+	if (from->nins == 0)
+	{							/* unreachable */
+		freearc(nfa, con);
+		return 1;
+	}
+
+	/*
+	 * First, clone from state if necessary to avoid other outarcs.  This may
+	 * seem wasteful, but it simplifies the logic, and we'll get rid of the
+	 * clone state again at the bottom.
+	 */
+	if (from->nouts > 1)
+	{
+		s = newstate(nfa);
+		if (NISERR())
+			return 0;
+		copyins(nfa, from, s);	/* duplicate inarcs */
+		cparc(nfa, con, s, to); /* move constraint arc */
+		freearc(nfa, con);
+		if (NISERR())
+			return 0;
+		from = s;
+		con = from->outs;
+	}
+	assert(from->nouts == 1);
+
+	/* propagate the constraint into the from state's inarcs */
+	for (a = from->ins; a != NULL && !NISERR(); a = nexta)
+	{
+		nexta = a->inchain;
+		switch (combine(con, a))
+		{
+			case INCOMPATIBLE:	/* destroy the arc */
+				freearc(nfa, a);
+				break;
+			case SATISFIED:		/* no action needed */
+				break;
+			case COMPATIBLE:	/* swap the two arcs, more or less */
+				/* need an intermediate state, but might have one already */
+				for (s = *intermediates; s != NULL; s = s->tmp)
+				{
+					assert(s->nins > 0 && s->nouts > 0);
+					if (s->ins->from == a->from && s->outs->to == to)
+						break;
+				}
+				if (s == NULL)
+				{
+					s = newstate(nfa);
+					if (NISERR())
+						return 0;
+					s->tmp = *intermediates;
+					*intermediates = s;
+				}
+				cparc(nfa, con, a->from, s);
+				cparc(nfa, a, s, to);
+				freearc(nfa, a);
+				break;
+			default:
+				assert(NOTREACHED);
+				break;
+		}
+	}
+
+	/* remaining inarcs, if any, incorporate the constraint */
+	moveins(nfa, from, to);
+	freearc(nfa, con);
+	/* from state is now useless, but we leave it to pullback() to clean up */
+	return 1;
+}
+
+/*
+ * pushfwd - push forward constraints forward to eliminate them
+ */
+static void
+pushfwd(struct nfa * nfa,
+		FILE *f)				/* for debug output; NULL none */
+{
+	struct state *s;
+	struct state *nexts;
+	struct arc *a;
+	struct arc *nexta;
+	struct state *intermediates;
+	int			progress;
+
+	/* find and push until there are no more */
+	do
+	{
+		progress = 0;
+		for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+		{
+			nexts = s->next;
+			intermediates = NULL;
+			for (a = s->ins; a != NULL && !NISERR(); a = nexta)
+			{
+				nexta = a->inchain;
+				if (a->type == '$' || a->type == AHEAD)
+					if (push(nfa, a, &intermediates))
+						progress = 1;
+			}
+			/* clear tmp fields of intermediate states created here */
+			while (intermediates != NULL)
+			{
+				struct state *ns = intermediates->tmp;
+
+				intermediates->tmp = NULL;
+				intermediates = ns;
+			}
+			/* if s is now useless, get rid of it */
+			if ((s->nins == 0 || s->nouts == 0) && !s->flag)
+				dropstate(nfa, s);
+		}
+		if (progress && f != NULL)
+			dumpnfa(nfa, f);
+	} while (progress && !NISERR());
+	if (NISERR())
+		return;
+
+	/*
+	 * Any $ constraints we were able to push to the post state can now be
+	 * replaced by PLAIN arcs referencing the EOS or EOL colors.  There should
+	 * be no other $ or AHEAD arcs left in the NFA, though we do not check
+	 * that here (compact() will fail if so).
+	 */
+	for (a = nfa->post->ins; a != NULL; a = nexta)
+	{
+		nexta = a->inchain;
+		if (a->type == '$')
+		{
+			assert(a->co == 0 || a->co == 1);
+			newarc(nfa, PLAIN, nfa->eos[a->co], a->from, a->to);
+			freearc(nfa, a);
+		}
+	}
+}
+
+/*
+ * push - push a forward constraint forward past its destination state
+ *
+ * Returns 1 if successful (which it always is unless the destination is the
+ * post state or we have an internal error), 0 if nothing happened.
+ *
+ * A significant property of this function is that it deletes no pre-existing
+ * states, and no inarcs of the constraint's to state other than the given
+ * constraint arc.  This makes the loops in pushfwd() safe, at the cost that
+ * we may leave useless states behind.  Therefore, we leave it to pushfwd()
+ * to delete such states.
+ *
+ * If the to state has multiple forward-constraint inarcs, and/or multiple
+ * compatible constraint outarcs, we only need to create one new intermediate
+ * state per combination of predecessor and successor states.  *intermediates
+ * points to a list of such intermediate states for this to state (chained
+ * through their tmp fields).
+ */
+static int
+push(struct nfa * nfa,
+	 struct arc * con,
+	 struct state ** intermediates)
+{
+	struct state *from = con->from;
+	struct state *to = con->to;
+	struct arc *a;
+	struct arc *nexta;
+	struct state *s;
+
+	assert(to != from);			/* should have gotten rid of this earlier */
+	if (to->flag)				/* can't push forward beyond end */
+		return 0;
+	if (to->nouts == 0)
+	{							/* dead end */
+		freearc(nfa, con);
+		return 1;
+	}
+
+	/*
+	 * First, clone to state if necessary to avoid other inarcs.  This may
+	 * seem wasteful, but it simplifies the logic, and we'll get rid of the
+	 * clone state again at the bottom.
+	 */
+	if (to->nins > 1)
+	{
+		s = newstate(nfa);
+		if (NISERR())
+			return 0;
+		copyouts(nfa, to, s);	/* duplicate outarcs */
+		cparc(nfa, con, from, s);		/* move constraint arc */
+		freearc(nfa, con);
+		if (NISERR())
+			return 0;
+		to = s;
+		con = to->ins;
+	}
+	assert(to->nins == 1);
+
+	/* propagate the constraint into the to state's outarcs */
+	for (a = to->outs; a != NULL && !NISERR(); a = nexta)
+	{
+		nexta = a->outchain;
+		switch (combine(con, a))
+		{
+			case INCOMPATIBLE:	/* destroy the arc */
+				freearc(nfa, a);
+				break;
+			case SATISFIED:		/* no action needed */
+				break;
+			case COMPATIBLE:	/* swap the two arcs, more or less */
+				/* need an intermediate state, but might have one already */
+				for (s = *intermediates; s != NULL; s = s->tmp)
+				{
+					assert(s->nins > 0 && s->nouts > 0);
+					if (s->ins->from == from && s->outs->to == a->to)
+						break;
+				}
+				if (s == NULL)
+				{
+					s = newstate(nfa);
+					if (NISERR())
+						return 0;
+					s->tmp = *intermediates;
+					*intermediates = s;
+				}
+				cparc(nfa, con, s, a->to);
+				cparc(nfa, a, from, s);
+				freearc(nfa, a);
+				break;
+			default:
+				assert(NOTREACHED);
+				break;
+		}
+	}
+
+	/* remaining outarcs, if any, incorporate the constraint */
+	moveouts(nfa, to, from);
+	freearc(nfa, con);
+	/* to state is now useless, but we leave it to pushfwd() to clean up */
+	return 1;
+}
+
+/*
+ * combine - constraint lands on an arc, what happens?
+ *
+ * #def INCOMPATIBLE	1	// destroys arc
+ * #def SATISFIED		2	// constraint satisfied
+ * #def COMPATIBLE		3	// compatible but not satisfied yet
+ */
+static int
+combine(struct arc * con,
+		struct arc * a)
+{
+#define  CA(ct,at)	 (((ct)<<CHAR_BIT) | (at))
+
+	switch (CA(con->type, a->type))
+	{
+		case CA('^', PLAIN):	/* newlines are handled separately */
+		case CA('$', PLAIN):
+			return INCOMPATIBLE;
+			break;
+		case CA(AHEAD, PLAIN):	/* color constraints meet colors */
+		case CA(BEHIND, PLAIN):
+			if (con->co == a->co)
+				return SATISFIED;
+			return INCOMPATIBLE;
+			break;
+		case CA('^', '^'):		/* collision, similar constraints */
+		case CA('$', '$'):
+		case CA(AHEAD, AHEAD):
+		case CA(BEHIND, BEHIND):
+			if (con->co == a->co)		/* true duplication */
+				return SATISFIED;
+			return INCOMPATIBLE;
+			break;
+		case CA('^', BEHIND):	/* collision, dissimilar constraints */
+		case CA(BEHIND, '^'):
+		case CA('$', AHEAD):
+		case CA(AHEAD, '$'):
+			return INCOMPATIBLE;
+			break;
+		case CA('^', '$'):		/* constraints passing each other */
+		case CA('^', AHEAD):
+		case CA(BEHIND, '$'):
+		case CA(BEHIND, AHEAD):
+		case CA('$', '^'):
+		case CA('$', BEHIND):
+		case CA(AHEAD, '^'):
+		case CA(AHEAD, BEHIND):
+		case CA('^', LACON):
+		case CA(BEHIND, LACON):
+		case CA('$', LACON):
+		case CA(AHEAD, LACON):
+			return COMPATIBLE;
+			break;
+	}
+	assert(NOTREACHED);
+	return INCOMPATIBLE;		/* for benefit of blind compilers */
+}
+
+/*
+ * fixempties - get rid of EMPTY arcs
+ */
+static void
+fixempties(struct nfa * nfa,
+		   FILE *f)				/* for debug output; NULL none */
+{
+	struct state *s;
+	struct state *s2;
+	struct state *nexts;
+	struct arc *a;
+	struct arc *nexta;
+	int			totalinarcs;
+	struct arc **inarcsorig;
+	struct arc **arcarray;
+	int			arccount;
+	int			prevnins;
+	int			nskip;
+
+	/*
+	 * First, get rid of any states whose sole out-arc is an EMPTY, since
+	 * they're basically just aliases for their successor.  The parsing
+	 * algorithm creates enough of these that it's worth special-casing this.
+	 */
+	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+	{
+		nexts = s->next;
+		if (s->flag || s->nouts != 1)
+			continue;
+		a = s->outs;
+		assert(a != NULL && a->outchain == NULL);
+		if (a->type != EMPTY)
+			continue;
+		if (s != a->to)
+			moveins(nfa, s, a->to);
+		dropstate(nfa, s);
+	}
+
+	/*
+	 * Similarly, get rid of any state with a single EMPTY in-arc, by folding
+	 * it into its predecessor.
+	 */
+	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+	{
+		nexts = s->next;
+		/* while we're at it, ensure tmp fields are clear for next step */
+		assert(s->tmp == NULL);
+		if (s->flag || s->nins != 1)
+			continue;
+		a = s->ins;
+		assert(a != NULL && a->inchain == NULL);
+		if (a->type != EMPTY)
+			continue;
+		if (s != a->from)
+			moveouts(nfa, s, a->from);
+		dropstate(nfa, s);
+	}
+
+	if (NISERR())
+		return;
+
+	/*
+	 * For each remaining NFA state, find all other states from which it is
+	 * reachable by a chain of one or more EMPTY arcs.  Then generate new arcs
+	 * that eliminate the need for each such chain.
+	 *
+	 * We could replace a chain of EMPTY arcs that leads from a "from" state
+	 * to a "to" state either by pushing non-EMPTY arcs forward (linking
+	 * directly from "from"'s predecessors to "to") or by pulling them back
+	 * (linking directly from "from" to "to"'s successors).  We choose to
+	 * always do the former; this choice is somewhat arbitrary, but the
+	 * approach below requires that we uniformly do one or the other.
+	 *
+	 * Suppose we have a chain of N successive EMPTY arcs (where N can easily
+	 * approach the size of the NFA).  All of the intermediate states must
+	 * have additional inarcs and outarcs, else they'd have been removed by
+	 * the steps above.  Assuming their inarcs are mostly not empties, we will
+	 * add O(N^2) arcs to the NFA, since a non-EMPTY inarc leading to any one
+	 * state in the chain must be duplicated to lead to all its successor
+	 * states as well.  So there is no hope of doing less than O(N^2) work;
+	 * however, we should endeavor to keep the big-O cost from being even
+	 * worse than that, which it can easily become without care.  In
+	 * particular, suppose we were to copy all S1's inarcs forward to S2, and
+	 * then also to S3, and then later we consider pushing S2's inarcs forward
+	 * to S3.  If we include the arcs already copied from S1 in that, we'd be
+	 * doing O(N^3) work.  (The duplicate-arc elimination built into newarc()
+	 * and its cohorts would get rid of the extra arcs, but not without cost.)
+	 *
+	 * We can avoid this cost by treating only arcs that existed at the start
+	 * of this phase as candidates to be pushed forward.  To identify those,
+	 * we remember the first inarc each state had to start with.  We rely on
+	 * the fact that newarc() and friends put new arcs on the front of their
+	 * to-states' inchains, and that this phase never deletes arcs, so that
+	 * the original arcs must be the last arcs in their to-states' inchains.
+	 *
+	 * So the process here is that, for each state in the NFA, we gather up
+	 * all non-EMPTY inarcs of states that can reach the target state via
+	 * EMPTY arcs.  We then sort, de-duplicate, and merge these arcs into the
+	 * target state's inchain.  (We can safely use sort-merge for this as long
+	 * as we update each state's original-arcs pointer after we add arcs to
+	 * it; the sort step of mergeins probably changed the order of the old
+	 * arcs.)
+	 *
+	 * Another refinement worth making is that, because we only add non-EMPTY
+	 * arcs during this phase, and all added arcs have the same from-state as
+	 * the non-EMPTY arc they were cloned from, we know ahead of time that any
+	 * states having only EMPTY outarcs will be useless for lack of outarcs
+	 * after we drop the EMPTY arcs.  (They cannot gain non-EMPTY outarcs if
+	 * they had none to start with.)  So we need not bother to update the
+	 * inchains of such states at all.
+	 */
+
+	/* Remember the states' first original inarcs */
+	/* ... and while at it, count how many old inarcs there are altogether */
+	inarcsorig = (struct arc **) MALLOC(nfa->nstates * sizeof(struct arc *));
+	if (inarcsorig == NULL)
+	{
+		NERR(REG_ESPACE);
+		return;
+	}
+	totalinarcs = 0;
+	for (s = nfa->states; s != NULL; s = s->next)
+	{
+		inarcsorig[s->no] = s->ins;
+		totalinarcs += s->nins;
+	}
+
+	/*
+	 * Create a workspace for accumulating the inarcs to be added to the
+	 * current target state.  totalinarcs is probably a considerable
+	 * overestimate of the space needed, but the NFA is unlikely to be large
+	 * enough at this point to make it worth being smarter.
+	 */
+	arcarray = (struct arc **) MALLOC(totalinarcs * sizeof(struct arc *));
+	if (arcarray == NULL)
+	{
+		NERR(REG_ESPACE);
+		FREE(inarcsorig);
+		return;
+	}
+
+	/* And iterate over the target states */
+	for (s = nfa->states; s != NULL && !NISERR(); s = s->next)
+	{
+		/* Ignore target states without non-EMPTY outarcs, per note above */
+		if (!s->flag && !hasnonemptyout(s))
+			continue;
+
+		/* Find predecessor states and accumulate their original inarcs */
+		arccount = 0;
+		for (s2 = emptyreachable(nfa, s, s, inarcsorig); s2 != s; s2 = nexts)
+		{
+			/* Add s2's original inarcs to arcarray[], but ignore empties */
+			for (a = inarcsorig[s2->no]; a != NULL; a = a->inchain)
+			{
+				if (a->type != EMPTY)
+					arcarray[arccount++] = a;
+			}
+
+			/* Reset the tmp fields as we walk back */
+			nexts = s2->tmp;
+			s2->tmp = NULL;
+		}
+		s->tmp = NULL;
+		assert(arccount <= totalinarcs);
+
+		/* Remember how many original inarcs this state has */
+		prevnins = s->nins;
+
+		/* Add non-duplicate inarcs to target state */
+		mergeins(nfa, s, arcarray, arccount);
+
+		/* Now we must update the state's inarcsorig pointer */
+		nskip = s->nins - prevnins;
+		a = s->ins;
+		while (nskip-- > 0)
+			a = a->inchain;
+		inarcsorig[s->no] = a;
+	}
+
+	FREE(arcarray);
+	FREE(inarcsorig);
+
+	if (NISERR())
+		return;
+
+	/*
+	 * Now remove all the EMPTY arcs, since we don't need them anymore.
+	 */
+	for (s = nfa->states; s != NULL; s = s->next)
+	{
+		for (a = s->outs; a != NULL; a = nexta)
+		{
+			nexta = a->outchain;
+			if (a->type == EMPTY)
+				freearc(nfa, a);
+		}
+	}
+
+	/*
+	 * And remove any states that have become useless.  (This cleanup is not
+	 * very thorough, and would be even less so if we tried to combine it with
+	 * the previous step; but cleanup() will take care of anything we miss.)
+	 */
+	for (s = nfa->states; s != NULL; s = nexts)
+	{
+		nexts = s->next;
+		if ((s->nins == 0 || s->nouts == 0) && !s->flag)
+			dropstate(nfa, s);
+	}
+
+	if (f != NULL)
+		dumpnfa(nfa, f);
+}
+
+/*
+ * emptyreachable - recursively find all states that can reach s by EMPTY arcs
+ *
+ * The return value is the last such state found.  Its tmp field links back
+ * to the next-to-last such state, and so on back to s, so that all these
+ * states can be located without searching the whole NFA.
+ *
+ * Since this is only used in fixempties(), we pass in the inarcsorig[] array
+ * maintained by that function.  This lets us skip over all new inarcs, which
+ * are certainly not EMPTY arcs.
+ *
+ * The maximum recursion depth here is equal to the length of the longest
+ * loop-free chain of EMPTY arcs, which is surely no more than the size of
+ * the NFA ... but that could still be enough to cause trouble.
+ */
+static struct state *
+emptyreachable(struct nfa * nfa,
+			   struct state * s,
+			   struct state * lastfound,
+			   struct arc ** inarcsorig)
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return lastfound;
+	}
+
+	s->tmp = lastfound;
+	lastfound = s;
+	for (a = inarcsorig[s->no]; a != NULL; a = a->inchain)
+	{
+		if (a->type == EMPTY && a->from->tmp == NULL)
+			lastfound = emptyreachable(nfa, a->from, lastfound, inarcsorig);
+	}
+	return lastfound;
+}
+
+/*
+ * isconstraintarc - detect whether an arc is of a constraint type
+ */
+static inline int
+isconstraintarc(struct arc * a)
+{
+	switch (a->type)
+	{
+		case '^':
+		case '$':
+		case BEHIND:
+		case AHEAD:
+		case LACON:
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * hasconstraintout - does state have a constraint out arc?
+ */
+static int
+hasconstraintout(struct state * s)
+{
+	struct arc *a;
+
+	for (a = s->outs; a != NULL; a = a->outchain)
+	{
+		if (isconstraintarc(a))
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * fixconstraintloops - get rid of loops containing only constraint arcs
+ *
+ * A loop of states that contains only constraint arcs is useless, since
+ * passing around the loop represents no forward progress.  Moreover, it
+ * would cause infinite looping in pullback/pushfwd, so we need to get rid
+ * of such loops before doing that.
+ */
+static void
+fixconstraintloops(struct nfa * nfa,
+				   FILE *f)		/* for debug output; NULL none */
+{
+	struct state *s;
+	struct state *nexts;
+	struct arc *a;
+	struct arc *nexta;
+	int			hasconstraints;
+
+	/*
+	 * In the trivial case of a state that loops to itself, we can just drop
+	 * the constraint arc altogether.  This is worth special-casing because
+	 * such loops are far more common than loops containing multiple states.
+	 * While we're at it, note whether any constraint arcs survive.
+	 */
+	hasconstraints = 0;
+	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+	{
+		nexts = s->next;
+		/* while we're at it, ensure tmp fields are clear for next step */
+		assert(s->tmp == NULL);
+		for (a = s->outs; a != NULL && !NISERR(); a = nexta)
+		{
+			nexta = a->outchain;
+			if (isconstraintarc(a))
+			{
+				if (a->to == s)
+					freearc(nfa, a);
+				else
+					hasconstraints = 1;
+			}
+		}
+		/* If we removed all the outarcs, the state is useless. */
+		if (s->nouts == 0 && !s->flag)
+			dropstate(nfa, s);
+	}
+
+	/* Nothing to do if no remaining constraint arcs */
+	if (NISERR() || !hasconstraints)
+		return;
+
+	/*
+	 * Starting from each remaining NFA state, search outwards for a
+	 * constraint loop.  If we find a loop, break the loop, then start the
+	 * search over.  (We could possibly retain some state from the first scan,
+	 * but it would complicate things greatly, and multi-state constraint
+	 * loops are rare enough that it's not worth optimizing the case.)
+	 */
+restart:
+	for (s = nfa->states; s != NULL && !NISERR(); s = s->next)
+	{
+		if (findconstraintloop(nfa, s))
+			goto restart;
+	}
+
+	if (NISERR())
+		return;
+
+	/*
+	 * Now remove any states that have become useless.  (This cleanup is not
+	 * very thorough, and would be even less so if we tried to combine it with
+	 * the previous step; but cleanup() will take care of anything we miss.)
+	 *
+	 * Because findconstraintloop intentionally doesn't reset all tmp fields,
+	 * we have to clear them after it's done.  This is a convenient place to
+	 * do that, too.
+	 */
+	for (s = nfa->states; s != NULL; s = nexts)
+	{
+		nexts = s->next;
+		s->tmp = NULL;
+		if ((s->nins == 0 || s->nouts == 0) && !s->flag)
+			dropstate(nfa, s);
+	}
+
+	if (f != NULL)
+		dumpnfa(nfa, f);
+}
+
+/*
+ * findconstraintloop - recursively find a loop of constraint arcs
+ *
+ * If we find a loop, break it by calling breakconstraintloop(), then
+ * return 1; otherwise return 0.
+ *
+ * State tmp fields are guaranteed all NULL on a success return, because
+ * breakconstraintloop does that.  After a failure return, any state that
+ * is known not to be part of a loop is marked with s->tmp == s; this allows
+ * us not to have to re-prove that fact on later calls.  (This convention is
+ * workable because we already eliminated single-state loops.)
+ *
+ * Note that the found loop doesn't necessarily include the first state we
+ * are called on.  Any loop reachable from that state will do.
+ *
+ * The maximum recursion depth here is one more than the length of the longest
+ * loop-free chain of constraint arcs, which is surely no more than the size
+ * of the NFA ... but that could still be enough to cause trouble.
+ */
+static int
+findconstraintloop(struct nfa * nfa, struct state * s)
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return 1;				/* to exit as quickly as possible */
+	}
+
+	if (s->tmp != NULL)
+	{
+		/* Already proven uninteresting? */
+		if (s->tmp == s)
+			return 0;
+		/* Found a loop involving s */
+		breakconstraintloop(nfa, s);
+		/* The tmp fields have been cleaned up by breakconstraintloop */
+		return 1;
+	}
+	for (a = s->outs; a != NULL; a = a->outchain)
+	{
+		if (isconstraintarc(a))
+		{
+			struct state *sto = a->to;
+
+			assert(sto != s);
+			s->tmp = sto;
+			if (findconstraintloop(nfa, sto))
+				return 1;
+		}
+	}
+
+	/*
+	 * If we get here, no constraint loop exists leading out from s.  Mark it
+	 * with s->tmp == s so we need not rediscover that fact again later.
+	 */
+	s->tmp = s;
+	return 0;
+}
+
+/*
+ * breakconstraintloop - break a loop of constraint arcs
+ *
+ * sinitial is any one member state of the loop.  Each loop member's tmp
+ * field links to its successor within the loop.  (Note that this function
+ * will reset all the tmp fields to NULL.)
+ *
+ * We can break the loop by, for any one state S1 in the loop, cloning its
+ * loop successor state S2 (and possibly following states), and then moving
+ * all S1->S2 constraint arcs to point to the cloned S2.  The cloned S2 should
+ * copy any non-constraint outarcs of S2.  Constraint outarcs should be
+ * dropped if they point back to S1, else they need to be copied as arcs to
+ * similarly cloned states S3, S4, etc.  In general, each cloned state copies
+ * non-constraint outarcs, drops constraint outarcs that would lead to itself
+ * or any earlier cloned state, and sends other constraint outarcs to newly
+ * cloned states.  No cloned state will have any inarcs that aren't constraint
+ * arcs or do not lead from S1 or earlier-cloned states.  It's okay to drop
+ * constraint back-arcs since they would not take us to any state we've not
+ * already been in; therefore, no new constraint loop is created.  In this way
+ * we generate a modified NFA that can still represent every useful state
+ * sequence, but not sequences that represent state loops with no consumption
+ * of input data.  Note that the set of cloned states will certainly include
+ * all of the loop member states other than S1, and it may also include
+ * non-loop states that are reachable from S2 via constraint arcs.  This is
+ * important because there is no guarantee that findconstraintloop found a
+ * maximal loop (and searching for one would be NP-hard, so don't try).
+ * Frequently the "non-loop states" are actually part of a larger loop that
+ * we didn't notice, and indeed there may be several overlapping loops.
+ * This technique ensures convergence in such cases, while considering only
+ * the originally-found loop does not.
+ *
+ * If there is only one S1->S2 constraint arc, then that constraint is
+ * certainly satisfied when we enter any of the clone states.  This means that
+ * in the common case where many of the constraint arcs are identically
+ * labeled, we can merge together clone states linked by a similarly-labeled
+ * constraint: if we can get to the first one we can certainly get to the
+ * second, so there's no need to distinguish.  This greatly reduces the number
+ * of new states needed, so we preferentially break the given loop at a state
+ * pair where this is true.
+ *
+ * Furthermore, it's fairly common to find that a cloned successor state has
+ * no outarcs, especially if we're a bit aggressive about removing unnecessary
+ * outarcs.  If that happens, then there is simply not any interesting state
+ * that can be reached through the predecessor's loop arcs, which means we can
+ * break the loop just by removing those loop arcs, with no new states added.
+ */
+static void
+breakconstraintloop(struct nfa * nfa, struct state * sinitial)
+{
+	struct state *s;
+	struct state *shead;
+	struct state *stail;
+	struct state *sclone;
+	struct state *nexts;
+	struct arc *refarc;
+	struct arc *a;
+	struct arc *nexta;
+
+	/*
+	 * Start by identifying which loop step we want to break at.
+	 * Preferentially this is one with only one constraint arc.  (XXX are
+	 * there any other secondary heuristics we want to use here?)  Set refarc
+	 * to point to the selected lone constraint arc, if there is one.
+	 */
+	refarc = NULL;
+	s = sinitial;
+	do
+	{
+		nexts = s->tmp;
+		assert(nexts != s);		/* should not see any one-element loops */
+		if (refarc == NULL)
+		{
+			int			narcs = 0;
+
+			for (a = s->outs; a != NULL; a = a->outchain)
+			{
+				if (a->to == nexts && isconstraintarc(a))
+				{
+					refarc = a;
+					narcs++;
+				}
+			}
+			assert(narcs > 0);
+			if (narcs > 1)
+				refarc = NULL;	/* multiple constraint arcs here, no good */
+		}
+		s = nexts;
+	} while (s != sinitial);
+
+	if (refarc)
+	{
+		/* break at the refarc */
+		shead = refarc->from;
+		stail = refarc->to;
+		assert(stail == shead->tmp);
+	}
+	else
+	{
+		/* for lack of a better idea, break after sinitial */
+		shead = sinitial;
+		stail = sinitial->tmp;
+	}
+
+	/*
+	 * Reset the tmp fields so that we can use them for local storage in
+	 * clonesuccessorstates.  (findconstraintloop won't mind, since it's just
+	 * going to abandon its search anyway.)
+	 */
+	for (s = nfa->states; s != NULL; s = s->next)
+		s->tmp = NULL;
+
+	/*
+	 * Recursively build clone state(s) as needed.
+	 */
+	sclone = newstate(nfa);
+	if (sclone == NULL)
+	{
+		assert(NISERR());
+		return;
+	}
+
+	clonesuccessorstates(nfa, stail, sclone, shead, refarc,
+						 NULL, NULL, nfa->nstates);
+
+	if (NISERR())
+		return;
+
+	/*
+	 * It's possible that sclone has no outarcs at all, in which case it's
+	 * useless.  (We don't try extremely hard to get rid of useless states
+	 * here, but this is an easy and fairly common case.)
+	 */
+	if (sclone->nouts == 0)
+	{
+		freestate(nfa, sclone);
+		sclone = NULL;
+	}
+
+	/*
+	 * Move shead's constraint-loop arcs to point to sclone, or just drop them
+	 * if we discovered we don't need sclone.
+	 */
+	for (a = shead->outs; a != NULL; a = nexta)
+	{
+		nexta = a->outchain;
+		if (a->to == stail && isconstraintarc(a))
+		{
+			if (sclone)
+				cparc(nfa, a, shead, sclone);
+			freearc(nfa, a);
+			if (NISERR())
+				break;
+		}
+	}
+}
+
+/*
+ * clonesuccessorstates - create a tree of constraint-arc successor states
+ *
+ * ssource is the state to be cloned, and sclone is the state to copy its
+ * outarcs into.  sclone's inarcs, if any, should already be set up.
+ *
+ * spredecessor is the original predecessor state that we are trying to build
+ * successors for (it may not be the immediate predecessor of ssource).
+ * refarc, if not NULL, is the original constraint arc that is known to have
+ * been traversed out of spredecessor to reach the successor(s).
+ *
+ * For each cloned successor state, we transiently create a "donemap" that is
+ * a boolean array showing which source states we've already visited for this
+ * clone state.  This prevents infinite recursion as well as useless repeat
+ * visits to the same state subtree (which can add up fast, since typical NFAs
+ * have multiple redundant arc pathways).  Each donemap is a char array
+ * indexed by state number.  The donemaps are all of the same size "nstates",
+ * which is nfa->nstates as of the start of the recursion.  This is enough to
+ * have entries for all pre-existing states, but *not* entries for clone
+ * states created during the recursion.  That's okay since we have no need to
+ * mark those.
+ *
+ * curdonemap is NULL when recursing to a new sclone state, or sclone's
+ * donemap when we are recursing without having created a new state (which we
+ * do when we decide we can merge a successor state into the current clone
+ * state).  outerdonemap is NULL at the top level and otherwise the parent
+ * clone state's donemap.
+ *
+ * The successor states we create and fill here form a strict tree structure,
+ * with each state having exactly one predecessor, except that the toplevel
+ * state has no inarcs as yet (breakconstraintloop will add its inarcs from
+ * spredecessor after we're done).  Thus, we can examine sclone's inarcs back
+ * to the root, plus refarc if any, to identify the set of constraints already
+ * known valid at the current point.  This allows us to avoid generating extra
+ * successor states.
+ */
+static void
+clonesuccessorstates(struct nfa * nfa,
+					 struct state * ssource,
+					 struct state * sclone,
+					 struct state * spredecessor,
+					 struct arc * refarc,
+					 char *curdonemap,
+					 char *outerdonemap,
+					 int nstates)
+{
+	char	   *donemap;
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	/* If this state hasn't already got a donemap, create one */
+	donemap = curdonemap;
+	if (donemap == NULL)
+	{
+		donemap = (char *) MALLOC(nstates * sizeof(char));
+		if (donemap == NULL)
+		{
+			NERR(REG_ESPACE);
+			return;
+		}
+
+		if (outerdonemap != NULL)
+		{
+			/*
+			 * Not at outermost recursion level, so copy the outer level's
+			 * donemap; this ensures that we see states in process of being
+			 * visited at outer levels, or already merged into predecessor
+			 * states, as ones we shouldn't traverse back to.
+			 */
+			memcpy(donemap, outerdonemap, nstates * sizeof(char));
+		}
+		else
+		{
+			/* At outermost level, only spredecessor is off-limits */
+			memset(donemap, 0, nstates * sizeof(char));
+			assert(spredecessor->no < nstates);
+			donemap[spredecessor->no] = 1;
+		}
+	}
+
+	/* Mark ssource as visited in the donemap */
+	assert(ssource->no < nstates);
+	assert(donemap[ssource->no] == 0);
+	donemap[ssource->no] = 1;
+
+	/*
+	 * We proceed by first cloning all of ssource's outarcs, creating new
+	 * clone states as needed but not doing more with them than that.  Then in
+	 * a second pass, recurse to process the child clone states.  This allows
+	 * us to have only one child clone state per reachable source state, even
+	 * when there are multiple outarcs leading to the same state.  Also, when
+	 * we do visit a child state, its set of inarcs is known exactly, which
+	 * makes it safe to apply the constraint-is-already-checked optimization.
+	 * Also, this ensures that we've merged all the states we can into the
+	 * current clone before we recurse to any children, thus possibly saving
+	 * them from making extra images of those states.
+	 *
+	 * While this function runs, child clone states of the current state are
+	 * marked by setting their tmp fields to point to the original state they
+	 * were cloned from.  This makes it possible to detect multiple outarcs
+	 * leading to the same state, and also makes it easy to distinguish clone
+	 * states from original states (which will have tmp == NULL).
+	 */
+	for (a = ssource->outs; a != NULL && !NISERR(); a = a->outchain)
+	{
+		struct state *sto = a->to;
+
+		/*
+		 * We do not consider cloning successor states that have no constraint
+		 * outarcs; just link to them as-is.  They cannot be part of a
+		 * constraint loop so there is no need to make copies.  In particular,
+		 * this rule keeps us from trying to clone the post state, which would
+		 * be a bad idea.
+		 */
+		if (isconstraintarc(a) && hasconstraintout(sto))
+		{
+			struct state *prevclone;
+			int			canmerge;
+			struct arc *a2;
+
+			/*
+			 * Back-link constraint arcs must not be followed.  Nor is there a
+			 * need to revisit states previously merged into this clone.
+			 */
+			assert(sto->no < nstates);
+			if (donemap[sto->no] != 0)
+				continue;
+
+			/*
+			 * Check whether we already have a child clone state for this
+			 * source state.
+			 */
+			prevclone = NULL;
+			for (a2 = sclone->outs; a2 != NULL; a2 = a2->outchain)
+			{
+				if (a2->to->tmp == sto)
+				{
+					prevclone = a2->to;
+					break;
+				}
+			}
+
+			/*
+			 * If this arc is labeled the same as refarc, or the same as any
+			 * arc we must have traversed to get to sclone, then no additional
+			 * constraints need to be met to get to sto, so we should just
+			 * merge its outarcs into sclone.
+			 */
+			if (refarc && a->type == refarc->type && a->co == refarc->co)
+				canmerge = 1;
+			else
+			{
+				struct state *s;
+
+				canmerge = 0;
+				for (s = sclone; s->ins; s = s->ins->from)
+				{
+					if (s->nins == 1 &&
+						a->type == s->ins->type && a->co == s->ins->co)
+					{
+						canmerge = 1;
+						break;
+					}
+				}
+			}
+
+			if (canmerge)
+			{
+				/*
+				 * We can merge into sclone.  If we previously made a child
+				 * clone state, drop it; there's no need to visit it.  (This
+				 * can happen if ssource has multiple pathways to sto, and we
+				 * only just now found one that is provably a no-op.)
+				 */
+				if (prevclone)
+					dropstate(nfa, prevclone);	/* kills our outarc, too */
+
+				/* Recurse to merge sto's outarcs into sclone */
+				clonesuccessorstates(nfa,
+									 sto,
+									 sclone,
+									 spredecessor,
+									 refarc,
+									 donemap,
+									 outerdonemap,
+									 nstates);
+				/* sto should now be marked as previously visited */
+				assert(NISERR() || donemap[sto->no] == 1);
+			}
+			else if (prevclone)
+			{
+				/*
+				 * We already have a clone state for this successor, so just
+				 * make another arc to it.
+				 */
+				cparc(nfa, a, sclone, prevclone);
+			}
+			else
+			{
+				/*
+				 * We need to create a new successor clone state.
+				 */
+				struct state *stoclone;
+
+				stoclone = newstate(nfa);
+				if (stoclone == NULL)
+				{
+					assert(NISERR());
+					break;
+				}
+				/* Mark it as to what it's a clone of */
+				stoclone->tmp = sto;
+				/* ... and add the outarc leading to it */
+				cparc(nfa, a, sclone, stoclone);
+			}
+		}
+		else
+		{
+			/*
+			 * Non-constraint outarcs just get copied to sclone, as do outarcs
+			 * leading to states with no constraint outarc.
+			 */
+			cparc(nfa, a, sclone, sto);
+		}
+	}
+
+	/*
+	 * If we are at outer level for this clone state, recurse to all its child
+	 * clone states, clearing their tmp fields as we go.  (If we're not
+	 * outermost for sclone, leave this to be done by the outer call level.)
+	 * Note that if we have multiple outarcs leading to the same clone state,
+	 * it will only be recursed-to once.
+	 */
+	if (curdonemap == NULL)
+	{
+		for (a = sclone->outs; a != NULL && !NISERR(); a = a->outchain)
+		{
+			struct state *stoclone = a->to;
+			struct state *sto = stoclone->tmp;
+
+			if (sto != NULL)
+			{
+				stoclone->tmp = NULL;
+				clonesuccessorstates(nfa,
+									 sto,
+									 stoclone,
+									 spredecessor,
+									 refarc,
+									 NULL,
+									 donemap,
+									 nstates);
+			}
+		}
+
+		/* Don't forget to free sclone's donemap when done with it */
+		FREE(donemap);
+	}
+}
+
+/*
+ * cleanup - clean up NFA after optimizations
+ */
+static void
+cleanup(struct nfa * nfa)
+{
+	struct state *s;
+	struct state *nexts;
+	int			n;
+
+	if (NISERR())
+		return;
+
+	/* clear out unreachable or dead-end states */
+	/* use pre to mark reachable, then post to mark can-reach-post */
+	markreachable(nfa, nfa->pre, (struct state *) NULL, nfa->pre);
+	markcanreach(nfa, nfa->post, nfa->pre, nfa->post);
+	for (s = nfa->states; s != NULL && !NISERR(); s = nexts)
+	{
+		nexts = s->next;
+		if (s->tmp != nfa->post && !s->flag)
+			dropstate(nfa, s);
+	}
+	assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == nfa->post);
+	cleartraverse(nfa, nfa->pre);
+	assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == NULL);
+	/* the nins==0 (final unreachable) case will be caught later */
+
+	/* renumber surviving states */
+	n = 0;
+	for (s = nfa->states; s != NULL; s = s->next)
+		s->no = n++;
+	nfa->nstates = n;
+}
+
+/*
+ * markreachable - recursive marking of reachable states
+ */
+static void
+markreachable(struct nfa * nfa,
+			  struct state * s,
+			  struct state * okay,		/* consider only states with this mark */
+			  struct state * mark)		/* the value to mark with */
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	if (s->tmp != okay)
+		return;
+	s->tmp = mark;
+
+	for (a = s->outs; a != NULL; a = a->outchain)
+		markreachable(nfa, a->to, okay, mark);
+}
+
+/*
+ * markcanreach - recursive marking of states which can reach here
+ */
+static void
+markcanreach(struct nfa * nfa,
+			 struct state * s,
+			 struct state * okay,		/* consider only states with this mark */
+			 struct state * mark)		/* the value to mark with */
+{
+	struct arc *a;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(nfa->v->re))
+	{
+		NERR(REG_ETOOBIG);
+		return;
+	}
+
+	if (s->tmp != okay)
+		return;
+	s->tmp = mark;
+
+	for (a = s->ins; a != NULL; a = a->inchain)
+		markcanreach(nfa, a->from, okay, mark);
+}
+
+/*
+ * analyze - ascertain potentially-useful facts about an optimized NFA
+ */
+static long						/* re_info bits to be ORed in */
+analyze(struct nfa * nfa)
+{
+	struct arc *a;
+	struct arc *aa;
+
+	if (NISERR())
+		return 0;
+
+	if (nfa->pre->outs == NULL)
+		return REG_UIMPOSSIBLE;
+	for (a = nfa->pre->outs; a != NULL; a = a->outchain)
+		for (aa = a->to->outs; aa != NULL; aa = aa->outchain)
+			if (aa->to == nfa->post)
+				return REG_UEMPTYMATCH;
+	return 0;
+}
+
+/*
+ * compact - construct the compact representation of an NFA
+ */
+static void
+compact(struct nfa * nfa,
+		struct cnfa * cnfa)
+{
+	struct state *s;
+	struct arc *a;
+	size_t		nstates;
+	size_t		narcs;
+	struct carc *ca;
+	struct carc *first;
+
+	assert(!NISERR());
+
+	nstates = 0;
+	narcs = 0;
+	for (s = nfa->states; s != NULL; s = s->next)
+	{
+		nstates++;
+		narcs += s->nouts + 1;	/* need one extra for endmarker */
+	}
+
+	cnfa->stflags = (char *) MALLOC(nstates * sizeof(char));
+	cnfa->states = (struct carc **) MALLOC(nstates * sizeof(struct carc *));
+	cnfa->arcs = (struct carc *) MALLOC(narcs * sizeof(struct carc));
+	if (cnfa->stflags == NULL || cnfa->states == NULL || cnfa->arcs == NULL)
+	{
+		if (cnfa->stflags != NULL)
+			FREE(cnfa->stflags);
+		if (cnfa->states != NULL)
+			FREE(cnfa->states);
+		if (cnfa->arcs != NULL)
+			FREE(cnfa->arcs);
+		NERR(REG_ESPACE);
+		return;
+	}
+	cnfa->nstates = nstates;
+	cnfa->pre = nfa->pre->no;
+	cnfa->post = nfa->post->no;
+	cnfa->bos[0] = nfa->bos[0];
+	cnfa->bos[1] = nfa->bos[1];
+	cnfa->eos[0] = nfa->eos[0];
+	cnfa->eos[1] = nfa->eos[1];
+	cnfa->ncolors = maxcolor(nfa->cm) + 1;
+	cnfa->flags = 0;
+
+	ca = cnfa->arcs;
+	for (s = nfa->states; s != NULL; s = s->next)
+	{
+		assert((size_t) s->no < nstates);
+		cnfa->stflags[s->no] = 0;
+		cnfa->states[s->no] = ca;
+		first = ca;
+		for (a = s->outs; a != NULL; a = a->outchain)
+			switch (a->type)
+			{
+				case PLAIN:
+					ca->co = a->co;
+					ca->to = a->to->no;
+					ca++;
+					break;
+				case LACON:
+					assert(s->no != cnfa->pre);
+					ca->co = (color) (cnfa->ncolors + a->co);
+					ca->to = a->to->no;
+					ca++;
+					cnfa->flags |= HASLACONS;
+					break;
+				default:
+					NERR(REG_ASSERT);
+					break;
+			}
+		carcsort(first, ca - first);
+		ca->co = COLORLESS;
+		ca->to = 0;
+		ca++;
+	}
+	assert(ca == &cnfa->arcs[narcs]);
+	assert(cnfa->nstates != 0);
+
+	/* mark no-progress states */
+	for (a = nfa->pre->outs; a != NULL; a = a->outchain)
+		cnfa->stflags[a->to->no] = CNFA_NOPROGRESS;
+	cnfa->stflags[nfa->pre->no] = CNFA_NOPROGRESS;
+}
+
+/*
+ * carcsort - sort compacted-NFA arcs by color
+ */
+static void
+carcsort(struct carc * first, size_t n)
+{
+	if (n > 1)
+		qsort(first, n, sizeof(struct carc), carc_cmp);
+}
+
+static int
+carc_cmp(const void *a, const void *b)
+{
+	const struct carc *aa = (const struct carc *) a;
+	const struct carc *bb = (const struct carc *) b;
+
+	if (aa->co < bb->co)
+		return -1;
+	if (aa->co > bb->co)
+		return +1;
+	if (aa->to < bb->to)
+		return -1;
+	if (aa->to > bb->to)
+		return +1;
+	return 0;
+}
+
+/*
+ * freecnfa - free a compacted NFA
+ */
+static void
+freecnfa(struct cnfa * cnfa)
+{
+	assert(cnfa->nstates != 0); /* not empty already */
+	cnfa->nstates = 0;
+	FREE(cnfa->stflags);
+	FREE(cnfa->states);
+	FREE(cnfa->arcs);
+}
+
+/*
+ * dumpnfa - dump an NFA in human-readable form
+ */
+static void
+dumpnfa(struct nfa * nfa,
+		FILE *f)
+{
+#ifdef REG_DEBUG
+	struct state *s;
+	int			nstates = 0;
+	int			narcs = 0;
+
+	fprintf(f, "pre %d, post %d", nfa->pre->no, nfa->post->no);
+	if (nfa->bos[0] != COLORLESS)
+		fprintf(f, ", bos [%ld]", (long) nfa->bos[0]);
+	if (nfa->bos[1] != COLORLESS)
+		fprintf(f, ", bol [%ld]", (long) nfa->bos[1]);
+	if (nfa->eos[0] != COLORLESS)
+		fprintf(f, ", eos [%ld]", (long) nfa->eos[0]);
+	if (nfa->eos[1] != COLORLESS)
+		fprintf(f, ", eol [%ld]", (long) nfa->eos[1]);
+	fprintf(f, "\n");
+	for (s = nfa->states; s != NULL; s = s->next)
+	{
+		dumpstate(s, f);
+		nstates++;
+		narcs += s->nouts;
+	}
+	fprintf(f, "total of %d states, %d arcs\n", nstates, narcs);
+	if (nfa->parent == NULL)
+		dumpcolors(nfa->cm, f);
+	fflush(f);
+#endif
+}
+
+#ifdef REG_DEBUG				/* subordinates of dumpnfa */
+
+/*
+ * dumpstate - dump an NFA state in human-readable form
+ */
+static void
+dumpstate(struct state * s,
+		  FILE *f)
+{
+	struct arc *a;
+
+	fprintf(f, "%d%s%c", s->no, (s->tmp != NULL) ? "T" : "",
+			(s->flag) ? s->flag : '.');
+	if (s->prev != NULL && s->prev->next != s)
+		fprintf(f, "\tstate chain bad\n");
+	if (s->nouts == 0)
+		fprintf(f, "\tno out arcs\n");
+	else
+		dumparcs(s, f);
+	fflush(f);
+	for (a = s->ins; a != NULL; a = a->inchain)
+	{
+		if (a->to != s)
+			fprintf(f, "\tlink from %d to %d on %d's in-chain\n",
+					a->from->no, a->to->no, s->no);
+	}
+}
+
+/*
+ * dumparcs - dump out-arcs in human-readable form
+ */
+static void
+dumparcs(struct state * s,
+		 FILE *f)
+{
+	int			pos;
+	struct arc *a;
+
+	/* printing oldest arcs first is usually clearer */
+	a = s->outs;
+	assert(a != NULL);
+	while (a->outchain != NULL)
+		a = a->outchain;
+	pos = 1;
+	do
+	{
+		dumparc(a, s, f);
+		if (pos == 5)
+		{
+			fprintf(f, "\n");
+			pos = 1;
+		}
+		else
+			pos++;
+		a = a->outchainRev;
+	} while (a != NULL);
+	if (pos != 1)
+		fprintf(f, "\n");
+}
+
+/*
+ * dumparc - dump one outarc in readable form, including prefixing tab
+ */
+static void
+dumparc(struct arc * a,
+		struct state * s,
+		FILE *f)
+{
+	struct arc *aa;
+	struct arcbatch *ab;
+
+	fprintf(f, "\t");
+	switch (a->type)
+	{
+		case PLAIN:
+			fprintf(f, "[%ld]", (long) a->co);
+			break;
+		case AHEAD:
+			fprintf(f, ">%ld>", (long) a->co);
+			break;
+		case BEHIND:
+			fprintf(f, "<%ld<", (long) a->co);
+			break;
+		case LACON:
+			fprintf(f, ":%ld:", (long) a->co);
+			break;
+		case '^':
+		case '$':
+			fprintf(f, "%c%d", a->type, (int) a->co);
+			break;
+		case EMPTY:
+			break;
+		default:
+			fprintf(f, "0x%x/0%lo", a->type, (long) a->co);
+			break;
+	}
+	if (a->from != s)
+		fprintf(f, "?%d?", a->from->no);
+	for (ab = &a->from->oas; ab != NULL; ab = ab->next)
+	{
+		for (aa = &ab->a[0]; aa < &ab->a[ABSIZE]; aa++)
+			if (aa == a)
+				break;			/* NOTE BREAK OUT */
+		if (aa < &ab->a[ABSIZE])	/* propagate break */
+			break;				/* NOTE BREAK OUT */
+	}
+	if (ab == NULL)
+		fprintf(f, "?!?");		/* not in allocated space */
+	fprintf(f, "->");
+	if (a->to == NULL)
+	{
+		fprintf(f, "NULL");
+		return;
+	}
+	fprintf(f, "%d", a->to->no);
+	for (aa = a->to->ins; aa != NULL; aa = aa->inchain)
+		if (aa == a)
+			break;				/* NOTE BREAK OUT */
+	if (aa == NULL)
+		fprintf(f, "?!?");		/* missing from in-chain */
+}
+#endif   /* REG_DEBUG */
+
+/*
+ * dumpcnfa - dump a compacted NFA in human-readable form
+ */
+#ifdef REG_DEBUG
+static void
+dumpcnfa(struct cnfa * cnfa,
+		 FILE *f)
+{
+	int			st;
+
+	fprintf(f, "pre %d, post %d", cnfa->pre, cnfa->post);
+	if (cnfa->bos[0] != COLORLESS)
+		fprintf(f, ", bos [%ld]", (long) cnfa->bos[0]);
+	if (cnfa->bos[1] != COLORLESS)
+		fprintf(f, ", bol [%ld]", (long) cnfa->bos[1]);
+	if (cnfa->eos[0] != COLORLESS)
+		fprintf(f, ", eos [%ld]", (long) cnfa->eos[0]);
+	if (cnfa->eos[1] != COLORLESS)
+		fprintf(f, ", eol [%ld]", (long) cnfa->eos[1]);
+	if (cnfa->flags & HASLACONS)
+		fprintf(f, ", haslacons");
+	fprintf(f, "\n");
+	for (st = 0; st < cnfa->nstates; st++)
+		dumpcstate(st, cnfa, f);
+	fflush(f);
+}
+#endif
+
+#ifdef REG_DEBUG				/* subordinates of dumpcnfa */
+
+/*
+ * dumpcstate - dump a compacted-NFA state in human-readable form
+ */
+static void
+dumpcstate(int st,
+		   struct cnfa * cnfa,
+		   FILE *f)
+{
+	struct carc *ca;
+	int			pos;
+
+	fprintf(f, "%d%s", st, (cnfa->stflags[st] & CNFA_NOPROGRESS) ? ":" : ".");
+	pos = 1;
+	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+	{
+		if (ca->co < cnfa->ncolors)
+			fprintf(f, "\t[%ld]->%d", (long) ca->co, ca->to);
+		else
+			fprintf(f, "\t:%ld:->%d", (long) (ca->co - cnfa->ncolors), ca->to);
+		if (pos == 5)
+		{
+			fprintf(f, "\n");
+			pos = 1;
+		}
+		else
+			pos++;
+	}
+	if (ca == cnfa->states[st] || pos != 1)
+		fprintf(f, "\n");
+	fflush(f);
+}
+
+#endif   /* REG_DEBUG */
diff --git a/src/common/regex/regc_pg_locale.c b/src/common/regex/regc_pg_locale.c
new file mode 100644
index 0000000..a631ac2
--- /dev/null
+++ b/src/common/regex/regc_pg_locale.c
@@ -0,0 +1,885 @@
+/*-------------------------------------------------------------------------
+ *
+ * regc_pg_locale.c
+ *	  ctype functions adapted to work on pg_wchar (a/k/a chr),
+ *	  and functions to cache the results of wholesale ctype probing.
+ *
+ * This file is #included by regcomp.c; it's not meant to compile standalone.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/regex/regc_pg_locale.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "catalog/pg_collation.h"
+#include "utils/pg_locale.h"
+
+/*
+ * To provide as much functionality as possible on a variety of platforms,
+ * without going so far as to implement everything from scratch, we use
+ * several implementation strategies depending on the situation:
+ *
+ * 1. In C/POSIX collations, we use hard-wired code.  We can't depend on
+ * the <ctype.h> functions since those will obey LC_CTYPE.  Note that these
+ * collations don't give a fig about multibyte characters.
+ *
+ * 2. In the "default" collation (which is supposed to obey LC_CTYPE):
+ *
+ * 2a. When working in UTF8 encoding, we use the <wctype.h> functions if
+ * available.  This assumes that every platform uses Unicode codepoints
+ * directly as the wchar_t representation of Unicode.  On some platforms
+ * wchar_t is only 16 bits wide, so we have to punt for codepoints > 0xFFFF.
+ *
+ * 2b. In all other encodings, or on machines that lack <wctype.h>, we use
+ * the <ctype.h> functions for pg_wchar values up to 255, and punt for values
+ * above that.  This is only 100% correct in single-byte encodings such as
+ * LATINn.  However, non-Unicode multibyte encodings are mostly Far Eastern
+ * character sets for which the properties being tested here aren't very
+ * relevant for higher code values anyway.  The difficulty with using the
+ * <wctype.h> functions with non-Unicode multibyte encodings is that we can
+ * have no certainty that the platform's wchar_t representation matches
+ * what we do in pg_wchar conversions.
+ *
+ * 3. Other collations are only supported on platforms that HAVE_LOCALE_T.
+ * Here, we use the locale_t-extended forms of the <wctype.h> and <ctype.h>
+ * functions, under exactly the same cases as #2.
+ *
+ * There is one notable difference between cases 2 and 3: in the "default"
+ * collation we force ASCII letters to follow ASCII upcase/downcase rules,
+ * while in a non-default collation we just let the library functions do what
+ * they will.  The case where this matters is treatment of I/i in Turkish,
+ * and the behavior is meant to match the upper()/lower() SQL functions.
+ *
+ * We store the active collation setting in static variables.  In principle
+ * it could be passed down to here via the regex library's "struct vars" data
+ * structure; but that would require somewhat invasive changes in the regex
+ * library, and right now there's no real benefit to be gained from that.
+ *
+ * NB: the coding here assumes pg_wchar is an unsigned type.
+ */
+
+typedef enum
+{
+	PG_REGEX_LOCALE_C,			/* C locale (encoding independent) */
+	PG_REGEX_LOCALE_WIDE,		/* Use <wctype.h> functions */
+	PG_REGEX_LOCALE_1BYTE,		/* Use <ctype.h> functions */
+	PG_REGEX_LOCALE_WIDE_L,		/* Use locale_t <wctype.h> functions */
+	PG_REGEX_LOCALE_1BYTE_L		/* Use locale_t <ctype.h> functions */
+} PG_Locale_Strategy;
+
+static PG_Locale_Strategy pg_regex_strategy;
+static pg_locale_t pg_regex_locale;
+static Oid	pg_regex_collation;
+
+/*
+ * Hard-wired character properties for C locale
+ */
+#define PG_ISDIGIT	0x01
+#define PG_ISALPHA	0x02
+#define PG_ISALNUM	(PG_ISDIGIT | PG_ISALPHA)
+#define PG_ISUPPER	0x04
+#define PG_ISLOWER	0x08
+#define PG_ISGRAPH	0x10
+#define PG_ISPRINT	0x20
+#define PG_ISPUNCT	0x40
+#define PG_ISSPACE	0x80
+
+static const unsigned char pg_char_properties[128] = {
+	 /* NUL */ 0,
+	 /* ^A */ 0,
+	 /* ^B */ 0,
+	 /* ^C */ 0,
+	 /* ^D */ 0,
+	 /* ^E */ 0,
+	 /* ^F */ 0,
+	 /* ^G */ 0,
+	 /* ^H */ 0,
+	 /* ^I */ PG_ISSPACE,
+	 /* ^J */ PG_ISSPACE,
+	 /* ^K */ PG_ISSPACE,
+	 /* ^L */ PG_ISSPACE,
+	 /* ^M */ PG_ISSPACE,
+	 /* ^N */ 0,
+	 /* ^O */ 0,
+	 /* ^P */ 0,
+	 /* ^Q */ 0,
+	 /* ^R */ 0,
+	 /* ^S */ 0,
+	 /* ^T */ 0,
+	 /* ^U */ 0,
+	 /* ^V */ 0,
+	 /* ^W */ 0,
+	 /* ^X */ 0,
+	 /* ^Y */ 0,
+	 /* ^Z */ 0,
+	 /* ^[ */ 0,
+	 /* ^\ */ 0,
+	 /* ^] */ 0,
+	 /* ^^ */ 0,
+	 /* ^_ */ 0,
+	 /* */ PG_ISPRINT | PG_ISSPACE,
+	 /* !  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* "  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* #  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* $  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* %  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* &  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* '  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* (  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* )  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* *  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* +  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ,  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* -  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* .  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* /  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* 0  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 1  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 2  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 3  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 4  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 5  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 6  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 7  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 8  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* 9  */ PG_ISDIGIT | PG_ISGRAPH | PG_ISPRINT,
+	 /* :  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ;  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* <  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* =  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* >  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ?  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* @  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* A  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* B  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* C  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* D  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* E  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* F  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* G  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* H  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* I  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* J  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* K  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* L  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* M  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* N  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* O  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* P  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* Q  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* R  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* S  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* T  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* U  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* V  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* W  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* X  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* Y  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* Z  */ PG_ISALPHA | PG_ISUPPER | PG_ISGRAPH | PG_ISPRINT,
+	 /* [  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* \  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ]  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ^  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* _  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* `  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* a  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* b  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* c  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* d  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* e  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* f  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* g  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* h  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* i  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* j  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* k  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* l  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* m  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* n  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* o  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* p  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* q  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* r  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* s  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* t  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* u  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* v  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* w  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* x  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* y  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* z  */ PG_ISALPHA | PG_ISLOWER | PG_ISGRAPH | PG_ISPRINT,
+	 /* {  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* |  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* }  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* ~  */ PG_ISGRAPH | PG_ISPRINT | PG_ISPUNCT,
+	 /* DEL */ 0
+};
+
+
+/* Get rid of using server-side feature elsewhere of server. */
+#ifdef FRONTEND
+#define lc_ctype_is_c(col) (true)
+#define GetDatabaseEncoding() PG_UTF8
+#define ereport(x,...) exit(1)
+#endif
+
+/*
+ * pg_set_regex_collation: set collation for these functions to obey
+ *
+ * This is called when beginning compilation or execution of a regexp.
+ * Since there's no need for re-entrancy of regexp operations, it's okay
+ * to store the results in static variables.
+ */
+void
+pg_set_regex_collation(Oid collation)
+{
+	if (lc_ctype_is_c(collation))
+	{
+		/* C/POSIX collations use this path regardless of database encoding */
+		pg_regex_strategy = PG_REGEX_LOCALE_C;
+		pg_regex_locale = 0;
+		pg_regex_collation = C_COLLATION_OID;
+	}
+	else
+	{
+		if (collation == DEFAULT_COLLATION_OID)
+			pg_regex_locale = 0;
+		else if (OidIsValid(collation))
+		{
+			/*
+			 * NB: pg_newlocale_from_collation will fail if not HAVE_LOCALE_T;
+			 * the case of pg_regex_locale != 0 but not HAVE_LOCALE_T does not
+			 * have to be considered below.
+			 */
+			pg_regex_locale = pg_newlocale_from_collation(collation);
+		}
+		else
+		{
+			/*
+			 * This typically means that the parser could not resolve a
+			 * conflict of implicit collations, so report it that way.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_INDETERMINATE_COLLATION),
+					 errmsg("could not determine which collation to use for regular expression"),
+					 errhint("Use the COLLATE clause to set the collation explicitly.")));
+		}
+
+#ifdef USE_WIDE_UPPER_LOWER
+		if (GetDatabaseEncoding() == PG_UTF8)
+		{
+			if (pg_regex_locale)
+				pg_regex_strategy = PG_REGEX_LOCALE_WIDE_L;
+			else
+				pg_regex_strategy = PG_REGEX_LOCALE_WIDE;
+		}
+		else
+#endif   /* USE_WIDE_UPPER_LOWER */
+		{
+			if (pg_regex_locale)
+				pg_regex_strategy = PG_REGEX_LOCALE_1BYTE_L;
+			else
+				pg_regex_strategy = PG_REGEX_LOCALE_1BYTE;
+		}
+
+		pg_regex_collation = collation;
+	}
+}
+
+static int
+pg_wc_isdigit(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISDIGIT));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswdigit((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isdigit((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswdigit_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isdigit_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isalpha(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISALPHA));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswalpha((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isalpha((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswalpha_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isalpha_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isalnum(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISALNUM));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswalnum((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isalnum((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswalnum_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isalnum_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isupper(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISUPPER));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswupper((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isupper((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswupper_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isupper_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_islower(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISLOWER));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswlower((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					islower((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswlower_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					islower_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isgraph(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISGRAPH));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswgraph((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isgraph((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswgraph_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isgraph_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isprint(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISPRINT));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswprint((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isprint((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswprint_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isprint_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_ispunct(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISPUNCT));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswpunct((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					ispunct((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswpunct_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					ispunct_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static int
+pg_wc_isspace(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			return (c <= (pg_wchar) 127 &&
+					(pg_char_properties[c] & PG_ISSPACE));
+		case PG_REGEX_LOCALE_WIDE:
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswspace((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isspace((unsigned char) c));
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return iswspace_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			return (c <= (pg_wchar) UCHAR_MAX &&
+					isspace_l((unsigned char) c, pg_regex_locale));
+#endif
+			break;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static pg_wchar
+pg_wc_toupper(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_toupper((unsigned char) c);
+			return c;
+		case PG_REGEX_LOCALE_WIDE:
+			/* force C behavior for ASCII characters, per comments above */
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_toupper((unsigned char) c);
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return towupper((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			/* force C behavior for ASCII characters, per comments above */
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_toupper((unsigned char) c);
+			if (c <= (pg_wchar) UCHAR_MAX)
+				return toupper((unsigned char) c);
+			return c;
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return towupper_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			if (c <= (pg_wchar) UCHAR_MAX)
+				return toupper_l((unsigned char) c, pg_regex_locale);
+#endif
+			return c;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+static pg_wchar
+pg_wc_tolower(pg_wchar c)
+{
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_tolower((unsigned char) c);
+			return c;
+		case PG_REGEX_LOCALE_WIDE:
+			/* force C behavior for ASCII characters, per comments above */
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_tolower((unsigned char) c);
+#ifdef USE_WIDE_UPPER_LOWER
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return towlower((wint_t) c);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE:
+			/* force C behavior for ASCII characters, per comments above */
+			if (c <= (pg_wchar) 127)
+				return pg_ascii_tolower((unsigned char) c);
+			if (c <= (pg_wchar) UCHAR_MAX)
+				return tolower((unsigned char) c);
+			return c;
+		case PG_REGEX_LOCALE_WIDE_L:
+#if defined(HAVE_LOCALE_T) && defined(USE_WIDE_UPPER_LOWER)
+			if (sizeof(wchar_t) >= 4 || c <= (pg_wchar) 0xFFFF)
+				return towlower_l((wint_t) c, pg_regex_locale);
+#endif
+			/* FALL THRU */
+		case PG_REGEX_LOCALE_1BYTE_L:
+#ifdef HAVE_LOCALE_T
+			if (c <= (pg_wchar) UCHAR_MAX)
+				return tolower_l((unsigned char) c, pg_regex_locale);
+#endif
+			return c;
+	}
+	return 0;					/* can't get here, but keep compiler quiet */
+}
+
+
+/*
+ * These functions cache the results of probing libc's ctype behavior for
+ * all character codes of interest in a given encoding/collation.  The
+ * result is provided as a "struct cvec", but notice that the representation
+ * is a touch different from a cvec created by regc_cvec.c: we allocate the
+ * chrs[] and ranges[] arrays separately from the struct so that we can
+ * realloc them larger at need.  This is okay since the cvecs made here
+ * should never be freed by freecvec().
+ *
+ * We use malloc not palloc since we mustn't lose control on out-of-memory;
+ * the main regex code expects us to return a failure indication instead.
+ */
+
+typedef int (*pg_wc_probefunc) (pg_wchar c);
+
+typedef struct pg_ctype_cache
+{
+	pg_wc_probefunc probefunc;	/* pg_wc_isalpha or a sibling */
+	Oid			collation;		/* collation this entry is for */
+	struct cvec cv;				/* cache entry contents */
+	struct pg_ctype_cache *next;	/* chain link */
+} pg_ctype_cache;
+
+static pg_ctype_cache *pg_ctype_cache_list = NULL;
+
+/*
+ * Add a chr or range to pcc->cv; return false if run out of memory
+ */
+static bool
+store_match(pg_ctype_cache *pcc, pg_wchar chr1, int nchrs)
+{
+	chr		   *newchrs;
+
+	if (nchrs > 1)
+	{
+		if (pcc->cv.nranges >= pcc->cv.rangespace)
+		{
+			pcc->cv.rangespace *= 2;
+			newchrs = (chr *) realloc(pcc->cv.ranges,
+									  pcc->cv.rangespace * sizeof(chr) * 2);
+			if (newchrs == NULL)
+				return false;
+			pcc->cv.ranges = newchrs;
+		}
+		pcc->cv.ranges[pcc->cv.nranges * 2] = chr1;
+		pcc->cv.ranges[pcc->cv.nranges * 2 + 1] = chr1 + nchrs - 1;
+		pcc->cv.nranges++;
+	}
+	else
+	{
+		assert(nchrs == 1);
+		if (pcc->cv.nchrs >= pcc->cv.chrspace)
+		{
+			pcc->cv.chrspace *= 2;
+			newchrs = (chr *) realloc(pcc->cv.chrs,
+									  pcc->cv.chrspace * sizeof(chr));
+			if (newchrs == NULL)
+				return false;
+			pcc->cv.chrs = newchrs;
+		}
+		pcc->cv.chrs[pcc->cv.nchrs++] = chr1;
+	}
+	return true;
+}
+
+/*
+ * Given a probe function (e.g., pg_wc_isalpha) get a struct cvec for all
+ * chrs satisfying the probe function.  The active collation is the one
+ * previously set by pg_set_regex_collation.  Return NULL if out of memory.
+ *
+ * Note that the result must not be freed or modified by caller.
+ */
+static struct cvec *
+pg_ctype_get_cache(pg_wc_probefunc probefunc)
+{
+	pg_ctype_cache *pcc;
+	pg_wchar	max_chr;
+	pg_wchar	cur_chr;
+	int			nmatches;
+	chr		   *newchrs;
+
+	/*
+	 * Do we already have the answer cached?
+	 */
+	for (pcc = pg_ctype_cache_list; pcc != NULL; pcc = pcc->next)
+	{
+		if (pcc->probefunc == probefunc &&
+			pcc->collation == pg_regex_collation)
+			return &pcc->cv;
+	}
+
+	/*
+	 * Nope, so initialize some workspace ...
+	 */
+	pcc = (pg_ctype_cache *) malloc(sizeof(pg_ctype_cache));
+	if (pcc == NULL)
+		return NULL;
+	pcc->probefunc = probefunc;
+	pcc->collation = pg_regex_collation;
+	pcc->cv.nchrs = 0;
+	pcc->cv.chrspace = 128;
+	pcc->cv.chrs = (chr *) malloc(pcc->cv.chrspace * sizeof(chr));
+	pcc->cv.nranges = 0;
+	pcc->cv.rangespace = 64;
+	pcc->cv.ranges = (chr *) malloc(pcc->cv.rangespace * sizeof(chr) * 2);
+	if (pcc->cv.chrs == NULL || pcc->cv.ranges == NULL)
+		goto out_of_memory;
+
+	/*
+	 * Decide how many character codes we ought to look through.  For C locale
+	 * there's no need to go further than 127.  Otherwise, if the encoding is
+	 * UTF8 go up to 0x7FF, which is a pretty arbitrary cutoff but we cannot
+	 * extend it as far as we'd like (say, 0xFFFF, the end of the Basic
+	 * Multilingual Plane) without creating significant performance issues due
+	 * to too many characters being fed through the colormap code.  This will
+	 * need redesign to fix reasonably, but at least for the moment we have
+	 * all common European languages covered.  Otherwise (not C, not UTF8) go
+	 * up to 255.  These limits are interrelated with restrictions discussed
+	 * at the head of this file.
+	 */
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			max_chr = (pg_wchar) 127;
+			break;
+		case PG_REGEX_LOCALE_WIDE:
+		case PG_REGEX_LOCALE_WIDE_L:
+			max_chr = (pg_wchar) 0x7FF;
+			break;
+		case PG_REGEX_LOCALE_1BYTE:
+		case PG_REGEX_LOCALE_1BYTE_L:
+			max_chr = (pg_wchar) UCHAR_MAX;
+			break;
+		default:
+			max_chr = 0;		/* can't get here, but keep compiler quiet */
+			break;
+	}
+
+	/*
+	 * And scan 'em ...
+	 */
+	nmatches = 0;				/* number of consecutive matches */
+
+	for (cur_chr = 0; cur_chr <= max_chr; cur_chr++)
+	{
+		if ((*probefunc) (cur_chr))
+			nmatches++;
+		else if (nmatches > 0)
+		{
+			if (!store_match(pcc, cur_chr - nmatches, nmatches))
+				goto out_of_memory;
+			nmatches = 0;
+		}
+	}
+
+	if (nmatches > 0)
+		if (!store_match(pcc, cur_chr - nmatches, nmatches))
+			goto out_of_memory;
+
+	/*
+	 * We might have allocated more memory than needed, if so free it
+	 */
+	if (pcc->cv.nchrs == 0)
+	{
+		free(pcc->cv.chrs);
+		pcc->cv.chrs = NULL;
+		pcc->cv.chrspace = 0;
+	}
+	else if (pcc->cv.nchrs < pcc->cv.chrspace)
+	{
+		newchrs = (chr *) realloc(pcc->cv.chrs,
+								  pcc->cv.nchrs * sizeof(chr));
+		if (newchrs == NULL)
+			goto out_of_memory;
+		pcc->cv.chrs = newchrs;
+		pcc->cv.chrspace = pcc->cv.nchrs;
+	}
+	if (pcc->cv.nranges == 0)
+	{
+		free(pcc->cv.ranges);
+		pcc->cv.ranges = NULL;
+		pcc->cv.rangespace = 0;
+	}
+	else if (pcc->cv.nranges < pcc->cv.rangespace)
+	{
+		newchrs = (chr *) realloc(pcc->cv.ranges,
+								  pcc->cv.nranges * sizeof(chr) * 2);
+		if (newchrs == NULL)
+			goto out_of_memory;
+		pcc->cv.ranges = newchrs;
+		pcc->cv.rangespace = pcc->cv.nranges;
+	}
+
+	/*
+	 * Success, link it into cache chain
+	 */
+	pcc->next = pg_ctype_cache_list;
+	pg_ctype_cache_list = pcc;
+
+	return &pcc->cv;
+
+	/*
+	 * Failure, clean up
+	 */
+out_of_memory:
+	if (pcc->cv.chrs)
+		free(pcc->cv.chrs);
+	if (pcc->cv.ranges)
+		free(pcc->cv.ranges);
+	free(pcc);
+
+	return NULL;
+}
diff --git a/src/common/regex/regcomp.c b/src/common/regex/regcomp.c
new file mode 100644
index 0000000..34d256b
--- /dev/null
+++ b/src/common/regex/regcomp.c
@@ -0,0 +1,2234 @@
+/*
+ * re_*comp and friends - compile REs
+ * This file #includes several others (see the bottom).
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regcomp.c
+ *
+ */
+
+#include "regex/regguts.h"
+
+#include "miscadmin.h"			/* needed by rcancelrequested/rstacktoodeep */
+
+/*
+ * forward declarations, up here so forward datatypes etc. are defined early
+ */
+/* === regcomp.c === */
+static void moresubs(struct vars *, int);
+static int	freev(struct vars *, int);
+static void makesearch(struct vars *, struct nfa *);
+static struct subre *parse(struct vars *, int, int, struct state *, struct state *);
+static struct subre *parsebranch(struct vars *, int, int, struct state *, struct state *, int);
+static void parseqatom(struct vars *, int, int, struct state *, struct state *, struct subre *);
+static void nonword(struct vars *, int, struct state *, struct state *);
+static void word(struct vars *, int, struct state *, struct state *);
+static int	scannum(struct vars *);
+static void repeat(struct vars *, struct state *, struct state *, int, int);
+static void bracket(struct vars *, struct state *, struct state *);
+static void cbracket(struct vars *, struct state *, struct state *);
+static void brackpart(struct vars *, struct state *, struct state *);
+static const chr *scanplain(struct vars *);
+static void onechr(struct vars *, chr, struct state *, struct state *);
+static void dovec(struct vars *, struct cvec *, struct state *, struct state *);
+static void wordchrs(struct vars *);
+static void processlacon(struct vars *, struct state *, struct state *, int,
+			 struct state *, struct state *);
+static struct subre *subre(struct vars *, int, int, struct state *, struct state *);
+static void freesubre(struct vars *, struct subre *);
+static void freesrnode(struct vars *, struct subre *);
+static void optst(struct vars *, struct subre *);
+static int	numst(struct subre *, int);
+static void markst(struct subre *);
+static void cleanst(struct vars *);
+static long nfatree(struct vars *, struct subre *, FILE *);
+static long nfanode(struct vars *, struct subre *, int, FILE *);
+static int	newlacon(struct vars *, struct state *, struct state *, int);
+static void freelacons(struct subre *, int);
+static void rfree(regex_t *);
+static int	rcancelrequested(void);
+static int	rstacktoodeep(void);
+
+#ifdef REG_DEBUG
+static void dump(regex_t *, FILE *);
+static void dumpst(struct subre *, FILE *, int);
+static void stdump(struct subre *, FILE *, int);
+static const char *stid(struct subre *, char *, size_t);
+#endif
+/* === regc_lex.c === */
+static void lexstart(struct vars *);
+static void prefixes(struct vars *);
+static void lexnest(struct vars *, const chr *, const chr *);
+static void lexword(struct vars *);
+static int	next(struct vars *);
+static int	lexescape(struct vars *);
+static chr	lexdigits(struct vars *, int, int, int);
+static int	brenext(struct vars *, chr);
+static void skip(struct vars *);
+static chr	newline(void);
+static chr	chrnamed(struct vars *, const chr *, const chr *, chr);
+
+/* === regc_color.c === */
+static void initcm(struct vars *, struct colormap *);
+static void freecm(struct colormap *);
+static void cmtreefree(struct colormap *, union tree *, int);
+static color setcolor(struct colormap *, chr, pcolor);
+static color maxcolor(struct colormap *);
+static color newcolor(struct colormap *);
+static void freecolor(struct colormap *, pcolor);
+static color pseudocolor(struct colormap *);
+static color subcolor(struct colormap *, chr c);
+static color newsub(struct colormap *, pcolor);
+static void subrange(struct vars *, chr, chr, struct state *, struct state *);
+static void subblock(struct vars *, chr, struct state *, struct state *);
+static void okcolors(struct nfa *, struct colormap *);
+static void colorchain(struct colormap *, struct arc *);
+static void uncolorchain(struct colormap *, struct arc *);
+static void rainbow(struct nfa *, struct colormap *, int, pcolor, struct state *, struct state *);
+static void colorcomplement(struct nfa *, struct colormap *, int, struct state *, struct state *, struct state *);
+
+#ifdef REG_DEBUG
+static void dumpcolors(struct colormap *, FILE *);
+static void fillcheck(struct colormap *, union tree *, int, FILE *);
+static void dumpchr(chr, FILE *);
+#endif
+/* === regc_nfa.c === */
+static struct nfa *newnfa(struct vars *, struct colormap *, struct nfa *);
+static void freenfa(struct nfa *);
+static struct state *newstate(struct nfa *);
+static struct state *newfstate(struct nfa *, int flag);
+static void dropstate(struct nfa *, struct state *);
+static void freestate(struct nfa *, struct state *);
+static void destroystate(struct nfa *, struct state *);
+static void newarc(struct nfa *, int, pcolor, struct state *, struct state *);
+static void createarc(struct nfa *, int, pcolor, struct state *, struct state *);
+static struct arc *allocarc(struct nfa *, struct state *);
+static void freearc(struct nfa *, struct arc *);
+static void changearctarget(struct arc *, struct state *);
+static int	hasnonemptyout(struct state *);
+static struct arc *findarc(struct state *, int, pcolor);
+static void cparc(struct nfa *, struct arc *, struct state *, struct state *);
+static void sortins(struct nfa *, struct state *);
+static int	sortins_cmp(const void *, const void *);
+static void sortouts(struct nfa *, struct state *);
+static int	sortouts_cmp(const void *, const void *);
+static void moveins(struct nfa *, struct state *, struct state *);
+static void copyins(struct nfa *, struct state *, struct state *);
+static void mergeins(struct nfa *, struct state *, struct arc **, int);
+static void moveouts(struct nfa *, struct state *, struct state *);
+static void copyouts(struct nfa *, struct state *, struct state *);
+static void cloneouts(struct nfa *, struct state *, struct state *, struct state *, int);
+static void delsub(struct nfa *, struct state *, struct state *);
+static void deltraverse(struct nfa *, struct state *, struct state *);
+static void dupnfa(struct nfa *, struct state *, struct state *, struct state *, struct state *);
+static void duptraverse(struct nfa *, struct state *, struct state *);
+static void cleartraverse(struct nfa *, struct state *);
+static struct state *single_color_transition(struct state *, struct state *);
+static void specialcolors(struct nfa *);
+static long optimize(struct nfa *, FILE *);
+static void pullback(struct nfa *, FILE *);
+static int	pull(struct nfa *, struct arc *, struct state **);
+static void pushfwd(struct nfa *, FILE *);
+static int	push(struct nfa *, struct arc *, struct state **);
+
+#define INCOMPATIBLE	1		/* destroys arc */
+#define SATISFIED	2			/* constraint satisfied */
+#define COMPATIBLE	3			/* compatible but not satisfied yet */
+static int	combine(struct arc *, struct arc *);
+static void fixempties(struct nfa *, FILE *);
+static struct state *emptyreachable(struct nfa *, struct state *,
+			   struct state *, struct arc **);
+static int	isconstraintarc(struct arc *);
+static int	hasconstraintout(struct state *);
+static void fixconstraintloops(struct nfa *, FILE *);
+static int	findconstraintloop(struct nfa *, struct state *);
+static void breakconstraintloop(struct nfa *, struct state *);
+static void clonesuccessorstates(struct nfa *, struct state *, struct state *,
+					 struct state *, struct arc *,
+					 char *, char *, int);
+static void cleanup(struct nfa *);
+static void markreachable(struct nfa *, struct state *, struct state *, struct state *);
+static void markcanreach(struct nfa *, struct state *, struct state *, struct state *);
+static long analyze(struct nfa *);
+static void compact(struct nfa *, struct cnfa *);
+static void carcsort(struct carc *, size_t);
+static int	carc_cmp(const void *, const void *);
+static void freecnfa(struct cnfa *);
+static void dumpnfa(struct nfa *, FILE *);
+
+#ifdef REG_DEBUG
+static void dumpstate(struct state *, FILE *);
+static void dumparcs(struct state *, FILE *);
+static void dumparc(struct arc *, struct state *, FILE *);
+static void dumpcnfa(struct cnfa *, FILE *);
+static void dumpcstate(int, struct cnfa *, FILE *);
+#endif
+/* === regc_cvec.c === */
+static struct cvec *newcvec(int, int);
+static struct cvec *clearcvec(struct cvec *);
+static void addchr(struct cvec *, chr);
+static void addrange(struct cvec *, chr, chr);
+static struct cvec *getcvec(struct vars *, int, int);
+static void freecvec(struct cvec *);
+
+/* === regc_pg_locale.c === */
+static int	pg_wc_isdigit(pg_wchar c);
+static int	pg_wc_isalpha(pg_wchar c);
+static int	pg_wc_isalnum(pg_wchar c);
+static int	pg_wc_isupper(pg_wchar c);
+static int	pg_wc_islower(pg_wchar c);
+static int	pg_wc_isgraph(pg_wchar c);
+static int	pg_wc_isprint(pg_wchar c);
+static int	pg_wc_ispunct(pg_wchar c);
+static int	pg_wc_isspace(pg_wchar c);
+static pg_wchar pg_wc_toupper(pg_wchar c);
+static pg_wchar pg_wc_tolower(pg_wchar c);
+
+/* === regc_locale.c === */
+static celt element(struct vars *, const chr *, const chr *);
+static struct cvec *range(struct vars *, celt, celt, int);
+static int	before(celt, celt);
+static struct cvec *eclass(struct vars *, celt, int);
+static struct cvec *cclass(struct vars *, const chr *, const chr *, int);
+static struct cvec *allcases(struct vars *, chr);
+static int	cmp(const chr *, const chr *, size_t);
+static int	casecmp(const chr *, const chr *, size_t);
+
+
+/* internal variables, bundled for easy passing around */
+struct vars
+{
+	regex_t    *re;
+	const chr  *now;			/* scan pointer into string */
+	const chr  *stop;			/* end of string */
+	const chr  *savenow;		/* saved now and stop for "subroutine call" */
+	const chr  *savestop;
+	int			err;			/* error code (0 if none) */
+	int			cflags;			/* copy of compile flags */
+	int			lasttype;		/* type of previous token */
+	int			nexttype;		/* type of next token */
+	chr			nextvalue;		/* value (if any) of next token */
+	int			lexcon;			/* lexical context type (see lex.c) */
+	int			nsubexp;		/* subexpression count */
+	struct subre **subs;		/* subRE pointer vector */
+	size_t		nsubs;			/* length of vector */
+	struct subre *sub10[10];	/* initial vector, enough for most */
+	struct nfa *nfa;			/* the NFA */
+	struct colormap *cm;		/* character color map */
+	color		nlcolor;		/* color of newline */
+	struct state *wordchrs;		/* state in nfa holding word-char outarcs */
+	struct subre *tree;			/* subexpression tree */
+	struct subre *treechain;	/* all tree nodes allocated */
+	struct subre *treefree;		/* any free tree nodes */
+	int			ntree;			/* number of tree nodes, plus one */
+	struct cvec *cv;			/* interface cvec */
+	struct cvec *cv2;			/* utility cvec */
+	struct subre *lacons;		/* lookaround-constraint vector */
+	int			nlacons;		/* size of lacons[]; note that only slots
+								 * numbered 1 .. nlacons-1 are used */
+	size_t		spaceused;		/* approx. space used for compilation */
+};
+
+/* parsing macros; most know that `v' is the struct vars pointer */
+#define NEXT()	(next(v))		/* advance by one token */
+#define SEE(t)	(v->nexttype == (t))	/* is next token this? */
+#define EAT(t)	(SEE(t) && next(v))		/* if next is this, swallow it */
+#define VISERR(vv)	((vv)->err != 0)	/* have we seen an error yet? */
+#define ISERR() VISERR(v)
+#define VERR(vv,e)	((vv)->nexttype = EOS, \
+					 (vv)->err = ((vv)->err ? (vv)->err : (e)))
+#define ERR(e)	VERR(v, e)		/* record an error */
+#define NOERR() {if (ISERR()) return;}	/* if error seen, return */
+#define NOERRN()	{if (ISERR()) return NULL;} /* NOERR with retval */
+#define NOERRZ()	{if (ISERR()) return 0;}	/* NOERR with retval */
+#define INSIST(c, e) do { if (!(c)) ERR(e); } while (0) /* error if c false */
+#define NOTE(b) (v->re->re_info |= (b)) /* note visible condition */
+#define EMPTYARC(x, y)	newarc(v->nfa, EMPTY, 0, x, y)
+
+/* token type codes, some also used as NFA arc types */
+#define EMPTY	'n'				/* no token present */
+#define EOS 'e'					/* end of string */
+#define PLAIN	'p'				/* ordinary character */
+#define DIGIT	'd'				/* digit (in bound) */
+#define BACKREF 'b'				/* back reference */
+#define COLLEL	'I'				/* start of [. */
+#define ECLASS	'E'				/* start of [= */
+#define CCLASS	'C'				/* start of [: */
+#define END 'X'					/* end of [. [= [: */
+#define RANGE	'R'				/* - within [] which might be range delim. */
+#define LACON	'L'				/* lookaround constraint subRE */
+#define AHEAD	'a'				/* color-lookahead arc */
+#define BEHIND	'r'				/* color-lookbehind arc */
+#define WBDRY	'w'				/* word boundary constraint */
+#define NWBDRY	'W'				/* non-word-boundary constraint */
+#define SBEGIN	'A'				/* beginning of string (even if not BOL) */
+#define SEND	'Z'				/* end of string (even if not EOL) */
+#define PREFER	'P'				/* length preference */
+
+/* is an arc colored, and hence on a color chain? */
+#define COLORED(a) \
+	((a)->type == PLAIN || (a)->type == AHEAD || (a)->type == BEHIND)
+
+
+/* static function list as a default */
+static const reg_callbacks def_callbacks = {
+	rcancelrequested,			/* check for cancel request */
+	rstacktoodeep				/* check for stack getting dangerously deep */
+};
+
+
+
+/*
+ * pg_regcomp - compile regular expression
+ *
+ * Note: on failure, no resources remain allocated, so pg_regfree()
+ * need not be applied to re.
+ */
+int
+pg_regcomp(regex_t *re,
+		   const chr *string,
+		   size_t len,
+		   int flags,
+		   Oid collation,
+		   reg_callbacks const *callbacks)
+{
+	struct vars var;
+	struct vars *v = &var;
+	struct guts *g;
+	int			i;
+	size_t		j;
+
+#ifdef REG_DEBUG
+	FILE	   *debug = (flags & REG_PROGRESS) ? stdout : (FILE *) NULL;
+#else
+	FILE	   *debug = (FILE *) NULL;
+#endif
+
+#define  CNOERR()	 { if (ISERR()) return freev(v, v->err); }
+
+	/* sanity checks */
+
+	if (re == NULL || string == NULL)
+		return REG_INVARG;
+	if ((flags & REG_QUOTE) &&
+		(flags & (REG_ADVANCED | REG_EXPANDED | REG_NEWLINE)))
+		return REG_INVARG;
+	if (!(flags & REG_EXTENDED) && (flags & REG_ADVF))
+		return REG_INVARG;
+
+	/* Initialize locale-dependent support */
+	pg_set_regex_collation(collation);
+
+	/* initial setup (after which freev() is callable) */
+	v->re = re;
+	v->now = string;
+	v->stop = v->now + len;
+	v->savenow = v->savestop = NULL;
+	v->err = 0;
+	v->cflags = flags;
+	v->nsubexp = 0;
+	v->subs = v->sub10;
+	v->nsubs = 10;
+	for (j = 0; j < v->nsubs; j++)
+		v->subs[j] = NULL;
+	v->nfa = NULL;
+	v->cm = NULL;
+	v->nlcolor = COLORLESS;
+	v->wordchrs = NULL;
+	v->tree = NULL;
+	v->treechain = NULL;
+	v->treefree = NULL;
+	v->cv = NULL;
+	v->cv2 = NULL;
+	v->lacons = NULL;
+	v->nlacons = 0;
+	v->spaceused = 0;
+	re->re_magic = REMAGIC;
+	re->re_info = 0;			/* bits get set during parse */
+	re->re_csize = sizeof(chr);
+	re->re_collation = collation;
+	re->re_guts = NULL;
+	re->free = rfree;
+	re->re_fns = (callbacks ? callbacks : &def_callbacks);
+
+	/* more complex setup, malloced things */
+	re->re_guts = VS(MALLOC(sizeof(struct guts)));
+	if (re->re_guts == NULL)
+		return freev(v, REG_ESPACE);
+	g = (struct guts *) re->re_guts;
+	g->tree = NULL;
+	initcm(v, &g->cmap);
+	v->cm = &g->cmap;
+	g->lacons = NULL;
+	g->nlacons = 0;
+	ZAPCNFA(g->search);
+	v->nfa = newnfa(v, v->cm, (struct nfa *) NULL);
+	CNOERR();
+	/* set up a reasonably-sized transient cvec for getcvec usage */
+	v->cv = newcvec(100, 20);
+	if (v->cv == NULL)
+		return freev(v, REG_ESPACE);
+
+	/* parsing */
+	lexstart(v);				/* also handles prefixes */
+	if ((v->cflags & REG_NLSTOP) || (v->cflags & REG_NLANCH))
+	{
+		/* assign newline a unique color */
+		v->nlcolor = subcolor(v->cm, newline());
+		okcolors(v->nfa, v->cm);
+	}
+	CNOERR();
+	v->tree = parse(v, EOS, PLAIN, v->nfa->init, v->nfa->final);
+	assert(SEE(EOS));			/* even if error; ISERR() => SEE(EOS) */
+	CNOERR();
+	assert(v->tree != NULL);
+
+	/* finish setup of nfa and its subre tree */
+	specialcolors(v->nfa);
+	CNOERR();
+#ifdef REG_DEBUG
+	if (debug != NULL)
+	{
+		fprintf(debug, "\n\n\n========= RAW ==========\n");
+		dumpnfa(v->nfa, debug);
+		dumpst(v->tree, debug, 1);
+	}
+#endif
+	optst(v, v->tree);
+	v->ntree = numst(v->tree, 1);
+	markst(v->tree);
+	cleanst(v);
+#ifdef REG_DEBUG
+	if (debug != NULL)
+	{
+		fprintf(debug, "\n\n\n========= TREE FIXED ==========\n");
+		dumpst(v->tree, debug, 1);
+	}
+#endif
+
+	/* build compacted NFAs for tree and lacons */
+	re->re_info |= nfatree(v, v->tree, debug);
+	CNOERR();
+	assert(v->nlacons == 0 || v->lacons != NULL);
+	for (i = 1; i < v->nlacons; i++)
+	{
+		struct subre *lasub = &v->lacons[i];
+
+#ifdef REG_DEBUG
+		if (debug != NULL)
+			fprintf(debug, "\n\n\n========= LA%d ==========\n", i);
+#endif
+
+		/* Prepend .* to pattern if it's a lookbehind LACON */
+		nfanode(v, lasub, !LATYPE_IS_AHEAD(lasub->subno), debug);
+	}
+	CNOERR();
+	if (v->tree->flags & SHORTER)
+		NOTE(REG_USHORTEST);
+
+	/* build compacted NFAs for tree, lacons, fast search */
+#ifdef REG_DEBUG
+	if (debug != NULL)
+		fprintf(debug, "\n\n\n========= SEARCH ==========\n");
+#endif
+	/* can sacrifice main NFA now, so use it as work area */
+	(DISCARD) optimize(v->nfa, debug);
+	CNOERR();
+	makesearch(v, v->nfa);
+	CNOERR();
+	compact(v->nfa, &g->search);
+	CNOERR();
+
+	/* looks okay, package it up */
+	re->re_nsub = v->nsubexp;
+	v->re = NULL;				/* freev no longer frees re */
+	g->magic = GUTSMAGIC;
+	g->cflags = v->cflags;
+	g->info = re->re_info;
+	g->nsub = re->re_nsub;
+	g->tree = v->tree;
+	v->tree = NULL;
+	g->ntree = v->ntree;
+	g->compare = (v->cflags & REG_ICASE) ? casecmp : cmp;
+	g->lacons = v->lacons;
+	v->lacons = NULL;
+	g->nlacons = v->nlacons;
+
+#ifdef REG_DEBUG
+	if (flags & REG_DUMP)
+		dump(re, stdout);
+#endif
+
+	assert(v->err == 0);
+	return freev(v, 0);
+}
+
+/*
+ * moresubs - enlarge subRE vector
+ */
+static void
+moresubs(struct vars * v,
+		 int wanted)			/* want enough room for this one */
+{
+	struct subre **p;
+	size_t		n;
+
+	assert(wanted > 0 && (size_t) wanted >= v->nsubs);
+	n = (size_t) wanted *3 / 2 + 1;
+
+	if (v->subs == v->sub10)
+	{
+		p = (struct subre **) MALLOC(n * sizeof(struct subre *));
+		if (p != NULL)
+			memcpy(VS(p), VS(v->subs),
+				   v->nsubs * sizeof(struct subre *));
+	}
+	else
+		p = (struct subre **) REALLOC(v->subs, n * sizeof(struct subre *));
+	if (p == NULL)
+	{
+		ERR(REG_ESPACE);
+		return;
+	}
+	v->subs = p;
+	for (p = &v->subs[v->nsubs]; v->nsubs < n; p++, v->nsubs++)
+		*p = NULL;
+	assert(v->nsubs == n);
+	assert((size_t) wanted < v->nsubs);
+}
+
+/*
+ * freev - free vars struct's substructures where necessary
+ *
+ * Optionally does error-number setting, and always returns error code
+ * (if any), to make error-handling code terser.
+ */
+static int
+freev(struct vars * v,
+	  int err)
+{
+	if (v->re != NULL)
+		rfree(v->re);
+	if (v->subs != v->sub10)
+		FREE(v->subs);
+	if (v->nfa != NULL)
+		freenfa(v->nfa);
+	if (v->tree != NULL)
+		freesubre(v, v->tree);
+	if (v->treechain != NULL)
+		cleanst(v);
+	if (v->cv != NULL)
+		freecvec(v->cv);
+	if (v->cv2 != NULL)
+		freecvec(v->cv2);
+	if (v->lacons != NULL)
+		freelacons(v->lacons, v->nlacons);
+	ERR(err);					/* nop if err==0 */
+
+	return v->err;
+}
+
+/*
+ * makesearch - turn an NFA into a search NFA (implicit prepend of .*?)
+ * NFA must have been optimize()d already.
+ */
+static void
+makesearch(struct vars * v,
+		   struct nfa * nfa)
+{
+	struct arc *a;
+	struct arc *b;
+	struct state *pre = nfa->pre;
+	struct state *s;
+	struct state *s2;
+	struct state *slist;
+
+	/* no loops are needed if it's anchored */
+	for (a = pre->outs; a != NULL; a = a->outchain)
+	{
+		assert(a->type == PLAIN);
+		if (a->co != nfa->bos[0] && a->co != nfa->bos[1])
+			break;
+	}
+	if (a != NULL)
+	{
+		/* add implicit .* in front */
+		rainbow(nfa, v->cm, PLAIN, COLORLESS, pre, pre);
+
+		/* and ^* and \A* too -- not always necessary, but harmless */
+		newarc(nfa, PLAIN, nfa->bos[0], pre, pre);
+		newarc(nfa, PLAIN, nfa->bos[1], pre, pre);
+	}
+
+	/*
+	 * Now here's the subtle part.  Because many REs have no lookback
+	 * constraints, often knowing when you were in the pre state tells you
+	 * little; it's the next state(s) that are informative.  But some of them
+	 * may have other inarcs, i.e. it may be possible to make actual progress
+	 * and then return to one of them.  We must de-optimize such cases,
+	 * splitting each such state into progress and no-progress states.
+	 */
+
+	/* first, make a list of the states reachable from pre and elsewhere */
+	slist = NULL;
+	for (a = pre->outs; a != NULL; a = a->outchain)
+	{
+		s = a->to;
+		for (b = s->ins; b != NULL; b = b->inchain)
+		{
+			if (b->from != pre)
+				break;
+		}
+
+		/*
+		 * We want to mark states as being in the list already by having non
+		 * NULL tmp fields, but we can't just store the old slist value in tmp
+		 * because that doesn't work for the first such state.  Instead, the
+		 * first list entry gets its own address in tmp.
+		 */
+		if (b != NULL && s->tmp == NULL)
+		{
+			s->tmp = (slist != NULL) ? slist : s;
+			slist = s;
+		}
+	}
+
+	/* do the splits */
+	for (s = slist; s != NULL; s = s2)
+	{
+		s2 = newstate(nfa);
+		NOERR();
+		copyouts(nfa, s, s2);
+		NOERR();
+		for (a = s->ins; a != NULL; a = b)
+		{
+			b = a->inchain;
+			if (a->from != pre)
+			{
+				cparc(nfa, a, a->from, s2);
+				freearc(nfa, a);
+			}
+		}
+		s2 = (s->tmp != s) ? s->tmp : NULL;
+		s->tmp = NULL;			/* clean up while we're at it */
+	}
+}
+
+/*
+ * parse - parse an RE
+ *
+ * This is actually just the top level, which parses a bunch of branches
+ * tied together with '|'.  They appear in the tree as the left children
+ * of a chain of '|' subres.
+ */
+static struct subre *
+parse(struct vars * v,
+	  int stopper,				/* EOS or ')' */
+	  int type,					/* LACON (lookaround subRE) or PLAIN */
+	  struct state * init,		/* initial state */
+	  struct state * final)		/* final state */
+{
+	struct state *left;			/* scaffolding for branch */
+	struct state *right;
+	struct subre *branches;		/* top level */
+	struct subre *branch;		/* current branch */
+	struct subre *t;			/* temporary */
+	int			firstbranch;	/* is this the first branch? */
+
+	assert(stopper == ')' || stopper == EOS);
+
+	branches = subre(v, '|', LONGER, init, final);
+	NOERRN();
+	branch = branches;
+	firstbranch = 1;
+	do
+	{							/* a branch */
+		if (!firstbranch)
+		{
+			/* need a place to hang it */
+			branch->right = subre(v, '|', LONGER, init, final);
+			NOERRN();
+			branch = branch->right;
+		}
+		firstbranch = 0;
+		left = newstate(v->nfa);
+		right = newstate(v->nfa);
+		NOERRN();
+		EMPTYARC(init, left);
+		EMPTYARC(right, final);
+		NOERRN();
+		branch->left = parsebranch(v, stopper, type, left, right, 0);
+		NOERRN();
+		branch->flags |= UP(branch->flags | branch->left->flags);
+		if ((branch->flags & ~branches->flags) != 0)	/* new flags */
+			for (t = branches; t != branch; t = t->right)
+				t->flags |= branch->flags;
+	} while (EAT('|'));
+	assert(SEE(stopper) || SEE(EOS));
+
+	if (!SEE(stopper))
+	{
+		assert(stopper == ')' && SEE(EOS));
+		ERR(REG_EPAREN);
+	}
+
+	/* optimize out simple cases */
+	if (branch == branches)
+	{							/* only one branch */
+		assert(branch->right == NULL);
+		t = branch->left;
+		branch->left = NULL;
+		freesubre(v, branches);
+		branches = t;
+	}
+	else if (!MESSY(branches->flags))
+	{							/* no interesting innards */
+		freesubre(v, branches->left);
+		branches->left = NULL;
+		freesubre(v, branches->right);
+		branches->right = NULL;
+		branches->op = '=';
+	}
+
+	return branches;
+}
+
+/*
+ * parsebranch - parse one branch of an RE
+ *
+ * This mostly manages concatenation, working closely with parseqatom().
+ * Concatenated things are bundled up as much as possible, with separate
+ * ',' nodes introduced only when necessary due to substructure.
+ */
+static struct subre *
+parsebranch(struct vars * v,
+			int stopper,		/* EOS or ')' */
+			int type,			/* LACON (lookaround subRE) or PLAIN */
+			struct state * left,	/* leftmost state */
+			struct state * right,		/* rightmost state */
+			int partial)		/* is this only part of a branch? */
+{
+	struct state *lp;			/* left end of current construct */
+	int			seencontent;	/* is there anything in this branch yet? */
+	struct subre *t;
+
+	lp = left;
+	seencontent = 0;
+	t = subre(v, '=', 0, left, right);	/* op '=' is tentative */
+	NOERRN();
+	while (!SEE('|') && !SEE(stopper) && !SEE(EOS))
+	{
+		if (seencontent)
+		{						/* implicit concat operator */
+			lp = newstate(v->nfa);
+			NOERRN();
+			moveins(v->nfa, right, lp);
+		}
+		seencontent = 1;
+
+		/* NB, recursion in parseqatom() may swallow rest of branch */
+		parseqatom(v, stopper, type, lp, right, t);
+		NOERRN();
+	}
+
+	if (!seencontent)
+	{							/* empty branch */
+		if (!partial)
+			NOTE(REG_UUNSPEC);
+		assert(lp == left);
+		EMPTYARC(left, right);
+	}
+
+	return t;
+}
+
+/*
+ * parseqatom - parse one quantified atom or constraint of an RE
+ *
+ * The bookkeeping near the end cooperates very closely with parsebranch();
+ * in particular, it contains a recursion that can involve parsing the rest
+ * of the branch, making this function's name somewhat inaccurate.
+ */
+static void
+parseqatom(struct vars * v,
+		   int stopper,			/* EOS or ')' */
+		   int type,			/* LACON (lookaround subRE) or PLAIN */
+		   struct state * lp,	/* left state to hang it on */
+		   struct state * rp,	/* right state to hang it on */
+		   struct subre * top)	/* subtree top */
+{
+	struct state *s;			/* temporaries for new states */
+	struct state *s2;
+
+#define  ARCV(t, val)	 newarc(v->nfa, t, val, lp, rp)
+	int			m,
+				n;
+	struct subre *atom;			/* atom's subtree */
+	struct subre *t;
+	int			cap;			/* capturing parens? */
+	int			latype;			/* lookaround constraint type */
+	int			subno;			/* capturing-parens or backref number */
+	int			atomtype;
+	int			qprefer;		/* quantifier short/long preference */
+	int			f;
+	struct subre **atomp;		/* where the pointer to atom is */
+
+	/* initial bookkeeping */
+	atom = NULL;
+	assert(lp->nouts == 0);		/* must string new code */
+	assert(rp->nins == 0);		/* between lp and rp */
+	subno = 0;					/* just to shut lint up */
+
+	/* an atom or constraint... */
+	atomtype = v->nexttype;
+	switch (atomtype)
+	{
+			/* first, constraints, which end by returning */
+		case '^':
+			ARCV('^', 1);
+			if (v->cflags & REG_NLANCH)
+				ARCV(BEHIND, v->nlcolor);
+			NEXT();
+			return;
+			break;
+		case '$':
+			ARCV('$', 1);
+			if (v->cflags & REG_NLANCH)
+				ARCV(AHEAD, v->nlcolor);
+			NEXT();
+			return;
+			break;
+		case SBEGIN:
+			ARCV('^', 1);		/* BOL */
+			ARCV('^', 0);		/* or BOS */
+			NEXT();
+			return;
+			break;
+		case SEND:
+			ARCV('$', 1);		/* EOL */
+			ARCV('$', 0);		/* or EOS */
+			NEXT();
+			return;
+			break;
+		case '<':
+			wordchrs(v);		/* does NEXT() */
+			s = newstate(v->nfa);
+			NOERR();
+			nonword(v, BEHIND, lp, s);
+			word(v, AHEAD, s, rp);
+			return;
+			break;
+		case '>':
+			wordchrs(v);		/* does NEXT() */
+			s = newstate(v->nfa);
+			NOERR();
+			word(v, BEHIND, lp, s);
+			nonword(v, AHEAD, s, rp);
+			return;
+			break;
+		case WBDRY:
+			wordchrs(v);		/* does NEXT() */
+			s = newstate(v->nfa);
+			NOERR();
+			nonword(v, BEHIND, lp, s);
+			word(v, AHEAD, s, rp);
+			s = newstate(v->nfa);
+			NOERR();
+			word(v, BEHIND, lp, s);
+			nonword(v, AHEAD, s, rp);
+			return;
+			break;
+		case NWBDRY:
+			wordchrs(v);		/* does NEXT() */
+			s = newstate(v->nfa);
+			NOERR();
+			word(v, BEHIND, lp, s);
+			word(v, AHEAD, s, rp);
+			s = newstate(v->nfa);
+			NOERR();
+			nonword(v, BEHIND, lp, s);
+			nonword(v, AHEAD, s, rp);
+			return;
+			break;
+		case LACON:				/* lookaround constraint */
+			latype = v->nextvalue;
+			NEXT();
+			s = newstate(v->nfa);
+			s2 = newstate(v->nfa);
+			NOERR();
+			t = parse(v, ')', LACON, s, s2);
+			freesubre(v, t);	/* internal structure irrelevant */
+			NOERR();
+			assert(SEE(')'));
+			NEXT();
+			processlacon(v, s, s2, latype, lp, rp);
+			return;
+			break;
+			/* then errors, to get them out of the way */
+		case '*':
+		case '+':
+		case '?':
+		case '{':
+			ERR(REG_BADRPT);
+			return;
+			break;
+		default:
+			ERR(REG_ASSERT);
+			return;
+			break;
+			/* then plain characters, and minor variants on that theme */
+		case ')':				/* unbalanced paren */
+			if ((v->cflags & REG_ADVANCED) != REG_EXTENDED)
+			{
+				ERR(REG_EPAREN);
+				return;
+			}
+			/* legal in EREs due to specification botch */
+			NOTE(REG_UPBOTCH);
+			/* fallthrough into case PLAIN */
+		case PLAIN:
+			onechr(v, v->nextvalue, lp, rp);
+			okcolors(v->nfa, v->cm);
+			NOERR();
+			NEXT();
+			break;
+		case '[':
+			if (v->nextvalue == 1)
+				bracket(v, lp, rp);
+			else
+				cbracket(v, lp, rp);
+			assert(SEE(']') || ISERR());
+			NEXT();
+			break;
+		case '.':
+			rainbow(v->nfa, v->cm, PLAIN,
+					(v->cflags & REG_NLSTOP) ? v->nlcolor : COLORLESS,
+					lp, rp);
+			NEXT();
+			break;
+			/* and finally the ugly stuff */
+		case '(':				/* value flags as capturing or non */
+			cap = (type == LACON) ? 0 : v->nextvalue;
+			if (cap)
+			{
+				v->nsubexp++;
+				subno = v->nsubexp;
+				if ((size_t) subno >= v->nsubs)
+					moresubs(v, subno);
+				assert((size_t) subno < v->nsubs);
+			}
+			else
+				atomtype = PLAIN;		/* something that's not '(' */
+			NEXT();
+			/* need new endpoints because tree will contain pointers */
+			s = newstate(v->nfa);
+			s2 = newstate(v->nfa);
+			NOERR();
+			EMPTYARC(lp, s);
+			EMPTYARC(s2, rp);
+			NOERR();
+			atom = parse(v, ')', type, s, s2);
+			assert(SEE(')') || ISERR());
+			NEXT();
+			NOERR();
+			if (cap)
+			{
+				v->subs[subno] = atom;
+				t = subre(v, '(', atom->flags | CAP, lp, rp);
+				NOERR();
+				t->subno = subno;
+				t->left = atom;
+				atom = t;
+			}
+			/* postpone everything else pending possible {0} */
+			break;
+		case BACKREF:			/* the Feature From The Black Lagoon */
+			INSIST(type != LACON, REG_ESUBREG);
+			INSIST(v->nextvalue < v->nsubs, REG_ESUBREG);
+			INSIST(v->subs[v->nextvalue] != NULL, REG_ESUBREG);
+			NOERR();
+			assert(v->nextvalue > 0);
+			atom = subre(v, 'b', BACKR, lp, rp);
+			NOERR();
+			subno = v->nextvalue;
+			atom->subno = subno;
+			EMPTYARC(lp, rp);	/* temporarily, so there's something */
+			NEXT();
+			break;
+	}
+
+	/* ...and an atom may be followed by a quantifier */
+	switch (v->nexttype)
+	{
+		case '*':
+			m = 0;
+			n = DUPINF;
+			qprefer = (v->nextvalue) ? LONGER : SHORTER;
+			NEXT();
+			break;
+		case '+':
+			m = 1;
+			n = DUPINF;
+			qprefer = (v->nextvalue) ? LONGER : SHORTER;
+			NEXT();
+			break;
+		case '?':
+			m = 0;
+			n = 1;
+			qprefer = (v->nextvalue) ? LONGER : SHORTER;
+			NEXT();
+			break;
+		case '{':
+			NEXT();
+			m = scannum(v);
+			if (EAT(','))
+			{
+				if (SEE(DIGIT))
+					n = scannum(v);
+				else
+					n = DUPINF;
+				if (m > n)
+				{
+					ERR(REG_BADBR);
+					return;
+				}
+				/* {m,n} exercises preference, even if it's {m,m} */
+				qprefer = (v->nextvalue) ? LONGER : SHORTER;
+			}
+			else
+			{
+				n = m;
+				/* {m} passes operand's preference through */
+				qprefer = 0;
+			}
+			if (!SEE('}'))
+			{					/* catches errors too */
+				ERR(REG_BADBR);
+				return;
+			}
+			NEXT();
+			break;
+		default:				/* no quantifier */
+			m = n = 1;
+			qprefer = 0;
+			break;
+	}
+
+	/* annoying special case:  {0} or {0,0} cancels everything */
+	if (m == 0 && n == 0)
+	{
+		if (atom != NULL)
+			freesubre(v, atom);
+		if (atomtype == '(')
+			v->subs[subno] = NULL;
+		delsub(v->nfa, lp, rp);
+		EMPTYARC(lp, rp);
+		return;
+	}
+
+	/* if not a messy case, avoid hard part */
+	assert(!MESSY(top->flags));
+	f = top->flags | qprefer | ((atom != NULL) ? atom->flags : 0);
+	if (atomtype != '(' && atomtype != BACKREF && !MESSY(UP(f)))
+	{
+		if (!(m == 1 && n == 1))
+			repeat(v, lp, rp, m, n);
+		if (atom != NULL)
+			freesubre(v, atom);
+		top->flags = f;
+		return;
+	}
+
+	/*
+	 * hard part:  something messy
+	 *
+	 * That is, capturing parens, back reference, short/long clash, or an atom
+	 * with substructure containing one of those.
+	 */
+
+	/* now we'll need a subre for the contents even if they're boring */
+	if (atom == NULL)
+	{
+		atom = subre(v, '=', 0, lp, rp);
+		NOERR();
+	}
+
+	/*----------
+	 * Prepare a general-purpose state skeleton.
+	 *
+	 * In the no-backrefs case, we want this:
+	 *
+	 * [lp] ---> [s] ---prefix---> [begin] ---atom---> [end] ---rest---> [rp]
+	 *
+	 * where prefix is some repetitions of atom.  In the general case we need
+	 *
+	 * [lp] ---> [s] ---iterator---> [s2] ---rest---> [rp]
+	 *
+	 * where the iterator wraps around [begin] ---atom---> [end]
+	 *
+	 * We make the s state here for both cases; s2 is made below if needed
+	 *----------
+	 */
+	s = newstate(v->nfa);		/* first, new endpoints for the atom */
+	s2 = newstate(v->nfa);
+	NOERR();
+	moveouts(v->nfa, lp, s);
+	moveins(v->nfa, rp, s2);
+	NOERR();
+	atom->begin = s;
+	atom->end = s2;
+	s = newstate(v->nfa);		/* set up starting state */
+	NOERR();
+	EMPTYARC(lp, s);
+	NOERR();
+
+	/* break remaining subRE into x{...} and what follows */
+	t = subre(v, '.', COMBINE(qprefer, atom->flags), lp, rp);
+	NOERR();
+	t->left = atom;
+	atomp = &t->left;
+
+	/* here we should recurse... but we must postpone that to the end */
+
+	/* split top into prefix and remaining */
+	assert(top->op == '=' && top->left == NULL && top->right == NULL);
+	top->left = subre(v, '=', top->flags, top->begin, lp);
+	NOERR();
+	top->op = '.';
+	top->right = t;
+
+	/* if it's a backref, now is the time to replicate the subNFA */
+	if (atomtype == BACKREF)
+	{
+		assert(atom->begin->nouts == 1);		/* just the EMPTY */
+		delsub(v->nfa, atom->begin, atom->end);
+		assert(v->subs[subno] != NULL);
+
+		/*
+		 * And here's why the recursion got postponed: it must wait until the
+		 * skeleton is filled in, because it may hit a backref that wants to
+		 * copy the filled-in skeleton.
+		 */
+		dupnfa(v->nfa, v->subs[subno]->begin, v->subs[subno]->end,
+			   atom->begin, atom->end);
+		NOERR();
+	}
+
+	/*
+	 * It's quantifier time.  If the atom is just a backref, we'll let it deal
+	 * with quantifiers internally.
+	 */
+	if (atomtype == BACKREF)
+	{
+		/* special case:  backrefs have internal quantifiers */
+		EMPTYARC(s, atom->begin);		/* empty prefix */
+		/* just stuff everything into atom */
+		repeat(v, atom->begin, atom->end, m, n);
+		atom->min = (short) m;
+		atom->max = (short) n;
+		atom->flags |= COMBINE(qprefer, atom->flags);
+		/* rest of branch can be strung starting from atom->end */
+		s2 = atom->end;
+	}
+	else if (m == 1 && n == 1)
+	{
+		/* no/vacuous quantifier:  done */
+		EMPTYARC(s, atom->begin);		/* empty prefix */
+		/* rest of branch can be strung starting from atom->end */
+		s2 = atom->end;
+	}
+	else if (m > 0 && !(atom->flags & BACKR))
+	{
+		/*
+		 * If there's no backrefs involved, we can turn x{m,n} into
+		 * x{m-1,n-1}x, with capturing parens in only the second x.  This is
+		 * valid because we only care about capturing matches from the final
+		 * iteration of the quantifier.  It's a win because we can implement
+		 * the backref-free left side as a plain DFA node, since we don't
+		 * really care where its submatches are.
+		 */
+		dupnfa(v->nfa, atom->begin, atom->end, s, atom->begin);
+		assert(m >= 1 && m != DUPINF && n >= 1);
+		repeat(v, s, atom->begin, m - 1, (n == DUPINF) ? n : n - 1);
+		f = COMBINE(qprefer, atom->flags);
+		t = subre(v, '.', f, s, atom->end);		/* prefix and atom */
+		NOERR();
+		t->left = subre(v, '=', PREF(f), s, atom->begin);
+		NOERR();
+		t->right = atom;
+		*atomp = t;
+		/* rest of branch can be strung starting from atom->end */
+		s2 = atom->end;
+	}
+	else
+	{
+		/* general case: need an iteration node */
+		s2 = newstate(v->nfa);
+		NOERR();
+		moveouts(v->nfa, atom->end, s2);
+		NOERR();
+		dupnfa(v->nfa, atom->begin, atom->end, s, s2);
+		repeat(v, s, s2, m, n);
+		f = COMBINE(qprefer, atom->flags);
+		t = subre(v, '*', f, s, s2);
+		NOERR();
+		t->min = (short) m;
+		t->max = (short) n;
+		t->left = atom;
+		*atomp = t;
+		/* rest of branch is to be strung from iteration's end state */
+	}
+
+	/* and finally, look after that postponed recursion */
+	t = top->right;
+	if (!(SEE('|') || SEE(stopper) || SEE(EOS)))
+		t->right = parsebranch(v, stopper, type, s2, rp, 1);
+	else
+	{
+		EMPTYARC(s2, rp);
+		t->right = subre(v, '=', 0, s2, rp);
+	}
+	NOERR();
+	assert(SEE('|') || SEE(stopper) || SEE(EOS));
+	t->flags |= COMBINE(t->flags, t->right->flags);
+	top->flags |= COMBINE(top->flags, t->flags);
+}
+
+/*
+ * nonword - generate arcs for non-word-character ahead or behind
+ */
+static void
+nonword(struct vars * v,
+		int dir,				/* AHEAD or BEHIND */
+		struct state * lp,
+		struct state * rp)
+{
+	int			anchor = (dir == AHEAD) ? '$' : '^';
+
+	assert(dir == AHEAD || dir == BEHIND);
+	newarc(v->nfa, anchor, 1, lp, rp);
+	newarc(v->nfa, anchor, 0, lp, rp);
+	colorcomplement(v->nfa, v->cm, dir, v->wordchrs, lp, rp);
+	/* (no need for special attention to \n) */
+}
+
+/*
+ * word - generate arcs for word character ahead or behind
+ */
+static void
+word(struct vars * v,
+	 int dir,					/* AHEAD or BEHIND */
+	 struct state * lp,
+	 struct state * rp)
+{
+	assert(dir == AHEAD || dir == BEHIND);
+	cloneouts(v->nfa, v->wordchrs, lp, rp, dir);
+	/* (no need for special attention to \n) */
+}
+
+/*
+ * scannum - scan a number
+ */
+static int						/* value, <= DUPMAX */
+scannum(struct vars * v)
+{
+	int			n = 0;
+
+	while (SEE(DIGIT) && n < DUPMAX)
+	{
+		n = n * 10 + v->nextvalue;
+		NEXT();
+	}
+	if (SEE(DIGIT) || n > DUPMAX)
+	{
+		ERR(REG_BADBR);
+		return 0;
+	}
+	return n;
+}
+
+/*
+ * repeat - replicate subNFA for quantifiers
+ *
+ * The sub-NFA strung from lp to rp is modified to represent m to n
+ * repetitions of its initial contents.
+ *
+ * The duplication sequences used here are chosen carefully so that any
+ * pointers starting out pointing into the subexpression end up pointing into
+ * the last occurrence.  (Note that it may not be strung between the same
+ * left and right end states, however!)  This used to be important for the
+ * subRE tree, although the important bits are now handled by the in-line
+ * code in parse(), and when this is called, it doesn't matter any more.
+ */
+static void
+repeat(struct vars * v,
+	   struct state * lp,
+	   struct state * rp,
+	   int m,
+	   int n)
+{
+#define  SOME	 2
+#define  INF	 3
+#define  PAIR(x, y)  ((x)*4 + (y))
+#define  REDUCE(x)	 ( ((x) == DUPINF) ? INF : (((x) > 1) ? SOME : (x)) )
+	const int	rm = REDUCE(m);
+	const int	rn = REDUCE(n);
+	struct state *s;
+	struct state *s2;
+
+	switch (PAIR(rm, rn))
+	{
+		case PAIR(0, 0):		/* empty string */
+			delsub(v->nfa, lp, rp);
+			EMPTYARC(lp, rp);
+			break;
+		case PAIR(0, 1):		/* do as x| */
+			EMPTYARC(lp, rp);
+			break;
+		case PAIR(0, SOME):		/* do as x{1,n}| */
+			repeat(v, lp, rp, 1, n);
+			NOERR();
+			EMPTYARC(lp, rp);
+			break;
+		case PAIR(0, INF):		/* loop x around */
+			s = newstate(v->nfa);
+			NOERR();
+			moveouts(v->nfa, lp, s);
+			moveins(v->nfa, rp, s);
+			EMPTYARC(lp, s);
+			EMPTYARC(s, rp);
+			break;
+		case PAIR(1, 1):		/* no action required */
+			break;
+		case PAIR(1, SOME):		/* do as x{0,n-1}x = (x{1,n-1}|)x */
+			s = newstate(v->nfa);
+			NOERR();
+			moveouts(v->nfa, lp, s);
+			dupnfa(v->nfa, s, rp, lp, s);
+			NOERR();
+			repeat(v, lp, s, 1, n - 1);
+			NOERR();
+			EMPTYARC(lp, s);
+			break;
+		case PAIR(1, INF):		/* add loopback arc */
+			s = newstate(v->nfa);
+			s2 = newstate(v->nfa);
+			NOERR();
+			moveouts(v->nfa, lp, s);
+			moveins(v->nfa, rp, s2);
+			EMPTYARC(lp, s);
+			EMPTYARC(s2, rp);
+			EMPTYARC(s2, s);
+			break;
+		case PAIR(SOME, SOME):	/* do as x{m-1,n-1}x */
+			s = newstate(v->nfa);
+			NOERR();
+			moveouts(v->nfa, lp, s);
+			dupnfa(v->nfa, s, rp, lp, s);
+			NOERR();
+			repeat(v, lp, s, m - 1, n - 1);
+			break;
+		case PAIR(SOME, INF):	/* do as x{m-1,}x */
+			s = newstate(v->nfa);
+			NOERR();
+			moveouts(v->nfa, lp, s);
+			dupnfa(v->nfa, s, rp, lp, s);
+			NOERR();
+			repeat(v, lp, s, m - 1, n);
+			break;
+		default:
+			ERR(REG_ASSERT);
+			break;
+	}
+}
+
+/*
+ * bracket - handle non-complemented bracket expression
+ * Also called from cbracket for complemented bracket expressions.
+ */
+static void
+bracket(struct vars * v,
+		struct state * lp,
+		struct state * rp)
+{
+	assert(SEE('['));
+	NEXT();
+	while (!SEE(']') && !SEE(EOS))
+		brackpart(v, lp, rp);
+	assert(SEE(']') || ISERR());
+	okcolors(v->nfa, v->cm);
+}
+
+/*
+ * cbracket - handle complemented bracket expression
+ * We do it by calling bracket() with dummy endpoints, and then complementing
+ * the result.  The alternative would be to invoke rainbow(), and then delete
+ * arcs as the b.e. is seen... but that gets messy.
+ */
+static void
+cbracket(struct vars * v,
+		 struct state * lp,
+		 struct state * rp)
+{
+	struct state *left = newstate(v->nfa);
+	struct state *right = newstate(v->nfa);
+
+	NOERR();
+	bracket(v, left, right);
+	if (v->cflags & REG_NLSTOP)
+		newarc(v->nfa, PLAIN, v->nlcolor, left, right);
+	NOERR();
+
+	assert(lp->nouts == 0);		/* all outarcs will be ours */
+
+	/*
+	 * Easy part of complementing, and all there is to do since the MCCE code
+	 * was removed.
+	 */
+	colorcomplement(v->nfa, v->cm, PLAIN, left, lp, rp);
+	NOERR();
+	dropstate(v->nfa, left);
+	assert(right->nins == 0);
+	freestate(v->nfa, right);
+}
+
+/*
+ * brackpart - handle one item (or range) within a bracket expression
+ */
+static void
+brackpart(struct vars * v,
+		  struct state * lp,
+		  struct state * rp)
+{
+	celt		startc;
+	celt		endc;
+	struct cvec *cv;
+	const chr  *startp;
+	const chr  *endp;
+	chr			c[1];
+
+	/* parse something, get rid of special cases, take shortcuts */
+	switch (v->nexttype)
+	{
+		case RANGE:				/* a-b-c or other botch */
+			ERR(REG_ERANGE);
+			return;
+			break;
+		case PLAIN:
+			c[0] = v->nextvalue;
+			NEXT();
+			/* shortcut for ordinary chr (not range) */
+			if (!SEE(RANGE))
+			{
+				onechr(v, c[0], lp, rp);
+				return;
+			}
+			startc = element(v, c, c + 1);
+			NOERR();
+			break;
+		case COLLEL:
+			startp = v->now;
+			endp = scanplain(v);
+			INSIST(startp < endp, REG_ECOLLATE);
+			NOERR();
+			startc = element(v, startp, endp);
+			NOERR();
+			break;
+		case ECLASS:
+			startp = v->now;
+			endp = scanplain(v);
+			INSIST(startp < endp, REG_ECOLLATE);
+			NOERR();
+			startc = element(v, startp, endp);
+			NOERR();
+			cv = eclass(v, startc, (v->cflags & REG_ICASE));
+			NOERR();
+			dovec(v, cv, lp, rp);
+			return;
+			break;
+		case CCLASS:
+			startp = v->now;
+			endp = scanplain(v);
+			INSIST(startp < endp, REG_ECTYPE);
+			NOERR();
+			cv = cclass(v, startp, endp, (v->cflags & REG_ICASE));
+			NOERR();
+			dovec(v, cv, lp, rp);
+			return;
+			break;
+		default:
+			ERR(REG_ASSERT);
+			return;
+			break;
+	}
+
+	if (SEE(RANGE))
+	{
+		NEXT();
+		switch (v->nexttype)
+		{
+			case PLAIN:
+			case RANGE:
+				c[0] = v->nextvalue;
+				NEXT();
+				endc = element(v, c, c + 1);
+				NOERR();
+				break;
+			case COLLEL:
+				startp = v->now;
+				endp = scanplain(v);
+				INSIST(startp < endp, REG_ECOLLATE);
+				NOERR();
+				endc = element(v, startp, endp);
+				NOERR();
+				break;
+			default:
+				ERR(REG_ERANGE);
+				return;
+				break;
+		}
+	}
+	else
+		endc = startc;
+
+	/*
+	 * Ranges are unportable.  Actually, standard C does guarantee that digits
+	 * are contiguous, but making that an exception is just too complicated.
+	 */
+	if (startc != endc)
+		NOTE(REG_UUNPORT);
+	cv = range(v, startc, endc, (v->cflags & REG_ICASE));
+	NOERR();
+	dovec(v, cv, lp, rp);
+}
+
+/*
+ * scanplain - scan PLAIN contents of [. etc.
+ *
+ * Certain bits of trickery in lex.c know that this code does not try
+ * to look past the final bracket of the [. etc.
+ */
+static const chr *				/* just after end of sequence */
+scanplain(struct vars * v)
+{
+	const chr  *endp;
+
+	assert(SEE(COLLEL) || SEE(ECLASS) || SEE(CCLASS));
+	NEXT();
+
+	endp = v->now;
+	while (SEE(PLAIN))
+	{
+		endp = v->now;
+		NEXT();
+	}
+
+	assert(SEE(END) || ISERR());
+	NEXT();
+
+	return endp;
+}
+
+/*
+ * onechr - fill in arcs for a plain character, and possible case complements
+ * This is mostly a shortcut for efficient handling of the common case.
+ */
+static void
+onechr(struct vars * v,
+	   chr c,
+	   struct state * lp,
+	   struct state * rp)
+{
+	if (!(v->cflags & REG_ICASE))
+	{
+		newarc(v->nfa, PLAIN, subcolor(v->cm, c), lp, rp);
+		return;
+	}
+
+	/* rats, need general case anyway... */
+	dovec(v, allcases(v, c), lp, rp);
+}
+
+/*
+ * dovec - fill in arcs for each element of a cvec
+ */
+static void
+dovec(struct vars * v,
+	  struct cvec * cv,
+	  struct state * lp,
+	  struct state * rp)
+{
+	chr			ch,
+				from,
+				to;
+	const chr  *p;
+	int			i;
+
+	/* ordinary characters */
+	for (p = cv->chrs, i = cv->nchrs; i > 0; p++, i--)
+	{
+		ch = *p;
+		newarc(v->nfa, PLAIN, subcolor(v->cm, ch), lp, rp);
+	}
+
+	/* and the ranges */
+	for (p = cv->ranges, i = cv->nranges; i > 0; p += 2, i--)
+	{
+		from = *p;
+		to = *(p + 1);
+		if (from <= to)
+			subrange(v, from, to, lp, rp);
+	}
+}
+
+/*
+ * wordchrs - set up word-chr list for word-boundary stuff, if needed
+ *
+ * The list is kept as a bunch of arcs between two dummy states; it's
+ * disposed of by the unreachable-states sweep in NFA optimization.
+ * Does NEXT().  Must not be called from any unusual lexical context.
+ * This should be reconciled with the \w etc. handling in lex.c, and
+ * should be cleaned up to reduce dependencies on input scanning.
+ */
+static void
+wordchrs(struct vars * v)
+{
+	struct state *left;
+	struct state *right;
+
+	if (v->wordchrs != NULL)
+	{
+		NEXT();					/* for consistency */
+		return;
+	}
+
+	left = newstate(v->nfa);
+	right = newstate(v->nfa);
+	NOERR();
+	/* fine point:	implemented with [::], and lexer will set REG_ULOCALE */
+	lexword(v);
+	NEXT();
+	assert(v->savenow != NULL && SEE('['));
+	bracket(v, left, right);
+	assert((v->savenow != NULL && SEE(']')) || ISERR());
+	NEXT();
+	NOERR();
+	v->wordchrs = left;
+}
+
+/*
+ * processlacon - generate the NFA representation of a LACON
+ *
+ * In the general case this is just newlacon() + newarc(), but some cases
+ * can be optimized.
+ */
+static void
+processlacon(struct vars * v,
+			 struct state * begin,		/* start of parsed LACON sub-re */
+			 struct state * end,	/* end of parsed LACON sub-re */
+			 int latype,
+			 struct state * lp, /* left state to hang it on */
+			 struct state * rp) /* right state to hang it on */
+{
+	struct state *s1;
+	int			n;
+
+	/*
+	 * Check for lookaround RE consisting of a single plain color arc (or set
+	 * of arcs); this would typically be a simple chr or a bracket expression.
+	 */
+	s1 = single_color_transition(begin, end);
+	switch (latype)
+	{
+		case LATYPE_AHEAD_POS:
+			/* If lookahead RE is just colorset C, convert to AHEAD(C) */
+			if (s1 != NULL)
+			{
+				cloneouts(v->nfa, s1, lp, rp, AHEAD);
+				return;
+			}
+			break;
+		case LATYPE_AHEAD_NEG:
+			/* If lookahead RE is just colorset C, convert to AHEAD(^C)|$ */
+			if (s1 != NULL)
+			{
+				colorcomplement(v->nfa, v->cm, AHEAD, s1, lp, rp);
+				newarc(v->nfa, '$', 1, lp, rp);
+				newarc(v->nfa, '$', 0, lp, rp);
+				return;
+			}
+			break;
+		case LATYPE_BEHIND_POS:
+			/* If lookbehind RE is just colorset C, convert to BEHIND(C) */
+			if (s1 != NULL)
+			{
+				cloneouts(v->nfa, s1, lp, rp, BEHIND);
+				return;
+			}
+			break;
+		case LATYPE_BEHIND_NEG:
+			/* If lookbehind RE is just colorset C, convert to BEHIND(^C)|^ */
+			if (s1 != NULL)
+			{
+				colorcomplement(v->nfa, v->cm, BEHIND, s1, lp, rp);
+				newarc(v->nfa, '^', 1, lp, rp);
+				newarc(v->nfa, '^', 0, lp, rp);
+				return;
+			}
+			break;
+		default:
+			assert(NOTREACHED);
+	}
+
+	/* General case: we need a LACON subre and arc */
+	n = newlacon(v, begin, end, latype);
+	newarc(v->nfa, LACON, n, lp, rp);
+}
+
+/*
+ * subre - allocate a subre
+ */
+static struct subre *
+subre(struct vars * v,
+	  int op,
+	  int flags,
+	  struct state * begin,
+	  struct state * end)
+{
+	struct subre *ret = v->treefree;
+
+	/*
+	 * Checking for stack overflow here is sufficient to protect parse() and
+	 * its recursive subroutines.
+	 */
+	if (STACK_TOO_DEEP(v->re))
+	{
+		ERR(REG_ETOOBIG);
+		return NULL;
+	}
+
+	if (ret != NULL)
+		v->treefree = ret->left;
+	else
+	{
+		ret = (struct subre *) MALLOC(sizeof(struct subre));
+		if (ret == NULL)
+		{
+			ERR(REG_ESPACE);
+			return NULL;
+		}
+		ret->chain = v->treechain;
+		v->treechain = ret;
+	}
+
+	assert(strchr("=b|.*(", op) != NULL);
+
+	ret->op = op;
+	ret->flags = flags;
+	ret->id = 0;				/* will be assigned later */
+	ret->subno = 0;
+	ret->min = ret->max = 1;
+	ret->left = NULL;
+	ret->right = NULL;
+	ret->begin = begin;
+	ret->end = end;
+	ZAPCNFA(ret->cnfa);
+
+	return ret;
+}
+
+/*
+ * freesubre - free a subRE subtree
+ */
+static void
+freesubre(struct vars * v,		/* might be NULL */
+		  struct subre * sr)
+{
+	if (sr == NULL)
+		return;
+
+	if (sr->left != NULL)
+		freesubre(v, sr->left);
+	if (sr->right != NULL)
+		freesubre(v, sr->right);
+
+	freesrnode(v, sr);
+}
+
+/*
+ * freesrnode - free one node in a subRE subtree
+ */
+static void
+freesrnode(struct vars * v,		/* might be NULL */
+		   struct subre * sr)
+{
+	if (sr == NULL)
+		return;
+
+	if (!NULLCNFA(sr->cnfa))
+		freecnfa(&sr->cnfa);
+	sr->flags = 0;
+
+	if (v != NULL && v->treechain != NULL)
+	{
+		/* we're still parsing, maybe we can reuse the subre */
+		sr->left = v->treefree;
+		v->treefree = sr;
+	}
+	else
+		FREE(sr);
+}
+
+/*
+ * optst - optimize a subRE subtree
+ */
+static void
+optst(struct vars * v,
+	  struct subre * t)
+{
+	/*
+	 * DGP (2007-11-13): I assume it was the programmer's intent to eventually
+	 * come back and add code to optimize subRE trees, but the routine coded
+	 * just spends effort traversing the tree and doing nothing. We can do
+	 * nothing with less effort.
+	 */
+	return;
+}
+
+/*
+ * numst - number tree nodes (assigning "id" indexes)
+ */
+static int						/* next number */
+numst(struct subre * t,
+	  int start)				/* starting point for subtree numbers */
+{
+	int			i;
+
+	assert(t != NULL);
+
+	i = start;
+	t->id = (short) i++;
+	if (t->left != NULL)
+		i = numst(t->left, i);
+	if (t->right != NULL)
+		i = numst(t->right, i);
+	return i;
+}
+
+/*
+ * markst - mark tree nodes as INUSE
+ *
+ * Note: this is a great deal more subtle than it looks.  During initial
+ * parsing of a regex, all subres are linked into the treechain list;
+ * discarded ones are also linked into the treefree list for possible reuse.
+ * After we are done creating all subres required for a regex, we run markst()
+ * then cleanst(), which results in discarding all subres not reachable from
+ * v->tree.  We then clear v->treechain, indicating that subres must be found
+ * by descending from v->tree.  This changes the behavior of freesubre(): it
+ * will henceforth FREE() unwanted subres rather than sticking them into the
+ * treefree list.  (Doing that any earlier would result in dangling links in
+ * the treechain list.)  This all means that freev() will clean up correctly
+ * if invoked before or after markst()+cleanst(); but it would not work if
+ * called partway through this state conversion, so we mustn't error out
+ * in or between these two functions.
+ */
+static void
+markst(struct subre * t)
+{
+	assert(t != NULL);
+
+	t->flags |= INUSE;
+	if (t->left != NULL)
+		markst(t->left);
+	if (t->right != NULL)
+		markst(t->right);
+}
+
+/*
+ * cleanst - free any tree nodes not marked INUSE
+ */
+static void
+cleanst(struct vars * v)
+{
+	struct subre *t;
+	struct subre *next;
+
+	for (t = v->treechain; t != NULL; t = next)
+	{
+		next = t->chain;
+		if (!(t->flags & INUSE))
+			FREE(t);
+	}
+	v->treechain = NULL;
+	v->treefree = NULL;			/* just on general principles */
+}
+
+/*
+ * nfatree - turn a subRE subtree into a tree of compacted NFAs
+ */
+static long						/* optimize results from top node */
+nfatree(struct vars * v,
+		struct subre * t,
+		FILE *f)				/* for debug output */
+{
+	assert(t != NULL && t->begin != NULL);
+
+	if (t->left != NULL)
+		(DISCARD) nfatree(v, t->left, f);
+	if (t->right != NULL)
+		(DISCARD) nfatree(v, t->right, f);
+
+	return nfanode(v, t, 0, f);
+}
+
+/*
+ * nfanode - do one NFA for nfatree or lacons
+ *
+ * If converttosearch is true, apply makesearch() to the NFA.
+ */
+static long						/* optimize results */
+nfanode(struct vars * v,
+		struct subre * t,
+		int converttosearch,
+		FILE *f)				/* for debug output */
+{
+	struct nfa *nfa;
+	long		ret = 0;
+
+	assert(t->begin != NULL);
+
+#ifdef REG_DEBUG
+	if (f != NULL)
+	{
+		char		idbuf[50];
+
+		fprintf(f, "\n\n\n========= TREE NODE %s ==========\n",
+				stid(t, idbuf, sizeof(idbuf)));
+	}
+#endif
+	nfa = newnfa(v, v->cm, v->nfa);
+	NOERRZ();
+	dupnfa(nfa, t->begin, t->end, nfa->init, nfa->final);
+	if (!ISERR())
+		specialcolors(nfa);
+	if (!ISERR())
+		ret = optimize(nfa, f);
+	if (converttosearch && !ISERR())
+		makesearch(v, nfa);
+	if (!ISERR())
+		compact(nfa, &t->cnfa);
+
+	freenfa(nfa);
+	return ret;
+}
+
+/*
+ * newlacon - allocate a lookaround-constraint subRE
+ */
+static int						/* lacon number */
+newlacon(struct vars * v,
+		 struct state * begin,
+		 struct state * end,
+		 int latype)
+{
+	int			n;
+	struct subre *newlacons;
+	struct subre *sub;
+
+	if (v->nlacons == 0)
+	{
+		n = 1;					/* skip 0th */
+		newlacons = (struct subre *) MALLOC(2 * sizeof(struct subre));
+	}
+	else
+	{
+		n = v->nlacons;
+		newlacons = (struct subre *) REALLOC(v->lacons,
+											 (n + 1) * sizeof(struct subre));
+	}
+	if (newlacons == NULL)
+	{
+		ERR(REG_ESPACE);
+		return 0;
+	}
+	v->lacons = newlacons;
+	v->nlacons = n + 1;
+	sub = &v->lacons[n];
+	sub->begin = begin;
+	sub->end = end;
+	sub->subno = latype;
+	ZAPCNFA(sub->cnfa);
+	return n;
+}
+
+/*
+ * freelacons - free lookaround-constraint subRE vector
+ */
+static void
+freelacons(struct subre * subs,
+		   int n)
+{
+	struct subre *sub;
+	int			i;
+
+	assert(n > 0);
+	for (sub = subs + 1, i = n - 1; i > 0; sub++, i--)	/* no 0th */
+		if (!NULLCNFA(sub->cnfa))
+			freecnfa(&sub->cnfa);
+	FREE(subs);
+}
+
+/*
+ * rfree - free a whole RE (insides of regfree)
+ */
+static void
+rfree(regex_t *re)
+{
+	struct guts *g;
+
+	if (re == NULL || re->re_magic != REMAGIC)
+		return;
+
+	re->re_magic = 0;			/* invalidate RE */
+	g = (struct guts *) re->re_guts;
+	re->re_guts = NULL;
+	re->re_fns = NULL;
+	if (g != NULL)
+	{
+		g->magic = 0;
+		freecm(&g->cmap);
+		if (g->tree != NULL)
+			freesubre((struct vars *) NULL, g->tree);
+		if (g->lacons != NULL)
+			freelacons(g->lacons, g->nlacons);
+		if (!NULLCNFA(g->search))
+			freecnfa(&g->search);
+		FREE(g);
+	}
+}
+
+/*
+ * rcancelrequested - check for external request to cancel regex operation
+ *
+ * Return nonzero to fail the operation with error code REG_CANCEL,
+ * zero to keep going
+ *
+ * The current implementation is Postgres-specific.  If we ever get around
+ * to splitting the regex code out as a standalone library, there will need
+ * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
+ */
+static int
+rcancelrequested(void)
+{
+#ifndef FRONTEND
+ 	return InterruptPending && (QueryCancelPending || ProcDiePending);
+#else
+	return 0;
+#endif
+}
+
+/*
+ * rstacktoodeep - check for stack getting dangerously deep
+ *
+ * Return nonzero to fail the operation with error code REG_ETOOBIG,
+ * zero to keep going
+ *
+ * The current implementation is Postgres-specific.  If we ever get around
+ * to splitting the regex code out as a standalone library, there will need
+ * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
+ */
+static int
+rstacktoodeep(void)
+{
+#ifndef FRONTEND
+ 	return stack_is_too_deep();
+#else
+	return 0;
+#endif
+}
+
+#ifdef REG_DEBUG
+
+/*
+ * dump - dump an RE in human-readable form
+ */
+static void
+dump(regex_t *re,
+	 FILE *f)
+{
+	struct guts *g;
+	int			i;
+
+	if (re->re_magic != REMAGIC)
+		fprintf(f, "bad magic number (0x%x not 0x%x)\n", re->re_magic,
+				REMAGIC);
+	if (re->re_guts == NULL)
+	{
+		fprintf(f, "NULL guts!!!\n");
+		return;
+	}
+	g = (struct guts *) re->re_guts;
+	if (g->magic != GUTSMAGIC)
+		fprintf(f, "bad guts magic number (0x%x not 0x%x)\n", g->magic,
+				GUTSMAGIC);
+
+	fprintf(f, "\n\n\n========= DUMP ==========\n");
+	fprintf(f, "nsub %d, info 0%lo, csize %d, ntree %d\n",
+			(int) re->re_nsub, re->re_info, re->re_csize, g->ntree);
+
+	dumpcolors(&g->cmap, f);
+	if (!NULLCNFA(g->search))
+	{
+		fprintf(f, "\nsearch:\n");
+		dumpcnfa(&g->search, f);
+	}
+	for (i = 1; i < g->nlacons; i++)
+	{
+		struct subre *lasub = &g->lacons[i];
+		const char *latype;
+
+		switch (lasub->subno)
+		{
+			case LATYPE_AHEAD_POS:
+				latype = "positive lookahead";
+				break;
+			case LATYPE_AHEAD_NEG:
+				latype = "negative lookahead";
+				break;
+			case LATYPE_BEHIND_POS:
+				latype = "positive lookbehind";
+				break;
+			case LATYPE_BEHIND_NEG:
+				latype = "negative lookbehind";
+				break;
+			default:
+				latype = "???";
+				break;
+		}
+		fprintf(f, "\nla%d (%s):\n", i, latype);
+		dumpcnfa(&lasub->cnfa, f);
+	}
+	fprintf(f, "\n");
+	dumpst(g->tree, f, 0);
+}
+
+/*
+ * dumpst - dump a subRE tree
+ */
+static void
+dumpst(struct subre * t,
+	   FILE *f,
+	   int nfapresent)			/* is the original NFA still around? */
+{
+	if (t == NULL)
+		fprintf(f, "null tree\n");
+	else
+		stdump(t, f, nfapresent);
+	fflush(f);
+}
+
+/*
+ * stdump - recursive guts of dumpst
+ */
+static void
+stdump(struct subre * t,
+	   FILE *f,
+	   int nfapresent)			/* is the original NFA still around? */
+{
+	char		idbuf[50];
+
+	fprintf(f, "%s. `%c'", stid(t, idbuf, sizeof(idbuf)), t->op);
+	if (t->flags & LONGER)
+		fprintf(f, " longest");
+	if (t->flags & SHORTER)
+		fprintf(f, " shortest");
+	if (t->flags & MIXED)
+		fprintf(f, " hasmixed");
+	if (t->flags & CAP)
+		fprintf(f, " hascapture");
+	if (t->flags & BACKR)
+		fprintf(f, " hasbackref");
+	if (!(t->flags & INUSE))
+		fprintf(f, " UNUSED");
+	if (t->subno != 0)
+		fprintf(f, " (#%d)", t->subno);
+	if (t->min != 1 || t->max != 1)
+	{
+		fprintf(f, " {%d,", t->min);
+		if (t->max != DUPINF)
+			fprintf(f, "%d", t->max);
+		fprintf(f, "}");
+	}
+	if (nfapresent)
+		fprintf(f, " %ld-%ld", (long) t->begin->no, (long) t->end->no);
+	if (t->left != NULL)
+		fprintf(f, " L:%s", stid(t->left, idbuf, sizeof(idbuf)));
+	if (t->right != NULL)
+		fprintf(f, " R:%s", stid(t->right, idbuf, sizeof(idbuf)));
+	if (!NULLCNFA(t->cnfa))
+	{
+		fprintf(f, "\n");
+		dumpcnfa(&t->cnfa, f);
+	}
+	fprintf(f, "\n");
+	if (t->left != NULL)
+		stdump(t->left, f, nfapresent);
+	if (t->right != NULL)
+		stdump(t->right, f, nfapresent);
+}
+
+/*
+ * stid - identify a subtree node for dumping
+ */
+static const char *				/* points to buf or constant string */
+stid(struct subre * t,
+	 char *buf,
+	 size_t bufsize)
+{
+	/* big enough for hex int or decimal t->id? */
+	if (bufsize < sizeof(void *) * 2 + 3 || bufsize < sizeof(t->id) * 3 + 1)
+		return "unable";
+	if (t->id != 0)
+		sprintf(buf, "%d", t->id);
+	else
+		sprintf(buf, "%p", t);
+	return buf;
+}
+#endif   /* REG_DEBUG */
+
+
+#include "regc_lex.c"
+#include "regc_color.c"
+#include "regc_nfa.c"
+#include "regc_cvec.c"
+#include "regc_pg_locale.c"
+#include "regc_locale.c"
diff --git a/src/common/regex/rege_dfa.c b/src/common/regex/rege_dfa.c
new file mode 100644
index 0000000..7d90242
--- /dev/null
+++ b/src/common/regex/rege_dfa.c
@@ -0,0 +1,929 @@
+/*
+ * DFA routines
+ * This file is #included by regexec.c.
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/rege_dfa.c
+ *
+ */
+
+/*
+ * longest - longest-preferred matching engine
+ *
+ * On success, returns match endpoint address.  Returns NULL on no match.
+ * Internal errors also return NULL, with v->err set.
+ */
+static chr *
+longest(struct vars * v,
+		struct dfa * d,
+		chr *start,				/* where the match should start */
+		chr *stop,				/* match must end at or before here */
+		int *hitstopp)			/* record whether hit v->stop, if non-NULL */
+{
+	chr		   *cp;
+	chr		   *realstop = (stop == v->stop) ? stop : stop + 1;
+	color		co;
+	struct sset *css;
+	struct sset *ss;
+	chr		   *post;
+	int			i;
+	struct colormap *cm = d->cm;
+
+	/* prevent "uninitialized variable" warnings */
+	if (hitstopp != NULL)
+		*hitstopp = 0;
+
+	/* initialize */
+	css = initialize(v, d, start);
+	if (css == NULL)
+		return NULL;
+	cp = start;
+
+	/* startup */
+	FDEBUG(("+++ startup +++\n"));
+	if (cp == v->start)
+	{
+		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+	}
+	else
+	{
+		co = GETCOLOR(cm, *(cp - 1));
+		FDEBUG(("char %c, color %ld\n", (char) *(cp - 1), (long) co));
+	}
+	css = miss(v, d, css, co, cp, start);
+	if (css == NULL)
+		return NULL;
+	css->lastseen = cp;
+
+	/*
+	 * This is the main text-scanning loop.  It seems worth having two copies
+	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
+	 * builds, when you're not actively tracing.
+	 */
+#ifdef REG_DEBUG
+	if (v->eflags & REG_FTRACE)
+	{
+		while (cp < realstop)
+		{
+			FDEBUG(("+++ at c%d +++\n", (int) (css - d->ssets)));
+			co = GETCOLOR(cm, *cp);
+			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+		}
+	}
+	else
+#endif
+	{
+		while (cp < realstop)
+		{
+			co = GETCOLOR(cm, *cp);
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+		}
+	}
+
+	if (ISERR())
+		return NULL;
+
+	/* shutdown */
+	FDEBUG(("+++ shutdown at c%d +++\n", (int) (css - d->ssets)));
+	if (cp == v->stop && stop == v->stop)
+	{
+		if (hitstopp != NULL)
+			*hitstopp = 1;
+		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+		ss = miss(v, d, css, co, cp, start);
+		if (ISERR())
+			return NULL;
+		/* special case:  match ended at eol? */
+		if (ss != NULL && (ss->flags & POSTSTATE))
+			return cp;
+		else if (ss != NULL)
+			ss->lastseen = cp;	/* to be tidy */
+	}
+
+	/* find last match, if any */
+	post = d->lastpost;
+	for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--)
+		if ((ss->flags & POSTSTATE) && post != ss->lastseen &&
+			(post == NULL || post < ss->lastseen))
+			post = ss->lastseen;
+	if (post != NULL)			/* found one */
+		return post - 1;
+
+	return NULL;
+}
+
+/*
+ * shortest - shortest-preferred matching engine
+ *
+ * On success, returns match endpoint address.  Returns NULL on no match.
+ * Internal errors also return NULL, with v->err set.
+ */
+static chr *
+shortest(struct vars * v,
+		 struct dfa * d,
+		 chr *start,			/* where the match should start */
+		 chr *min,				/* match must end at or after here */
+		 chr *max,				/* match must end at or before here */
+		 chr **coldp,			/* store coldstart pointer here, if non-NULL */
+		 int *hitstopp)			/* record whether hit v->stop, if non-NULL */
+{
+	chr		   *cp;
+	chr		   *realmin = (min == v->stop) ? min : min + 1;
+	chr		   *realmax = (max == v->stop) ? max : max + 1;
+	color		co;
+	struct sset *css;
+	struct sset *ss;
+	struct colormap *cm = d->cm;
+
+	/* prevent "uninitialized variable" warnings */
+	if (coldp != NULL)
+		*coldp = NULL;
+	if (hitstopp != NULL)
+		*hitstopp = 0;
+
+	/* initialize */
+	css = initialize(v, d, start);
+	if (css == NULL)
+		return NULL;
+	cp = start;
+
+	/* startup */
+	FDEBUG(("--- startup ---\n"));
+	if (cp == v->start)
+	{
+		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+	}
+	else
+	{
+		co = GETCOLOR(cm, *(cp - 1));
+		FDEBUG(("char %c, color %ld\n", (char) *(cp - 1), (long) co));
+	}
+	css = miss(v, d, css, co, cp, start);
+	if (css == NULL)
+		return NULL;
+	css->lastseen = cp;
+	ss = css;
+
+	/*
+	 * This is the main text-scanning loop.  It seems worth having two copies
+	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
+	 * builds, when you're not actively tracing.
+	 */
+#ifdef REG_DEBUG
+	if (v->eflags & REG_FTRACE)
+	{
+		while (cp < realmax)
+		{
+			FDEBUG(("--- at c%d ---\n", (int) (css - d->ssets)));
+			co = GETCOLOR(cm, *cp);
+			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+			if ((ss->flags & POSTSTATE) && cp >= realmin)
+				break;			/* NOTE BREAK OUT */
+		}
+	}
+	else
+#endif
+	{
+		while (cp < realmax)
+		{
+			co = GETCOLOR(cm, *cp);
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+			if ((ss->flags & POSTSTATE) && cp >= realmin)
+				break;			/* NOTE BREAK OUT */
+		}
+	}
+
+	if (ss == NULL)
+		return NULL;
+
+	if (coldp != NULL)			/* report last no-progress state set, if any */
+		*coldp = lastcold(v, d);
+
+	if ((ss->flags & POSTSTATE) && cp > min)
+	{
+		assert(cp >= realmin);
+		cp--;
+	}
+	else if (cp == v->stop && max == v->stop)
+	{
+		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+		ss = miss(v, d, css, co, cp, start);
+		/* match might have ended at eol */
+		if ((ss == NULL || !(ss->flags & POSTSTATE)) && hitstopp != NULL)
+			*hitstopp = 1;
+	}
+
+	if (ss == NULL || !(ss->flags & POSTSTATE))
+		return NULL;
+
+	return cp;
+}
+
+/*
+ * matchuntil - incremental matching engine
+ *
+ * This is meant for use with a search-style NFA (that is, the pattern is
+ * known to act as though it had a leading .*).  We determine whether a
+ * match exists starting at v->start and ending at probe.  Multiple calls
+ * require only O(N) time not O(N^2) so long as the probe values are
+ * nondecreasing.  *lastcss and *lastcp must be initialized to NULL before
+ * starting a series of calls.
+ *
+ * Returns 1 if a match exists, 0 if not.
+ * Internal errors also return 0, with v->err set.
+ */
+static int
+matchuntil(struct vars * v,
+		   struct dfa * d,
+		   chr *probe,			/* we want to know if a match ends here */
+		   struct sset ** lastcss,		/* state storage across calls */
+		   chr **lastcp)		/* state storage across calls */
+{
+	chr		   *cp = *lastcp;
+	color		co;
+	struct sset *css = *lastcss;
+	struct sset *ss;
+	struct colormap *cm = d->cm;
+
+	/* initialize and startup, or restart, if necessary */
+	if (cp == NULL || cp > probe)
+	{
+		cp = v->start;
+		css = initialize(v, d, cp);
+		if (css == NULL)
+			return 0;
+
+		FDEBUG((">>> startup >>>\n"));
+		co = d->cnfa->bos[(v->eflags & REG_NOTBOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+
+		css = miss(v, d, css, co, cp, v->start);
+		if (css == NULL)
+			return 0;
+		css->lastseen = cp;
+	}
+	else if (css == NULL)
+	{
+		/* we previously found that no match is possible beyond *lastcp */
+		return 0;
+	}
+	ss = css;
+
+	/*
+	 * This is the main text-scanning loop.  It seems worth having two copies
+	 * to avoid the overhead of REG_FTRACE tests here, even in REG_DEBUG
+	 * builds, when you're not actively tracing.
+	 */
+#ifdef REG_DEBUG
+	if (v->eflags & REG_FTRACE)
+	{
+		while (cp < probe)
+		{
+			FDEBUG((">>> at c%d >>>\n", (int) (css - d->ssets)));
+			co = GETCOLOR(cm, *cp);
+			FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, v->start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+		}
+	}
+	else
+#endif
+	{
+		while (cp < probe)
+		{
+			co = GETCOLOR(cm, *cp);
+			ss = css->outs[co];
+			if (ss == NULL)
+			{
+				ss = miss(v, d, css, co, cp + 1, v->start);
+				if (ss == NULL)
+					break;		/* NOTE BREAK OUT */
+			}
+			cp++;
+			ss->lastseen = cp;
+			css = ss;
+		}
+	}
+
+	*lastcss = ss;
+	*lastcp = cp;
+
+	if (ss == NULL)
+		return 0;				/* impossible match, or internal error */
+
+	/* We need to process one more chr, or the EOS symbol, to check match */
+	if (cp < v->stop)
+	{
+		FDEBUG((">>> at c%d >>>\n", (int) (css - d->ssets)));
+		co = GETCOLOR(cm, *cp);
+		FDEBUG(("char %c, color %ld\n", (char) *cp, (long) co));
+		ss = css->outs[co];
+		if (ss == NULL)
+			ss = miss(v, d, css, co, cp + 1, v->start);
+	}
+	else
+	{
+		assert(cp == v->stop);
+		co = d->cnfa->eos[(v->eflags & REG_NOTEOL) ? 0 : 1];
+		FDEBUG(("color %ld\n", (long) co));
+		ss = miss(v, d, css, co, cp, v->start);
+	}
+
+	if (ss == NULL || !(ss->flags & POSTSTATE))
+		return 0;
+
+	return 1;
+}
+
+/*
+ * lastcold - determine last point at which no progress had been made
+ */
+static chr *					/* endpoint, or NULL */
+lastcold(struct vars * v,
+		 struct dfa * d)
+{
+	struct sset *ss;
+	chr		   *nopr;
+	int			i;
+
+	nopr = d->lastnopr;
+	if (nopr == NULL)
+		nopr = v->start;
+	for (ss = d->ssets, i = d->nssused; i > 0; ss++, i--)
+		if ((ss->flags & NOPROGRESS) && nopr < ss->lastseen)
+			nopr = ss->lastseen;
+	return nopr;
+}
+
+/*
+ * newdfa - set up a fresh DFA
+ */
+static struct dfa *
+newdfa(struct vars * v,
+	   struct cnfa * cnfa,
+	   struct colormap * cm,
+	   struct smalldfa * sml)	/* preallocated space, may be NULL */
+{
+	struct dfa *d;
+	size_t		nss = cnfa->nstates * 2;
+	int			wordsper = (cnfa->nstates + UBITS - 1) / UBITS;
+	struct smalldfa *smallwas = sml;
+
+	assert(cnfa != NULL && cnfa->nstates != 0);
+
+	if (nss <= FEWSTATES && cnfa->ncolors <= FEWCOLORS)
+	{
+		assert(wordsper == 1);
+		if (sml == NULL)
+		{
+			sml = (struct smalldfa *) MALLOC(sizeof(struct smalldfa));
+			if (sml == NULL)
+			{
+				ERR(REG_ESPACE);
+				return NULL;
+			}
+		}
+		d = &sml->dfa;
+		d->ssets = sml->ssets;
+		d->statesarea = sml->statesarea;
+		d->work = &d->statesarea[nss];
+		d->outsarea = sml->outsarea;
+		d->incarea = sml->incarea;
+		d->cptsmalloced = 0;
+		d->mallocarea = (smallwas == NULL) ? (char *) sml : NULL;
+	}
+	else
+	{
+		d = (struct dfa *) MALLOC(sizeof(struct dfa));
+		if (d == NULL)
+		{
+			ERR(REG_ESPACE);
+			return NULL;
+		}
+		d->ssets = (struct sset *) MALLOC(nss * sizeof(struct sset));
+		d->statesarea = (unsigned *) MALLOC((nss + WORK) * wordsper *
+											sizeof(unsigned));
+		d->work = &d->statesarea[nss * wordsper];
+		d->outsarea = (struct sset **) MALLOC(nss * cnfa->ncolors *
+											  sizeof(struct sset *));
+		d->incarea = (struct arcp *) MALLOC(nss * cnfa->ncolors *
+											sizeof(struct arcp));
+		d->cptsmalloced = 1;
+		d->mallocarea = (char *) d;
+		if (d->ssets == NULL || d->statesarea == NULL ||
+			d->outsarea == NULL || d->incarea == NULL)
+		{
+			freedfa(d);
+			ERR(REG_ESPACE);
+			return NULL;
+		}
+	}
+
+	d->nssets = (v->eflags & REG_SMALL) ? 7 : nss;
+	d->nssused = 0;
+	d->nstates = cnfa->nstates;
+	d->ncolors = cnfa->ncolors;
+	d->wordsper = wordsper;
+	d->cnfa = cnfa;
+	d->cm = cm;
+	d->lastpost = NULL;
+	d->lastnopr = NULL;
+	d->search = d->ssets;
+
+	/* initialization of sset fields is done as needed */
+
+	return d;
+}
+
+/*
+ * freedfa - free a DFA
+ */
+static void
+freedfa(struct dfa * d)
+{
+	if (d->cptsmalloced)
+	{
+		if (d->ssets != NULL)
+			FREE(d->ssets);
+		if (d->statesarea != NULL)
+			FREE(d->statesarea);
+		if (d->outsarea != NULL)
+			FREE(d->outsarea);
+		if (d->incarea != NULL)
+			FREE(d->incarea);
+	}
+
+	if (d->mallocarea != NULL)
+		FREE(d->mallocarea);
+}
+
+/*
+ * hash - construct a hash code for a bitvector
+ *
+ * There are probably better ways, but they're more expensive.
+ */
+static unsigned
+hash(unsigned *uv,
+	 int n)
+{
+	int			i;
+	unsigned	h;
+
+	h = 0;
+	for (i = 0; i < n; i++)
+		h ^= uv[i];
+	return h;
+}
+
+/*
+ * initialize - hand-craft a cache entry for startup, otherwise get ready
+ */
+static struct sset *
+initialize(struct vars * v,
+		   struct dfa * d,
+		   chr *start)
+{
+	struct sset *ss;
+	int			i;
+
+	/* is previous one still there? */
+	if (d->nssused > 0 && (d->ssets[0].flags & STARTER))
+		ss = &d->ssets[0];
+	else
+	{							/* no, must (re)build it */
+		ss = getvacant(v, d, start, start);
+		if (ss == NULL)
+			return NULL;
+		for (i = 0; i < d->wordsper; i++)
+			ss->states[i] = 0;
+		BSET(ss->states, d->cnfa->pre);
+		ss->hash = HASH(ss->states, d->wordsper);
+		assert(d->cnfa->pre != d->cnfa->post);
+		ss->flags = STARTER | LOCKED | NOPROGRESS;
+		/* lastseen dealt with below */
+	}
+
+	for (i = 0; i < d->nssused; i++)
+		d->ssets[i].lastseen = NULL;
+	ss->lastseen = start;		/* maybe untrue, but harmless */
+	d->lastpost = NULL;
+	d->lastnopr = NULL;
+	return ss;
+}
+
+/*
+ * miss - handle a stateset cache miss
+ *
+ * css is the current stateset, co is the color of the current input character,
+ * cp points to the character after that (which is where we may need to test
+ * LACONs).  start does not affect matching behavior but is needed for pickss'
+ * heuristics about which stateset cache entry to replace.
+ *
+ * Ordinarily, returns the address of the next stateset (the one that is
+ * valid after consuming the input character).  Returns NULL if no valid
+ * NFA states remain, ie we have a certain match failure.
+ * Internal errors also return NULL, with v->err set.
+ */
+static struct sset *
+miss(struct vars * v,
+	 struct dfa * d,
+	 struct sset * css,
+	 pcolor co,
+	 chr *cp,					/* next chr */
+	 chr *start)				/* where the attempt got started */
+{
+	struct cnfa *cnfa = d->cnfa;
+	int			i;
+	unsigned	h;
+	struct carc *ca;
+	struct sset *p;
+	int			ispost;
+	int			noprogress;
+	int			gotstate;
+	int			dolacons;
+	int			sawlacons;
+
+	/* for convenience, we can be called even if it might not be a miss */
+	if (css->outs[co] != NULL)
+	{
+		FDEBUG(("hit\n"));
+		return css->outs[co];
+	}
+	FDEBUG(("miss\n"));
+
+	/*
+	 * Checking for operation cancel in the inner text search loop seems
+	 * unduly expensive.  As a compromise, check during cache misses.
+	 */
+	if (CANCEL_REQUESTED(v->re))
+	{
+		ERR(REG_CANCEL);
+		return NULL;
+	}
+
+	/*
+	 * What set of states would we end up in after consuming the co character?
+	 * We first consider PLAIN arcs that consume the character, and then look
+	 * to see what LACON arcs could be traversed after consuming it.
+	 */
+	for (i = 0; i < d->wordsper; i++)
+		d->work[i] = 0;			/* build new stateset bitmap in d->work */
+	ispost = 0;
+	noprogress = 1;
+	gotstate = 0;
+	for (i = 0; i < d->nstates; i++)
+		if (ISBSET(css->states, i))
+			for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++)
+				if (ca->co == co)
+				{
+					BSET(d->work, ca->to);
+					gotstate = 1;
+					if (ca->to == cnfa->post)
+						ispost = 1;
+					if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS))
+						noprogress = 0;
+					FDEBUG(("%d -> %d\n", i, ca->to));
+				}
+	if (!gotstate)
+		return NULL;			/* character cannot reach any new state */
+	dolacons = (cnfa->flags & HASLACONS);
+	sawlacons = 0;
+	/* outer loop handles transitive closure of reachable-by-LACON states */
+	while (dolacons)
+	{
+		dolacons = 0;
+		for (i = 0; i < d->nstates; i++)
+			if (ISBSET(d->work, i))
+				for (ca = cnfa->states[i]; ca->co != COLORLESS; ca++)
+				{
+					if (ca->co < cnfa->ncolors)
+						continue;		/* not a LACON arc */
+					if (ISBSET(d->work, ca->to))
+						continue;		/* arc would be a no-op anyway */
+					sawlacons = 1;		/* this LACON affects our result */
+					if (!lacon(v, cnfa, cp, ca->co))
+					{
+						if (ISERR())
+							return NULL;
+						continue;		/* LACON arc cannot be traversed */
+					}
+					if (ISERR())
+						return NULL;
+					BSET(d->work, ca->to);
+					dolacons = 1;
+					if (ca->to == cnfa->post)
+						ispost = 1;
+					if (!(cnfa->stflags[ca->to] & CNFA_NOPROGRESS))
+						noprogress = 0;
+					FDEBUG(("%d :> %d\n", i, ca->to));
+				}
+	}
+	h = HASH(d->work, d->wordsper);
+
+	/* Is this stateset already in the cache? */
+	for (p = d->ssets, i = d->nssused; i > 0; p++, i--)
+		if (HIT(h, d->work, p, d->wordsper))
+		{
+			FDEBUG(("cached c%d\n", (int) (p - d->ssets)));
+			break;				/* NOTE BREAK OUT */
+		}
+	if (i == 0)
+	{							/* nope, need a new cache entry */
+		p = getvacant(v, d, cp, start);
+		if (p == NULL)
+			return NULL;
+		assert(p != css);
+		for (i = 0; i < d->wordsper; i++)
+			p->states[i] = d->work[i];
+		p->hash = h;
+		p->flags = (ispost) ? POSTSTATE : 0;
+		if (noprogress)
+			p->flags |= NOPROGRESS;
+		/* lastseen to be dealt with by caller */
+	}
+
+	/*
+	 * Link new stateset to old, unless a LACON affected the result, in which
+	 * case we don't create the link.  That forces future transitions across
+	 * this same arc (same prior stateset and character color) to come through
+	 * miss() again, so that we can recheck the LACON(s), which might or might
+	 * not pass since context will be different.
+	 */
+	if (!sawlacons)
+	{
+		FDEBUG(("c%d[%d]->c%d\n",
+				(int) (css - d->ssets), co, (int) (p - d->ssets)));
+		css->outs[co] = p;
+		css->inchain[co] = p->ins;
+		p->ins.ss = css;
+		p->ins.co = (color) co;
+	}
+	return p;
+}
+
+/*
+ * lacon - lookaround-constraint checker for miss()
+ */
+static int						/* predicate:  constraint satisfied? */
+lacon(struct vars * v,
+	  struct cnfa * pcnfa,		/* parent cnfa */
+	  chr *cp,
+	  pcolor co)				/* "color" of the lookaround constraint */
+{
+	int			n;
+	struct subre *sub;
+	struct dfa *d;
+	chr		   *end;
+	int			satisfied;
+
+	/* Since this is recursive, it could be driven to stack overflow */
+	if (STACK_TOO_DEEP(v->re))
+	{
+		ERR(REG_ETOOBIG);
+		return 0;
+	}
+
+	n = co - pcnfa->ncolors;
+	assert(n > 0 && n < v->g->nlacons && v->g->lacons != NULL);
+	FDEBUG(("=== testing lacon %d\n", n));
+	sub = &v->g->lacons[n];
+	d = getladfa(v, n);
+	if (d == NULL)
+		return 0;
+	if (LATYPE_IS_AHEAD(sub->subno))
+	{
+		/* used to use longest() here, but shortest() could be much cheaper */
+		end = shortest(v, d, cp, cp, v->stop,
+					   (chr **) NULL, (int *) NULL);
+		satisfied = LATYPE_IS_POS(sub->subno) ? (end != NULL) : (end == NULL);
+	}
+	else
+	{
+		/*
+		 * To avoid doing O(N^2) work when repeatedly testing a lookbehind
+		 * constraint in an N-character string, we use matchuntil() which can
+		 * cache the DFA state across calls.  We only need to restart if the
+		 * probe point decreases, which is not common.  The NFA we're using is
+		 * a search NFA, so it doesn't mind scanning over stuff before the
+		 * nominal match.
+		 */
+		satisfied = matchuntil(v, d, cp, &v->lblastcss[n], &v->lblastcp[n]);
+		if (!LATYPE_IS_POS(sub->subno))
+			satisfied = !satisfied;
+	}
+	FDEBUG(("=== lacon %d satisfied %d\n", n, satisfied));
+	return satisfied;
+}
+
+/*
+ * getvacant - get a vacant state set
+ *
+ * This routine clears out the inarcs and outarcs, but does not otherwise
+ * clear the innards of the state set -- that's up to the caller.
+ */
+static struct sset *
+getvacant(struct vars * v,
+		  struct dfa * d,
+		  chr *cp,
+		  chr *start)
+{
+	int			i;
+	struct sset *ss;
+	struct sset *p;
+	struct arcp ap;
+	color		co;
+
+	ss = pickss(v, d, cp, start);
+	if (ss == NULL)
+		return NULL;
+	assert(!(ss->flags & LOCKED));
+
+	/* clear out its inarcs, including self-referential ones */
+	ap = ss->ins;
+	while ((p = ap.ss) != NULL)
+	{
+		co = ap.co;
+		FDEBUG(("zapping c%d's %ld outarc\n", (int) (p - d->ssets), (long) co));
+		p->outs[co] = NULL;
+		ap = p->inchain[co];
+		p->inchain[co].ss = NULL;		/* paranoia */
+	}
+	ss->ins.ss = NULL;
+
+	/* take it off the inarc chains of the ssets reached by its outarcs */
+	for (i = 0; i < d->ncolors; i++)
+	{
+		p = ss->outs[i];
+		assert(p != ss);		/* not self-referential */
+		if (p == NULL)
+			continue;			/* NOTE CONTINUE */
+		FDEBUG(("del outarc %d from c%d's in chn\n", i, (int) (p - d->ssets)));
+		if (p->ins.ss == ss && p->ins.co == i)
+			p->ins = ss->inchain[i];
+		else
+		{
+			struct arcp lastap = {NULL, 0};
+
+			assert(p->ins.ss != NULL);
+			for (ap = p->ins; ap.ss != NULL &&
+				 !(ap.ss == ss && ap.co == i);
+				 ap = ap.ss->inchain[ap.co])
+				lastap = ap;
+			assert(ap.ss != NULL);
+			lastap.ss->inchain[lastap.co] = ss->inchain[i];
+		}
+		ss->outs[i] = NULL;
+		ss->inchain[i].ss = NULL;
+	}
+
+	/* if ss was a success state, may need to remember location */
+	if ((ss->flags & POSTSTATE) && ss->lastseen != d->lastpost &&
+		(d->lastpost == NULL || d->lastpost < ss->lastseen))
+		d->lastpost = ss->lastseen;
+
+	/* likewise for a no-progress state */
+	if ((ss->flags & NOPROGRESS) && ss->lastseen != d->lastnopr &&
+		(d->lastnopr == NULL || d->lastnopr < ss->lastseen))
+		d->lastnopr = ss->lastseen;
+
+	return ss;
+}
+
+/*
+ * pickss - pick the next stateset to be used
+ */
+static struct sset *
+pickss(struct vars * v,
+	   struct dfa * d,
+	   chr *cp,
+	   chr *start)
+{
+	int			i;
+	struct sset *ss;
+	struct sset *end;
+	chr		   *ancient;
+
+	/* shortcut for cases where cache isn't full */
+	if (d->nssused < d->nssets)
+	{
+		i = d->nssused;
+		d->nssused++;
+		ss = &d->ssets[i];
+		FDEBUG(("new c%d\n", i));
+		/* set up innards */
+		ss->states = &d->statesarea[i * d->wordsper];
+		ss->flags = 0;
+		ss->ins.ss = NULL;
+		ss->ins.co = WHITE;		/* give it some value */
+		ss->outs = &d->outsarea[i * d->ncolors];
+		ss->inchain = &d->incarea[i * d->ncolors];
+		for (i = 0; i < d->ncolors; i++)
+		{
+			ss->outs[i] = NULL;
+			ss->inchain[i].ss = NULL;
+		}
+		return ss;
+	}
+
+	/* look for oldest, or old enough anyway */
+	if (cp - start > d->nssets * 2 / 3) /* oldest 33% are expendable */
+		ancient = cp - d->nssets * 2 / 3;
+	else
+		ancient = start;
+	for (ss = d->search, end = &d->ssets[d->nssets]; ss < end; ss++)
+		if ((ss->lastseen == NULL || ss->lastseen < ancient) &&
+			!(ss->flags & LOCKED))
+		{
+			d->search = ss + 1;
+			FDEBUG(("replacing c%d\n", (int) (ss - d->ssets)));
+			return ss;
+		}
+	for (ss = d->ssets, end = d->search; ss < end; ss++)
+		if ((ss->lastseen == NULL || ss->lastseen < ancient) &&
+			!(ss->flags & LOCKED))
+		{
+			d->search = ss + 1;
+			FDEBUG(("replacing c%d\n", (int) (ss - d->ssets)));
+			return ss;
+		}
+
+	/* nobody's old enough?!? -- something's really wrong */
+	FDEBUG(("cannot find victim to replace!\n"));
+	ERR(REG_ASSERT);
+	return NULL;
+}
diff --git a/src/common/regex/regerror.c b/src/common/regex/regerror.c
new file mode 100644
index 0000000..f2fe696
--- /dev/null
+++ b/src/common/regex/regerror.c
@@ -0,0 +1,120 @@
+/*
+ * regerror - error-code expansion
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regerror.c
+ *
+ */
+
+#include "regex/regguts.h"
+
+/* unknown-error explanation */
+static const char unk[] = "*** unknown regex error code 0x%x ***";
+
+/* struct to map among codes, code names, and explanations */
+static const struct rerr
+{
+	int			code;
+	const char *name;
+	const char *explain;
+}	rerrs[] =
+
+{
+	/* the actual table is built from regex.h */
+#include "regex/regerrs.h"		/* pgrminclude ignore */
+	{
+		-1, "", "oops"
+	},							/* explanation special-cased in code */
+};
+
+/*
+ * pg_regerror - the interface to error numbers
+ */
+/* ARGSUSED */
+size_t							/* actual space needed (including NUL) */
+pg_regerror(int errcode,		/* error code, or REG_ATOI or REG_ITOA */
+			const regex_t *preg,	/* associated regex_t (unused at present) */
+			char *errbuf,		/* result buffer (unless errbuf_size==0) */
+			size_t errbuf_size) /* available space in errbuf, can be 0 */
+{
+	const struct rerr *r;
+	const char *msg;
+	char		convbuf[sizeof(unk) + 50];		/* 50 = plenty for int */
+	size_t		len;
+	int			icode;
+
+	switch (errcode)
+	{
+		case REG_ATOI:			/* convert name to number */
+			for (r = rerrs; r->code >= 0; r++)
+				if (strcmp(r->name, errbuf) == 0)
+					break;
+			sprintf(convbuf, "%d", r->code);	/* -1 for unknown */
+			msg = convbuf;
+			break;
+		case REG_ITOA:			/* convert number to name */
+			icode = atoi(errbuf);		/* not our problem if this fails */
+			for (r = rerrs; r->code >= 0; r++)
+				if (r->code == icode)
+					break;
+			if (r->code >= 0)
+				msg = r->name;
+			else
+			{					/* unknown; tell him the number */
+				sprintf(convbuf, "REG_%u", (unsigned) icode);
+				msg = convbuf;
+			}
+			break;
+		default:				/* a real, normal error code */
+			for (r = rerrs; r->code >= 0; r++)
+				if (r->code == errcode)
+					break;
+			if (r->code >= 0)
+				msg = r->explain;
+			else
+			{					/* unknown; say so */
+				sprintf(convbuf, unk, errcode);
+				msg = convbuf;
+			}
+			break;
+	}
+
+	len = strlen(msg) + 1;		/* space needed, including NUL */
+	if (errbuf_size > 0)
+	{
+		if (errbuf_size > len)
+			strcpy(errbuf, msg);
+		else
+		{						/* truncate to fit */
+			memcpy(errbuf, msg, errbuf_size - 1);
+			errbuf[errbuf_size - 1] = '\0';
+		}
+	}
+
+	return len;
+}
diff --git a/src/common/regex/regexec.c b/src/common/regex/regexec.c
new file mode 100644
index 0000000..82659a0
--- /dev/null
+++ b/src/common/regex/regexec.c
@@ -0,0 +1,1425 @@
+/*
+ * re_*exec and friends - match REs
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regexec.c
+ *
+ */
+
+#include "regex/regguts.h"
+
+
+
+/* lazy-DFA representation */
+struct arcp
+{								/* "pointer" to an outarc */
+	struct sset *ss;
+	color		co;
+};
+
+struct sset
+{								/* state set */
+	unsigned   *states;			/* pointer to bitvector */
+	unsigned	hash;			/* hash of bitvector */
+#define  HASH(bv, nw)	 (((nw) == 1) ? *(bv) : hash(bv, nw))
+#define  HIT(h,bv,ss,nw) ((ss)->hash == (h) && ((nw) == 1 || \
+		memcmp(VS(bv), VS((ss)->states), (nw)*sizeof(unsigned)) == 0))
+	int			flags;
+#define  STARTER	 01			/* the initial state set */
+#define  POSTSTATE	 02			/* includes the goal state */
+#define  LOCKED		 04			/* locked in cache */
+#define  NOPROGRESS  010		/* zero-progress state set */
+	struct arcp ins;			/* chain of inarcs pointing here */
+	chr		   *lastseen;		/* last entered on arrival here */
+	struct sset **outs;			/* outarc vector indexed by color */
+	struct arcp *inchain;		/* chain-pointer vector for outarcs */
+};
+
+struct dfa
+{
+	int			nssets;			/* size of cache */
+	int			nssused;		/* how many entries occupied yet */
+	int			nstates;		/* number of states */
+	int			ncolors;		/* length of outarc and inchain vectors */
+	int			wordsper;		/* length of state-set bitvectors */
+	struct sset *ssets;			/* state-set cache */
+	unsigned   *statesarea;		/* bitvector storage */
+	unsigned   *work;			/* pointer to work area within statesarea */
+	struct sset **outsarea;		/* outarc-vector storage */
+	struct arcp *incarea;		/* inchain storage */
+	struct cnfa *cnfa;
+	struct colormap *cm;
+	chr		   *lastpost;		/* location of last cache-flushed success */
+	chr		   *lastnopr;		/* location of last cache-flushed NOPROGRESS */
+	struct sset *search;		/* replacement-search-pointer memory */
+	int			cptsmalloced;	/* were the areas individually malloced? */
+	char	   *mallocarea;		/* self, or master malloced area, or NULL */
+};
+
+#define WORK	1				/* number of work bitvectors needed */
+
+/* setup for non-malloc allocation for small cases */
+#define FEWSTATES	20			/* must be less than UBITS */
+#define FEWCOLORS	15
+struct smalldfa
+{
+	struct dfa	dfa;
+	struct sset ssets[FEWSTATES * 2];
+	unsigned	statesarea[FEWSTATES * 2 + WORK];
+	struct sset *outsarea[FEWSTATES * 2 * FEWCOLORS];
+	struct arcp incarea[FEWSTATES * 2 * FEWCOLORS];
+};
+
+#define DOMALLOC	((struct smalldfa *)NULL)	/* force malloc */
+
+
+
+/* internal variables, bundled for easy passing around */
+struct vars
+{
+	regex_t    *re;
+	struct guts *g;
+	int			eflags;			/* copies of arguments */
+	size_t		nmatch;
+	regmatch_t *pmatch;
+	rm_detail_t *details;
+	chr		   *start;			/* start of string */
+	chr		   *search_start;	/* search start of string */
+	chr		   *stop;			/* just past end of string */
+	int			err;			/* error code if any (0 none) */
+	struct dfa **subdfas;		/* per-tree-subre DFAs */
+	struct dfa **ladfas;		/* per-lacon-subre DFAs */
+	struct sset **lblastcss;	/* per-lacon-subre lookbehind restart data */
+	chr		  **lblastcp;		/* per-lacon-subre lookbehind restart data */
+	struct smalldfa dfa1;
+	struct smalldfa dfa2;
+};
+
+#define VISERR(vv)	((vv)->err != 0)	/* have we seen an error yet? */
+#define ISERR() VISERR(v)
+#define VERR(vv,e)	((vv)->err = ((vv)->err ? (vv)->err : (e)))
+#define ERR(e)	VERR(v, e)		/* record an error */
+#define NOERR() {if (ISERR()) return v->err;}	/* if error seen, return it */
+#define OFF(p)	((p) - v->start)
+#define LOFF(p) ((long)OFF(p))
+
+
+
+/*
+ * forward declarations
+ */
+/* === regexec.c === */
+static struct dfa *getsubdfa(struct vars *, struct subre *);
+static struct dfa *getladfa(struct vars *, int);
+static int	find(struct vars *, struct cnfa *, struct colormap *);
+static int	cfind(struct vars *, struct cnfa *, struct colormap *);
+static int	cfindloop(struct vars *, struct cnfa *, struct colormap *, struct dfa *, struct dfa *, chr **);
+static void zapallsubs(regmatch_t *, size_t);
+static void zaptreesubs(struct vars *, struct subre *);
+static void subset(struct vars *, struct subre *, chr *, chr *);
+static int	cdissect(struct vars *, struct subre *, chr *, chr *);
+static int	ccondissect(struct vars *, struct subre *, chr *, chr *);
+static int	crevcondissect(struct vars *, struct subre *, chr *, chr *);
+static int	cbrdissect(struct vars *, struct subre *, chr *, chr *);
+static int	caltdissect(struct vars *, struct subre *, chr *, chr *);
+static int	citerdissect(struct vars *, struct subre *, chr *, chr *);
+static int	creviterdissect(struct vars *, struct subre *, chr *, chr *);
+
+/* === rege_dfa.c === */
+static chr *longest(struct vars *, struct dfa *, chr *, chr *, int *);
+static chr *shortest(struct vars *, struct dfa *, chr *, chr *, chr *, chr **, int *);
+static int	matchuntil(struct vars *, struct dfa *, chr *, struct sset **, chr **);
+static chr *lastcold(struct vars *, struct dfa *);
+static struct dfa *newdfa(struct vars *, struct cnfa *, struct colormap *, struct smalldfa *);
+static void freedfa(struct dfa *);
+static unsigned hash(unsigned *, int);
+static struct sset *initialize(struct vars *, struct dfa *, chr *);
+static struct sset *miss(struct vars *, struct dfa *, struct sset *, pcolor, chr *, chr *);
+static int	lacon(struct vars *, struct cnfa *, chr *, pcolor);
+static struct sset *getvacant(struct vars *, struct dfa *, chr *, chr *);
+static struct sset *pickss(struct vars *, struct dfa *, chr *, chr *);
+
+
+/*
+ * pg_regexec - match regular expression
+ */
+int
+pg_regexec(regex_t *re,
+		   const chr *string,
+		   size_t len,
+		   size_t search_start,
+		   rm_detail_t *details,
+		   size_t nmatch,
+		   regmatch_t pmatch[],
+		   int flags)
+{
+	struct vars var;
+	register struct vars *v = &var;
+	int			st;
+	size_t		n;
+	size_t		i;
+	int			backref;
+
+#define  LOCALMAT	 20
+	regmatch_t	mat[LOCALMAT];
+
+#define  LOCALDFAS	 40
+	struct dfa *subdfas[LOCALDFAS];
+
+	/* sanity checks */
+	if (re == NULL || string == NULL || re->re_magic != REMAGIC)
+		return REG_INVARG;
+	if (re->re_csize != sizeof(chr))
+		return REG_MIXED;
+
+	/* Initialize locale-dependent support */
+	pg_set_regex_collation(re->re_collation);
+
+	/* setup */
+	v->re = re;
+	v->g = (struct guts *) re->re_guts;
+	if ((v->g->cflags & REG_EXPECT) && details == NULL)
+		return REG_INVARG;
+	if (v->g->info & REG_UIMPOSSIBLE)
+		return REG_NOMATCH;
+	backref = (v->g->info & REG_UBACKREF) ? 1 : 0;
+	v->eflags = flags;
+	if (v->g->cflags & REG_NOSUB)
+		nmatch = 0;				/* override client */
+	v->nmatch = nmatch;
+	if (backref)
+	{
+		/* need work area */
+		if (v->g->nsub + 1 <= LOCALMAT)
+			v->pmatch = mat;
+		else
+			v->pmatch = (regmatch_t *) MALLOC((v->g->nsub + 1) *
+											  sizeof(regmatch_t));
+		if (v->pmatch == NULL)
+			return REG_ESPACE;
+		v->nmatch = v->g->nsub + 1;
+	}
+	else
+		v->pmatch = pmatch;
+	v->details = details;
+	v->start = (chr *) string;
+	v->search_start = (chr *) string + search_start;
+	v->stop = (chr *) string + len;
+	v->err = 0;
+	v->subdfas = NULL;
+	v->ladfas = NULL;
+	v->lblastcss = NULL;
+	v->lblastcp = NULL;
+	/* below this point, "goto cleanup" will behave sanely */
+
+	assert(v->g->ntree >= 0);
+	n = (size_t) v->g->ntree;
+	if (n <= LOCALDFAS)
+		v->subdfas = subdfas;
+	else
+	{
+		v->subdfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
+		if (v->subdfas == NULL)
+		{
+			st = REG_ESPACE;
+			goto cleanup;
+		}
+	}
+	for (i = 0; i < n; i++)
+		v->subdfas[i] = NULL;
+
+	assert(v->g->nlacons >= 0);
+	n = (size_t) v->g->nlacons;
+	if (n > 0)
+	{
+		v->ladfas = (struct dfa **) MALLOC(n * sizeof(struct dfa *));
+		if (v->ladfas == NULL)
+		{
+			st = REG_ESPACE;
+			goto cleanup;
+		}
+		for (i = 0; i < n; i++)
+			v->ladfas[i] = NULL;
+		v->lblastcss = (struct sset **) MALLOC(n * sizeof(struct sset *));
+		v->lblastcp = (chr **) MALLOC(n * sizeof(chr *));
+		if (v->lblastcss == NULL || v->lblastcp == NULL)
+		{
+			st = REG_ESPACE;
+			goto cleanup;
+		}
+		for (i = 0; i < n; i++)
+		{
+			v->lblastcss[i] = NULL;
+			v->lblastcp[i] = NULL;
+		}
+	}
+
+	/* do it */
+	assert(v->g->tree != NULL);
+	if (backref)
+		st = cfind(v, &v->g->tree->cnfa, &v->g->cmap);
+	else
+		st = find(v, &v->g->tree->cnfa, &v->g->cmap);
+
+	/* copy (portion of) match vector over if necessary */
+	if (st == REG_OKAY && v->pmatch != pmatch && nmatch > 0)
+	{
+		zapallsubs(pmatch, nmatch);
+		n = (nmatch < v->nmatch) ? nmatch : v->nmatch;
+		memcpy(VS(pmatch), VS(v->pmatch), n * sizeof(regmatch_t));
+	}
+
+	/* clean up */
+cleanup:
+	if (v->pmatch != pmatch && v->pmatch != mat)
+		FREE(v->pmatch);
+	if (v->subdfas != NULL)
+	{
+		n = (size_t) v->g->ntree;
+		for (i = 0; i < n; i++)
+		{
+			if (v->subdfas[i] != NULL)
+				freedfa(v->subdfas[i]);
+		}
+		if (v->subdfas != subdfas)
+			FREE(v->subdfas);
+	}
+	if (v->ladfas != NULL)
+	{
+		n = (size_t) v->g->nlacons;
+		for (i = 0; i < n; i++)
+		{
+			if (v->ladfas[i] != NULL)
+				freedfa(v->ladfas[i]);
+		}
+		FREE(v->ladfas);
+	}
+	if (v->lblastcss != NULL)
+		FREE(v->lblastcss);
+	if (v->lblastcp != NULL)
+		FREE(v->lblastcp);
+
+	return st;
+}
+
+/*
+ * getsubdfa - create or re-fetch the DFA for a tree subre node
+ *
+ * We only need to create the DFA once per overall regex execution.
+ * The DFA will be freed by the cleanup step in pg_regexec().
+ */
+static struct dfa *
+getsubdfa(struct vars * v,
+		  struct subre * t)
+{
+	if (v->subdfas[t->id] == NULL)
+	{
+		v->subdfas[t->id] = newdfa(v, &t->cnfa, &v->g->cmap, DOMALLOC);
+		if (ISERR())
+			return NULL;
+	}
+	return v->subdfas[t->id];
+}
+
+/*
+ * getladfa - create or re-fetch the DFA for a LACON subre node
+ *
+ * Same as above, but for LACONs.
+ */
+static struct dfa *
+getladfa(struct vars * v,
+		 int n)
+{
+	assert(n > 0 && n < v->g->nlacons && v->g->lacons != NULL);
+
+	if (v->ladfas[n] == NULL)
+	{
+		struct subre *sub = &v->g->lacons[n];
+
+		v->ladfas[n] = newdfa(v, &sub->cnfa, &v->g->cmap, DOMALLOC);
+		if (ISERR())
+			return NULL;
+	}
+	return v->ladfas[n];
+}
+
+/*
+ * find - find a match for the main NFA (no-complications case)
+ */
+static int
+find(struct vars * v,
+	 struct cnfa * cnfa,
+	 struct colormap * cm)
+{
+	struct dfa *s;
+	struct dfa *d;
+	chr		   *begin;
+	chr		   *end = NULL;
+	chr		   *cold;
+	chr		   *open;			/* open and close of range of possible starts */
+	chr		   *close;
+	int			hitend;
+	int			shorter = (v->g->tree->flags & SHORTER) ? 1 : 0;
+
+	/* first, a shot with the search RE */
+	s = newdfa(v, &v->g->search, cm, &v->dfa1);
+	assert(!(ISERR() && s != NULL));
+	NOERR();
+	MDEBUG(("\nsearch at %ld\n", LOFF(v->start)));
+	cold = NULL;
+	close = shortest(v, s, v->search_start, v->search_start, v->stop,
+					 &cold, (int *) NULL);
+	freedfa(s);
+	NOERR();
+	if (v->g->cflags & REG_EXPECT)
+	{
+		assert(v->details != NULL);
+		if (cold != NULL)
+			v->details->rm_extend.rm_so = OFF(cold);
+		else
+			v->details->rm_extend.rm_so = OFF(v->stop);
+		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
+	}
+	if (close == NULL)			/* not found */
+		return REG_NOMATCH;
+	if (v->nmatch == 0)			/* found, don't need exact location */
+		return REG_OKAY;
+
+	/* find starting point and match */
+	assert(cold != NULL);
+	open = cold;
+	cold = NULL;
+	MDEBUG(("between %ld and %ld\n", LOFF(open), LOFF(close)));
+	d = newdfa(v, cnfa, cm, &v->dfa1);
+	assert(!(ISERR() && d != NULL));
+	NOERR();
+	for (begin = open; begin <= close; begin++)
+	{
+		MDEBUG(("\nfind trying at %ld\n", LOFF(begin)));
+		if (shorter)
+			end = shortest(v, d, begin, begin, v->stop,
+						   (chr **) NULL, &hitend);
+		else
+			end = longest(v, d, begin, v->stop, &hitend);
+		if (ISERR())
+		{
+			freedfa(d);
+			return v->err;
+		}
+		if (hitend && cold == NULL)
+			cold = begin;
+		if (end != NULL)
+			break;				/* NOTE BREAK OUT */
+	}
+	assert(end != NULL);		/* search RE succeeded so loop should */
+	freedfa(d);
+
+	/* and pin down details */
+	assert(v->nmatch > 0);
+	v->pmatch[0].rm_so = OFF(begin);
+	v->pmatch[0].rm_eo = OFF(end);
+	if (v->g->cflags & REG_EXPECT)
+	{
+		if (cold != NULL)
+			v->details->rm_extend.rm_so = OFF(cold);
+		else
+			v->details->rm_extend.rm_so = OFF(v->stop);
+		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
+	}
+	if (v->nmatch == 1)			/* no need for submatches */
+		return REG_OKAY;
+
+	/* find submatches */
+	zapallsubs(v->pmatch, v->nmatch);
+	return cdissect(v, v->g->tree, begin, end);
+}
+
+/*
+ * cfind - find a match for the main NFA (with complications)
+ */
+static int
+cfind(struct vars * v,
+	  struct cnfa * cnfa,
+	  struct colormap * cm)
+{
+	struct dfa *s;
+	struct dfa *d;
+	chr		   *cold;
+	int			ret;
+
+	s = newdfa(v, &v->g->search, cm, &v->dfa1);
+	NOERR();
+	d = newdfa(v, cnfa, cm, &v->dfa2);
+	if (ISERR())
+	{
+		assert(d == NULL);
+		freedfa(s);
+		return v->err;
+	}
+
+	ret = cfindloop(v, cnfa, cm, d, s, &cold);
+
+	freedfa(d);
+	freedfa(s);
+	NOERR();
+	if (v->g->cflags & REG_EXPECT)
+	{
+		assert(v->details != NULL);
+		if (cold != NULL)
+			v->details->rm_extend.rm_so = OFF(cold);
+		else
+			v->details->rm_extend.rm_so = OFF(v->stop);
+		v->details->rm_extend.rm_eo = OFF(v->stop);		/* unknown */
+	}
+	return ret;
+}
+
+/*
+ * cfindloop - the heart of cfind
+ */
+static int
+cfindloop(struct vars * v,
+		  struct cnfa * cnfa,
+		  struct colormap * cm,
+		  struct dfa * d,
+		  struct dfa * s,
+		  chr **coldp)			/* where to put coldstart pointer */
+{
+	chr		   *begin;
+	chr		   *end;
+	chr		   *cold;
+	chr		   *open;			/* open and close of range of possible starts */
+	chr		   *close;
+	chr		   *estart;
+	chr		   *estop;
+	int			er;
+	int			shorter = v->g->tree->flags & SHORTER;
+	int			hitend;
+
+	assert(d != NULL && s != NULL);
+	cold = NULL;
+	close = v->search_start;
+	do
+	{
+		/* Search with the search RE for match range at/beyond "close" */
+		MDEBUG(("\ncsearch at %ld\n", LOFF(close)));
+		close = shortest(v, s, close, close, v->stop, &cold, (int *) NULL);
+		if (ISERR())
+		{
+			*coldp = cold;
+			return v->err;
+		}
+		if (close == NULL)
+			break;				/* no more possible match anywhere */
+		assert(cold != NULL);
+		open = cold;
+		cold = NULL;
+		/* Search for matches starting between "open" and "close" inclusive */
+		MDEBUG(("cbetween %ld and %ld\n", LOFF(open), LOFF(close)));
+		for (begin = open; begin <= close; begin++)
+		{
+			MDEBUG(("\ncfind trying at %ld\n", LOFF(begin)));
+			estart = begin;
+			estop = v->stop;
+			for (;;)
+			{
+				/* Here we use the top node's detailed RE */
+				if (shorter)
+					end = shortest(v, d, begin, estart,
+								   estop, (chr **) NULL, &hitend);
+				else
+					end = longest(v, d, begin, estop,
+								  &hitend);
+				if (ISERR())
+				{
+					*coldp = cold;
+					return v->err;
+				}
+				if (hitend && cold == NULL)
+					cold = begin;
+				if (end == NULL)
+					break;		/* no match with this begin point, try next */
+				MDEBUG(("tentative end %ld\n", LOFF(end)));
+				/* Dissect the potential match to see if it really matches */
+				zapallsubs(v->pmatch, v->nmatch);
+				er = cdissect(v, v->g->tree, begin, end);
+				if (er == REG_OKAY)
+				{
+					if (v->nmatch > 0)
+					{
+						v->pmatch[0].rm_so = OFF(begin);
+						v->pmatch[0].rm_eo = OFF(end);
+					}
+					*coldp = cold;
+					return REG_OKAY;
+				}
+				if (er != REG_NOMATCH)
+				{
+					ERR(er);
+					*coldp = cold;
+					return er;
+				}
+				/* Try next longer/shorter match with same begin point */
+				if (shorter)
+				{
+					if (end == estop)
+						break;	/* no more, so try next begin point */
+					estart = end + 1;
+				}
+				else
+				{
+					if (end == begin)
+						break;	/* no more, so try next begin point */
+					estop = end - 1;
+				}
+			}					/* end loop over endpoint positions */
+		}						/* end loop over beginning positions */
+
+		/*
+		 * If we get here, there is no possible match starting at or before
+		 * "close", so consider matches beyond that.  We'll do a fresh search
+		 * with the search RE to find a new promising match range.
+		 */
+		close++;
+	} while (close < v->stop);
+
+	*coldp = cold;
+	return REG_NOMATCH;
+}
+
+/*
+ * zapallsubs - initialize all subexpression matches to "no match"
+ */
+static void
+zapallsubs(regmatch_t *p,
+		   size_t n)
+{
+	size_t		i;
+
+	for (i = n - 1; i > 0; i--)
+	{
+		p[i].rm_so = -1;
+		p[i].rm_eo = -1;
+	}
+}
+
+/*
+ * zaptreesubs - initialize subexpressions within subtree to "no match"
+ */
+static void
+zaptreesubs(struct vars * v,
+			struct subre * t)
+{
+	if (t->op == '(')
+	{
+		int			n = t->subno;
+
+		assert(n > 0);
+		if ((size_t) n < v->nmatch)
+		{
+			v->pmatch[n].rm_so = -1;
+			v->pmatch[n].rm_eo = -1;
+		}
+	}
+
+	if (t->left != NULL)
+		zaptreesubs(v, t->left);
+	if (t->right != NULL)
+		zaptreesubs(v, t->right);
+}
+
+/*
+ * subset - set subexpression match data for a successful subre
+ */
+static void
+subset(struct vars * v,
+	   struct subre * sub,
+	   chr *begin,
+	   chr *end)
+{
+	int			n = sub->subno;
+
+	assert(n > 0);
+	if ((size_t) n >= v->nmatch)
+		return;
+
+	MDEBUG(("setting %d\n", n));
+	v->pmatch[n].rm_so = OFF(begin);
+	v->pmatch[n].rm_eo = OFF(end);
+}
+
+/*
+ * cdissect - check backrefs and determine subexpression matches
+ *
+ * cdissect recursively processes a subre tree to check matching of backrefs
+ * and/or identify submatch boundaries for capture nodes.  The proposed match
+ * runs from "begin" to "end" (not including "end"), and we are basically
+ * "dissecting" it to see where the submatches are.
+ *
+ * Before calling any level of cdissect, the caller must have run the node's
+ * DFA and found that the proposed substring satisfies the DFA.  (We make
+ * the caller do that because in concatenation and iteration nodes, it's
+ * much faster to check all the substrings against the child DFAs before we
+ * recurse.)  Also, caller must have cleared subexpression match data via
+ * zaptreesubs (or zapallsubs at the top level).
+ */
+static int						/* regexec return code */
+cdissect(struct vars * v,
+		 struct subre * t,
+		 chr *begin,			/* beginning of relevant substring */
+		 chr *end)				/* end of same */
+{
+	int			er;
+
+	assert(t != NULL);
+	MDEBUG(("cdissect %ld-%ld %c\n", LOFF(begin), LOFF(end), t->op));
+
+	/* handy place to check for operation cancel */
+	if (CANCEL_REQUESTED(v->re))
+		return REG_CANCEL;
+	/* ... and stack overrun */
+	if (STACK_TOO_DEEP(v->re))
+		return REG_ETOOBIG;
+
+	switch (t->op)
+	{
+		case '=':				/* terminal node */
+			assert(t->left == NULL && t->right == NULL);
+			er = REG_OKAY;		/* no action, parent did the work */
+			break;
+		case 'b':				/* back reference */
+			assert(t->left == NULL && t->right == NULL);
+			er = cbrdissect(v, t, begin, end);
+			break;
+		case '.':				/* concatenation */
+			assert(t->left != NULL && t->right != NULL);
+			if (t->left->flags & SHORTER)		/* reverse scan */
+				er = crevcondissect(v, t, begin, end);
+			else
+				er = ccondissect(v, t, begin, end);
+			break;
+		case '|':				/* alternation */
+			assert(t->left != NULL);
+			er = caltdissect(v, t, begin, end);
+			break;
+		case '*':				/* iteration */
+			assert(t->left != NULL);
+			if (t->left->flags & SHORTER)		/* reverse scan */
+				er = creviterdissect(v, t, begin, end);
+			else
+				er = citerdissect(v, t, begin, end);
+			break;
+		case '(':				/* capturing */
+			assert(t->left != NULL && t->right == NULL);
+			assert(t->subno > 0);
+			er = cdissect(v, t->left, begin, end);
+			if (er == REG_OKAY)
+				subset(v, t, begin, end);
+			break;
+		default:
+			er = REG_ASSERT;
+			break;
+	}
+
+	/*
+	 * We should never have a match failure unless backrefs lurk below;
+	 * otherwise, either caller failed to check the DFA, or there's some
+	 * inconsistency between the DFA and the node's innards.
+	 */
+	assert(er != REG_NOMATCH || (t->flags & BACKR));
+
+	return er;
+}
+
+/*
+ * ccondissect - dissect match for concatenation node
+ */
+static int						/* regexec return code */
+ccondissect(struct vars * v,
+			struct subre * t,
+			chr *begin,			/* beginning of relevant substring */
+			chr *end)			/* end of same */
+{
+	struct dfa *d;
+	struct dfa *d2;
+	chr		   *mid;
+	int			er;
+
+	assert(t->op == '.');
+	assert(t->left != NULL && t->left->cnfa.nstates > 0);
+	assert(t->right != NULL && t->right->cnfa.nstates > 0);
+	assert(!(t->left->flags & SHORTER));
+
+	d = getsubdfa(v, t->left);
+	NOERR();
+	d2 = getsubdfa(v, t->right);
+	NOERR();
+	MDEBUG(("cconcat %d\n", t->id));
+
+	/* pick a tentative midpoint */
+	mid = longest(v, d, begin, end, (int *) NULL);
+	NOERR();
+	if (mid == NULL)
+		return REG_NOMATCH;
+	MDEBUG(("tentative midpoint %ld\n", LOFF(mid)));
+
+	/* iterate until satisfaction or failure */
+	for (;;)
+	{
+		/* try this midpoint on for size */
+		if (longest(v, d2, mid, end, (int *) NULL) == end)
+		{
+			er = cdissect(v, t->left, begin, mid);
+			if (er == REG_OKAY)
+			{
+				er = cdissect(v, t->right, mid, end);
+				if (er == REG_OKAY)
+				{
+					/* satisfaction */
+					MDEBUG(("successful\n"));
+					return REG_OKAY;
+				}
+			}
+			if (er != REG_NOMATCH)
+				return er;
+		}
+		NOERR();
+
+		/* that midpoint didn't work, find a new one */
+		if (mid == begin)
+		{
+			/* all possibilities exhausted */
+			MDEBUG(("%d no midpoint\n", t->id));
+			return REG_NOMATCH;
+		}
+		mid = longest(v, d, begin, mid - 1, (int *) NULL);
+		NOERR();
+		if (mid == NULL)
+		{
+			/* failed to find a new one */
+			MDEBUG(("%d failed midpoint\n", t->id));
+			return REG_NOMATCH;
+		}
+		MDEBUG(("%d: new midpoint %ld\n", t->id, LOFF(mid)));
+		zaptreesubs(v, t->left);
+		zaptreesubs(v, t->right);
+	}
+
+	/* can't get here */
+	return REG_ASSERT;
+}
+
+/*
+ * crevcondissect - dissect match for concatenation node, shortest-first
+ */
+static int						/* regexec return code */
+crevcondissect(struct vars * v,
+			   struct subre * t,
+			   chr *begin,		/* beginning of relevant substring */
+			   chr *end)		/* end of same */
+{
+	struct dfa *d;
+	struct dfa *d2;
+	chr		   *mid;
+	int			er;
+
+	assert(t->op == '.');
+	assert(t->left != NULL && t->left->cnfa.nstates > 0);
+	assert(t->right != NULL && t->right->cnfa.nstates > 0);
+	assert(t->left->flags & SHORTER);
+
+	d = getsubdfa(v, t->left);
+	NOERR();
+	d2 = getsubdfa(v, t->right);
+	NOERR();
+	MDEBUG(("crevcon %d\n", t->id));
+
+	/* pick a tentative midpoint */
+	mid = shortest(v, d, begin, begin, end, (chr **) NULL, (int *) NULL);
+	NOERR();
+	if (mid == NULL)
+		return REG_NOMATCH;
+	MDEBUG(("tentative midpoint %ld\n", LOFF(mid)));
+
+	/* iterate until satisfaction or failure */
+	for (;;)
+	{
+		/* try this midpoint on for size */
+		if (longest(v, d2, mid, end, (int *) NULL) == end)
+		{
+			er = cdissect(v, t->left, begin, mid);
+			if (er == REG_OKAY)
+			{
+				er = cdissect(v, t->right, mid, end);
+				if (er == REG_OKAY)
+				{
+					/* satisfaction */
+					MDEBUG(("successful\n"));
+					return REG_OKAY;
+				}
+			}
+			if (er != REG_NOMATCH)
+				return er;
+		}
+		NOERR();
+
+		/* that midpoint didn't work, find a new one */
+		if (mid == end)
+		{
+			/* all possibilities exhausted */
+			MDEBUG(("%d no midpoint\n", t->id));
+			return REG_NOMATCH;
+		}
+		mid = shortest(v, d, begin, mid + 1, end, (chr **) NULL, (int *) NULL);
+		NOERR();
+		if (mid == NULL)
+		{
+			/* failed to find a new one */
+			MDEBUG(("%d failed midpoint\n", t->id));
+			return REG_NOMATCH;
+		}
+		MDEBUG(("%d: new midpoint %ld\n", t->id, LOFF(mid)));
+		zaptreesubs(v, t->left);
+		zaptreesubs(v, t->right);
+	}
+
+	/* can't get here */
+	return REG_ASSERT;
+}
+
+/*
+ * cbrdissect - dissect match for backref node
+ */
+static int						/* regexec return code */
+cbrdissect(struct vars * v,
+		   struct subre * t,
+		   chr *begin,			/* beginning of relevant substring */
+		   chr *end)			/* end of same */
+{
+	int			n = t->subno;
+	size_t		numreps;
+	size_t		tlen;
+	size_t		brlen;
+	chr		   *brstring;
+	chr		   *p;
+	int			min = t->min;
+	int			max = t->max;
+
+	assert(t != NULL);
+	assert(t->op == 'b');
+	assert(n >= 0);
+	assert((size_t) n < v->nmatch);
+
+	MDEBUG(("cbackref n%d %d{%d-%d}\n", t->id, n, min, max));
+
+	/* get the backreferenced string */
+	if (v->pmatch[n].rm_so == -1)
+		return REG_NOMATCH;
+	brstring = v->start + v->pmatch[n].rm_so;
+	brlen = v->pmatch[n].rm_eo - v->pmatch[n].rm_so;
+
+	/* special cases for zero-length strings */
+	if (brlen == 0)
+	{
+		/*
+		 * matches only if target is zero length, but any number of
+		 * repetitions can be considered to be present
+		 */
+		if (begin == end && min <= max)
+		{
+			MDEBUG(("cbackref matched trivially\n"));
+			return REG_OKAY;
+		}
+		return REG_NOMATCH;
+	}
+	if (begin == end)
+	{
+		/* matches only if zero repetitions are okay */
+		if (min == 0)
+		{
+			MDEBUG(("cbackref matched trivially\n"));
+			return REG_OKAY;
+		}
+		return REG_NOMATCH;
+	}
+
+	/*
+	 * check target length to see if it could possibly be an allowed number of
+	 * repetitions of brstring
+	 */
+	assert(end > begin);
+	tlen = end - begin;
+	if (tlen % brlen != 0)
+		return REG_NOMATCH;
+	numreps = tlen / brlen;
+	if (numreps < min || (numreps > max && max != DUPINF))
+		return REG_NOMATCH;
+
+	/* okay, compare the actual string contents */
+	p = begin;
+	while (numreps-- > 0)
+	{
+		if ((*v->g->compare) (brstring, p, brlen) != 0)
+			return REG_NOMATCH;
+		p += brlen;
+	}
+
+	MDEBUG(("cbackref matched\n"));
+	return REG_OKAY;
+}
+
+/*
+ * caltdissect - dissect match for alternation node
+ */
+static int						/* regexec return code */
+caltdissect(struct vars * v,
+			struct subre * t,
+			chr *begin,			/* beginning of relevant substring */
+			chr *end)			/* end of same */
+{
+	struct dfa *d;
+	int			er;
+
+	/* We loop, rather than tail-recurse, to handle a chain of alternatives */
+	while (t != NULL)
+	{
+		assert(t->op == '|');
+		assert(t->left != NULL && t->left->cnfa.nstates > 0);
+
+		MDEBUG(("calt n%d\n", t->id));
+
+		d = getsubdfa(v, t->left);
+		NOERR();
+		if (longest(v, d, begin, end, (int *) NULL) == end)
+		{
+			MDEBUG(("calt matched\n"));
+			er = cdissect(v, t->left, begin, end);
+			if (er != REG_NOMATCH)
+				return er;
+		}
+		NOERR();
+
+		t = t->right;
+	}
+
+	return REG_NOMATCH;
+}
+
+/*
+ * citerdissect - dissect match for iteration node
+ */
+static int						/* regexec return code */
+citerdissect(struct vars * v,
+			 struct subre * t,
+			 chr *begin,		/* beginning of relevant substring */
+			 chr *end)			/* end of same */
+{
+	struct dfa *d;
+	chr		  **endpts;
+	chr		   *limit;
+	int			min_matches;
+	size_t		max_matches;
+	int			nverified;
+	int			k;
+	int			i;
+	int			er;
+
+	assert(t->op == '*');
+	assert(t->left != NULL && t->left->cnfa.nstates > 0);
+	assert(!(t->left->flags & SHORTER));
+	assert(begin <= end);
+
+	/*
+	 * For the moment, assume the minimum number of matches is 1.  If zero
+	 * matches are allowed, and the target string is empty, we are allowed to
+	 * match regardless of the contents of the iter node --- but we would
+	 * prefer to match once, so that capturing parens get set.  (An example of
+	 * the concern here is a pattern like "()*\1", which historically this
+	 * code has allowed to succeed.)  Therefore, we deal with the zero-matches
+	 * case at the bottom, after failing to find any other way to match.
+	 */
+	min_matches = t->min;
+	if (min_matches <= 0)
+		min_matches = 1;
+
+	/*
+	 * We need workspace to track the endpoints of each sub-match.  Normally
+	 * we consider only nonzero-length sub-matches, so there can be at most
+	 * end-begin of them.  However, if min is larger than that, we will also
+	 * consider zero-length sub-matches in order to find enough matches.
+	 *
+	 * For convenience, endpts[0] contains the "begin" pointer and we store
+	 * sub-match endpoints in endpts[1..max_matches].
+	 */
+	max_matches = end - begin;
+	if (max_matches > t->max && t->max != DUPINF)
+		max_matches = t->max;
+	if (max_matches < min_matches)
+		max_matches = min_matches;
+	endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+	if (endpts == NULL)
+		return REG_ESPACE;
+	endpts[0] = begin;
+
+	d = getsubdfa(v, t->left);
+	if (ISERR())
+	{
+		FREE(endpts);
+		return v->err;
+	}
+	MDEBUG(("citer %d\n", t->id));
+
+	/*
+	 * Our strategy is to first find a set of sub-match endpoints that are
+	 * valid according to the child node's DFA, and then recursively dissect
+	 * each sub-match to confirm validity.  If any validity check fails,
+	 * backtrack the last sub-match and try again.  And, when we next try for
+	 * a validity check, we need not recheck any successfully verified
+	 * sub-matches that we didn't move the endpoints of.  nverified remembers
+	 * how many sub-matches are currently known okay.
+	 */
+
+	/* initialize to consider first sub-match */
+	nverified = 0;
+	k = 1;
+	limit = end;
+
+	/* iterate until satisfaction or failure */
+	while (k > 0)
+	{
+		/* try to find an endpoint for the k'th sub-match */
+		endpts[k] = longest(v, d, endpts[k - 1], limit, (int *) NULL);
+		if (ISERR())
+		{
+			FREE(endpts);
+			return v->err;
+		}
+		if (endpts[k] == NULL)
+		{
+			/* no match possible, so see if we can shorten previous one */
+			k--;
+			goto backtrack;
+		}
+		MDEBUG(("%d: working endpoint %d: %ld\n",
+				t->id, k, LOFF(endpts[k])));
+
+		/* k'th sub-match can no longer be considered verified */
+		if (nverified >= k)
+			nverified = k - 1;
+
+		if (endpts[k] != end)
+		{
+			/* haven't reached end yet, try another iteration if allowed */
+			if (k >= max_matches)
+			{
+				/* must try to shorten some previous match */
+				k--;
+				goto backtrack;
+			}
+
+			/* reject zero-length match unless necessary to achieve min */
+			if (endpts[k] == endpts[k - 1] &&
+				(k >= min_matches || min_matches - k < end - endpts[k]))
+				goto backtrack;
+
+			k++;
+			limit = end;
+			continue;
+		}
+
+		/*
+		 * We've identified a way to divide the string into k sub-matches that
+		 * works so far as the child DFA can tell.  If k is an allowed number
+		 * of matches, start the slow part: recurse to verify each sub-match.
+		 * We always have k <= max_matches, needn't check that.
+		 */
+		if (k < min_matches)
+			goto backtrack;
+
+		MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k));
+
+		for (i = nverified + 1; i <= k; i++)
+		{
+			zaptreesubs(v, t->left);
+			er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
+			if (er == REG_OKAY)
+			{
+				nverified = i;
+				continue;
+			}
+			if (er == REG_NOMATCH)
+				break;
+			/* oops, something failed */
+			FREE(endpts);
+			return er;
+		}
+
+		if (i > k)
+		{
+			/* satisfaction */
+			MDEBUG(("%d successful\n", t->id));
+			FREE(endpts);
+			return REG_OKAY;
+		}
+
+		/* match failed to verify, so backtrack */
+
+backtrack:
+
+		/*
+		 * Must consider shorter versions of the current sub-match.  However,
+		 * we'll only ask for a zero-length match if necessary.
+		 */
+		while (k > 0)
+		{
+			chr		   *prev_end = endpts[k - 1];
+
+			if (endpts[k] > prev_end)
+			{
+				limit = endpts[k] - 1;
+				if (limit > prev_end ||
+					(k < min_matches && min_matches - k >= end - prev_end))
+				{
+					/* break out of backtrack loop, continue the outer one */
+					break;
+				}
+			}
+			/* can't shorten k'th sub-match any more, consider previous one */
+			k--;
+		}
+	}
+
+	/* all possibilities exhausted */
+	FREE(endpts);
+
+	/*
+	 * Now consider the possibility that we can match to a zero-length string
+	 * by using zero repetitions.
+	 */
+	if (t->min == 0 && begin == end)
+	{
+		MDEBUG(("%d allowing zero matches\n", t->id));
+		return REG_OKAY;
+	}
+
+	MDEBUG(("%d failed\n", t->id));
+	return REG_NOMATCH;
+}
+
+/*
+ * creviterdissect - dissect match for iteration node, shortest-first
+ */
+static int						/* regexec return code */
+creviterdissect(struct vars * v,
+				struct subre * t,
+				chr *begin,		/* beginning of relevant substring */
+				chr *end)		/* end of same */
+{
+	struct dfa *d;
+	chr		  **endpts;
+	chr		   *limit;
+	int			min_matches;
+	size_t		max_matches;
+	int			nverified;
+	int			k;
+	int			i;
+	int			er;
+
+	assert(t->op == '*');
+	assert(t->left != NULL && t->left->cnfa.nstates > 0);
+	assert(t->left->flags & SHORTER);
+	assert(begin <= end);
+
+	/*
+	 * If zero matches are allowed, and target string is empty, just declare
+	 * victory.  OTOH, if target string isn't empty, zero matches can't work
+	 * so we pretend the min is 1.
+	 */
+	min_matches = t->min;
+	if (min_matches <= 0)
+	{
+		if (begin == end)
+			return REG_OKAY;
+		min_matches = 1;
+	}
+
+	/*
+	 * We need workspace to track the endpoints of each sub-match.  Normally
+	 * we consider only nonzero-length sub-matches, so there can be at most
+	 * end-begin of them.  However, if min is larger than that, we will also
+	 * consider zero-length sub-matches in order to find enough matches.
+	 *
+	 * For convenience, endpts[0] contains the "begin" pointer and we store
+	 * sub-match endpoints in endpts[1..max_matches].
+	 */
+	max_matches = end - begin;
+	if (max_matches > t->max && t->max != DUPINF)
+		max_matches = t->max;
+	if (max_matches < min_matches)
+		max_matches = min_matches;
+	endpts = (chr **) MALLOC((max_matches + 1) * sizeof(chr *));
+	if (endpts == NULL)
+		return REG_ESPACE;
+	endpts[0] = begin;
+
+	d = getsubdfa(v, t->left);
+	if (ISERR())
+	{
+		FREE(endpts);
+		return v->err;
+	}
+	MDEBUG(("creviter %d\n", t->id));
+
+	/*
+	 * Our strategy is to first find a set of sub-match endpoints that are
+	 * valid according to the child node's DFA, and then recursively dissect
+	 * each sub-match to confirm validity.  If any validity check fails,
+	 * backtrack the last sub-match and try again.  And, when we next try for
+	 * a validity check, we need not recheck any successfully verified
+	 * sub-matches that we didn't move the endpoints of.  nverified remembers
+	 * how many sub-matches are currently known okay.
+	 */
+
+	/* initialize to consider first sub-match */
+	nverified = 0;
+	k = 1;
+	limit = begin;
+
+	/* iterate until satisfaction or failure */
+	while (k > 0)
+	{
+		/* disallow zero-length match unless necessary to achieve min */
+		if (limit == endpts[k - 1] &&
+			limit != end &&
+			(k >= min_matches || min_matches - k < end - limit))
+			limit++;
+
+		/* if this is the last allowed sub-match, it must reach to the end */
+		if (k >= max_matches)
+			limit = end;
+
+		/* try to find an endpoint for the k'th sub-match */
+		endpts[k] = shortest(v, d, endpts[k - 1], limit, end,
+							 (chr **) NULL, (int *) NULL);
+		if (ISERR())
+		{
+			FREE(endpts);
+			return v->err;
+		}
+		if (endpts[k] == NULL)
+		{
+			/* no match possible, so see if we can lengthen previous one */
+			k--;
+			goto backtrack;
+		}
+		MDEBUG(("%d: working endpoint %d: %ld\n",
+				t->id, k, LOFF(endpts[k])));
+
+		/* k'th sub-match can no longer be considered verified */
+		if (nverified >= k)
+			nverified = k - 1;
+
+		if (endpts[k] != end)
+		{
+			/* haven't reached end yet, try another iteration if allowed */
+			if (k >= max_matches)
+			{
+				/* must try to lengthen some previous match */
+				k--;
+				goto backtrack;
+			}
+
+			k++;
+			limit = endpts[k - 1];
+			continue;
+		}
+
+		/*
+		 * We've identified a way to divide the string into k sub-matches that
+		 * works so far as the child DFA can tell.  If k is an allowed number
+		 * of matches, start the slow part: recurse to verify each sub-match.
+		 * We always have k <= max_matches, needn't check that.
+		 */
+		if (k < min_matches)
+			goto backtrack;
+
+		MDEBUG(("%d: verifying %d..%d\n", t->id, nverified + 1, k));
+
+		for (i = nverified + 1; i <= k; i++)
+		{
+			zaptreesubs(v, t->left);
+			er = cdissect(v, t->left, endpts[i - 1], endpts[i]);
+			if (er == REG_OKAY)
+			{
+				nverified = i;
+				continue;
+			}
+			if (er == REG_NOMATCH)
+				break;
+			/* oops, something failed */
+			FREE(endpts);
+			return er;
+		}
+
+		if (i > k)
+		{
+			/* satisfaction */
+			MDEBUG(("%d successful\n", t->id));
+			FREE(endpts);
+			return REG_OKAY;
+		}
+
+		/* match failed to verify, so backtrack */
+
+backtrack:
+
+		/*
+		 * Must consider longer versions of the current sub-match.
+		 */
+		while (k > 0)
+		{
+			if (endpts[k] < end)
+			{
+				limit = endpts[k] + 1;
+				/* break out of backtrack loop, continue the outer one */
+				break;
+			}
+			/* can't lengthen k'th sub-match any more, consider previous one */
+			k--;
+		}
+	}
+
+	/* all possibilities exhausted */
+	MDEBUG(("%d failed\n", t->id));
+	FREE(endpts);
+	return REG_NOMATCH;
+}
+
+
+
+#include "rege_dfa.c"
diff --git a/src/common/regex/regexport.c b/src/common/regex/regexport.c
new file mode 100644
index 0000000..9134071
--- /dev/null
+++ b/src/common/regex/regexport.c
@@ -0,0 +1,292 @@
+/*-------------------------------------------------------------------------
+ *
+ * regexport.c
+ *	  Functions for exporting info about a regex's NFA
+ *
+ * In this implementation, the NFA defines a necessary but not sufficient
+ * condition for a string to match the regex: that is, there can be strings
+ * that match the NFA but don't match the full regex, but not vice versa.
+ * Thus, for example, it is okay for the functions below to ignore lookaround
+ * constraints, which merely constrain the string some more.
+ *
+ * Notice that these functions return info into caller-provided arrays
+ * rather than doing their own malloc's.  This simplifies the APIs by
+ * eliminating a class of error conditions, and in the case of colors
+ * allows the caller to decide how big is too big to bother with.
+ *
+ *
+ * Portions Copyright (c) 2013-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1998, 1999 Henry Spencer
+ *
+ * IDENTIFICATION
+ *	  src/backend/regex/regexport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "regex/regguts.h"
+
+#include "regex/regexport.h"
+
+static void scancolormap(struct colormap * cm, int co,
+			 union tree * t, int level, chr partial,
+			 pg_wchar **chars, int *chars_len);
+
+
+/*
+ * Get total number of NFA states.
+ */
+int
+pg_reg_getnumstates(const regex_t *regex)
+{
+	struct cnfa *cnfa;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	return cnfa->nstates;
+}
+
+/*
+ * Get initial state of NFA.
+ */
+int
+pg_reg_getinitialstate(const regex_t *regex)
+{
+	struct cnfa *cnfa;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	return cnfa->pre;
+}
+
+/*
+ * Get final state of NFA.
+ */
+int
+pg_reg_getfinalstate(const regex_t *regex)
+{
+	struct cnfa *cnfa;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	return cnfa->post;
+}
+
+/*
+ * Get number of outgoing NFA arcs of state number "st".
+ *
+ * Note: LACON arcs are ignored, both here and in pg_reg_getoutarcs().
+ */
+int
+pg_reg_getnumoutarcs(const regex_t *regex, int st)
+{
+	struct cnfa *cnfa;
+	struct carc *ca;
+	int			count;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	if (st < 0 || st >= cnfa->nstates)
+		return 0;
+	count = 0;
+	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+	{
+		if (ca->co < cnfa->ncolors)
+			count++;
+	}
+	return count;
+}
+
+/*
+ * Write array of outgoing NFA arcs of state number "st" into arcs[],
+ * whose length arcs_len must be at least as long as indicated by
+ * pg_reg_getnumoutarcs(), else not all arcs will be returned.
+ */
+void
+pg_reg_getoutarcs(const regex_t *regex, int st,
+				  regex_arc_t *arcs, int arcs_len)
+{
+	struct cnfa *cnfa;
+	struct carc *ca;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	if (st < 0 || st >= cnfa->nstates || arcs_len <= 0)
+		return;
+	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+	{
+		if (ca->co < cnfa->ncolors)
+		{
+			arcs->co = ca->co;
+			arcs->to = ca->to;
+			arcs++;
+			if (--arcs_len == 0)
+				break;
+		}
+	}
+}
+
+/*
+ * Get total number of colors.
+ */
+int
+pg_reg_getnumcolors(const regex_t *regex)
+{
+	struct colormap *cm;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cm = &((struct guts *) regex->re_guts)->cmap;
+
+	return cm->max + 1;
+}
+
+/*
+ * Check if color is beginning of line/string.
+ *
+ * (We might at some point need to offer more refined handling of pseudocolors,
+ * but this will do for now.)
+ */
+int
+pg_reg_colorisbegin(const regex_t *regex, int co)
+{
+	struct cnfa *cnfa;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	if (co == cnfa->bos[0] || co == cnfa->bos[1])
+		return true;
+	else
+		return false;
+}
+
+/*
+ * Check if color is end of line/string.
+ */
+int
+pg_reg_colorisend(const regex_t *regex, int co)
+{
+	struct cnfa *cnfa;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cnfa = &((struct guts *) regex->re_guts)->search;
+
+	if (co == cnfa->eos[0] || co == cnfa->eos[1])
+		return true;
+	else
+		return false;
+}
+
+/*
+ * Get number of member chrs of color number "co".
+ *
+ * Note: we return -1 if the color number is invalid, or if it is a special
+ * color (WHITE or a pseudocolor), or if the number of members is uncertain.
+ * The latter case cannot arise right now but is specified to allow for future
+ * improvements (see musings about run-time handling of higher character codes
+ * in regex/README).  Callers should not try to extract the members if -1 is
+ * returned.
+ */
+int
+pg_reg_getnumcharacters(const regex_t *regex, int co)
+{
+	struct colormap *cm;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cm = &((struct guts *) regex->re_guts)->cmap;
+
+	if (co <= 0 || co > cm->max)	/* we reject 0 which is WHITE */
+		return -1;
+	if (cm->cd[co].flags & PSEUDO)		/* also pseudocolors (BOS etc) */
+		return -1;
+
+	return cm->cd[co].nchrs;
+}
+
+/*
+ * Write array of member chrs of color number "co" into chars[],
+ * whose length chars_len must be at least as long as indicated by
+ * pg_reg_getnumcharacters(), else not all chars will be returned.
+ *
+ * Fetching the members of WHITE or a pseudocolor is not supported.
+ *
+ * Caution: this is a relatively expensive operation.
+ */
+void
+pg_reg_getcharacters(const regex_t *regex, int co,
+					 pg_wchar *chars, int chars_len)
+{
+	struct colormap *cm;
+
+	assert(regex != NULL && regex->re_magic == REMAGIC);
+	cm = &((struct guts *) regex->re_guts)->cmap;
+
+	if (co <= 0 || co > cm->max || chars_len <= 0)
+		return;
+	if (cm->cd[co].flags & PSEUDO)
+		return;
+
+	/* Recursively search the colormap tree */
+	scancolormap(cm, co, cm->tree, 0, 0, &chars, &chars_len);
+}
+
+/*
+ * Recursively scan the colormap tree to find chrs belonging to color "co".
+ * See regex/README for info about the tree structure.
+ *
+ * t: tree block to scan
+ * level: level (from 0) of t
+ * partial: partial chr code for chrs within t
+ * chars, chars_len: output area
+ */
+static void
+scancolormap(struct colormap * cm, int co,
+			 union tree * t, int level, chr partial,
+			 pg_wchar **chars, int *chars_len)
+{
+	int			i;
+
+	if (level < NBYTS - 1)
+	{
+		/* non-leaf node */
+		for (i = 0; i < BYTTAB; i++)
+		{
+			/*
+			 * We do not support search for chrs of color 0 (WHITE), so
+			 * all-white subtrees need not be searched.  These can be
+			 * recognized because they are represented by the fill blocks in
+			 * the colormap struct.  This typically allows us to avoid
+			 * scanning large regions of higher-numbered chrs.
+			 */
+			if (t->tptr[i] == &cm->tree[level + 1])
+				continue;
+
+			/* Recursively scan next level down */
+			scancolormap(cm, co,
+						 t->tptr[i], level + 1,
+						 (partial | (chr) i) << BYTBITS,
+						 chars, chars_len);
+		}
+	}
+	else
+	{
+		/* leaf node */
+		for (i = 0; i < BYTTAB; i++)
+		{
+			if (t->tcolor[i] == co)
+			{
+				if (*chars_len > 0)
+				{
+					**chars = partial | (chr) i;
+					(*chars)++;
+					(*chars_len)--;
+				}
+			}
+		}
+	}
+}
diff --git a/src/common/regex/regfree.c b/src/common/regex/regfree.c
new file mode 100644
index 0000000..4624b0a
--- /dev/null
+++ b/src/common/regex/regfree.c
@@ -0,0 +1,54 @@
+/*
+ * regfree - free an RE
+ *
+ * Copyright (c) 1998, 1999 Henry Spencer.  All rights reserved.
+ *
+ * Development of this software was funded, in part, by Cray Research Inc.,
+ * UUNET Communications Services Inc., Sun Microsystems Inc., and Scriptics
+ * Corporation, none of whom are responsible for the results.  The author
+ * thanks all of them.
+ *
+ * Redistribution and use in source and binary forms -- with or without
+ * modification -- are permitted for any purpose, provided that
+ * redistributions in source form retain this entire copyright notice and
+ * indicate the origin and nature of any modifications.
+ *
+ * I'd appreciate being given credit for this package in the documentation
+ * of software which uses it, but that is not a requirement.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * HENRY SPENCER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * src/backend/regex/regfree.c
+ *
+ *
+ * You might think that this could be incorporated into regcomp.c, and
+ * that would be a reasonable idea... except that this is a generic
+ * function (with a generic name), applicable to all compiled REs
+ * regardless of the size of their characters, whereas the stuff in
+ * regcomp.c gets compiled once per character size.
+ */
+
+#include "regex/regguts.h"
+
+
+/*
+ * pg_regfree - free an RE (generic function, punts to RE-specific function)
+ *
+ * Ignoring invocation with NULL is a convenience.
+ */
+void
+pg_regfree(regex_t *re)
+{
+	if (re == NULL)
+		return;
+	re->free(re);
+}
diff --git a/src/common/regex/regprefix.c b/src/common/regex/regprefix.c
new file mode 100644
index 0000000..8692845
--- /dev/null
+++ b/src/common/regex/regprefix.c
@@ -0,0 +1,257 @@
+/*-------------------------------------------------------------------------
+ *
+ * regprefix.c
+ *	  Extract a common prefix, if any, from a compiled regex.
+ *
+ *
+ * Portions Copyright (c) 2012-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1998, 1999 Henry Spencer
+ *
+ * IDENTIFICATION
+ *	  src/backend/regex/regprefix.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "regex/regguts.h"
+
+
+/*
+ * forward declarations
+ */
+static int findprefix(struct cnfa * cnfa, struct colormap * cm,
+		   chr *string, size_t *slength);
+
+
+/*
+ * pg_regprefix - get common prefix for regular expression
+ *
+ * Returns one of:
+ *	REG_NOMATCH: there is no common prefix of strings matching the regex
+ *	REG_PREFIX: there is a common prefix of strings matching the regex
+ *	REG_EXACT: all strings satisfying the regex must match the same string
+ *	or a REG_XXX error code
+ *
+ * In the non-failure cases, *string is set to a malloc'd string containing
+ * the common prefix or exact value, of length *slength (measured in chrs
+ * not bytes!).
+ *
+ * This function does not analyze all complex cases (such as lookaround
+ * constraints) exactly.  Therefore it is possible that some strings matching
+ * the reported prefix or exact-match string do not satisfy the regex.  But
+ * it should never be the case that a string satisfying the regex does not
+ * match the reported prefix or exact-match string.
+ */
+int
+pg_regprefix(regex_t *re,
+			 chr **string,
+			 size_t *slength)
+{
+	struct guts *g;
+	struct cnfa *cnfa;
+	int			st;
+
+	/* sanity checks */
+	if (string == NULL || slength == NULL)
+		return REG_INVARG;
+	*string = NULL;				/* initialize for failure cases */
+	*slength = 0;
+	if (re == NULL || re->re_magic != REMAGIC)
+		return REG_INVARG;
+	if (re->re_csize != sizeof(chr))
+		return REG_MIXED;
+
+	/* Initialize locale-dependent support */
+	pg_set_regex_collation(re->re_collation);
+
+	/* setup */
+	g = (struct guts *) re->re_guts;
+	if (g->info & REG_UIMPOSSIBLE)
+		return REG_NOMATCH;
+
+	/*
+	 * This implementation considers only the search NFA for the topmost regex
+	 * tree node.  Therefore, constraints such as backrefs are not fully
+	 * applied, which is allowed per the function's API spec.
+	 */
+	assert(g->tree != NULL);
+	cnfa = &g->tree->cnfa;
+
+	/*
+	 * Since a correct NFA should never contain any exit-free loops, it should
+	 * not be possible for our traversal to return to a previously visited NFA
+	 * state.  Hence we need at most nstates chrs in the output string.
+	 */
+	*string = (chr *) MALLOC(cnfa->nstates * sizeof(chr));
+	if (*string == NULL)
+		return REG_ESPACE;
+
+	/* do it */
+	st = findprefix(cnfa, &g->cmap, *string, slength);
+
+	assert(*slength <= cnfa->nstates);
+
+	/* clean up */
+	if (st != REG_PREFIX && st != REG_EXACT)
+	{
+		FREE(*string);
+		*string = NULL;
+		*slength = 0;
+	}
+
+	return st;
+}
+
+/*
+ * findprefix - extract common prefix from cNFA
+ *
+ * Results are returned into the preallocated chr array string[], with
+ * *slength (which must be preset to zero) incremented for each chr.
+ */
+static int						/* regprefix return code */
+findprefix(struct cnfa * cnfa,
+		   struct colormap * cm,
+		   chr *string,
+		   size_t *slength)
+{
+	int			st;
+	int			nextst;
+	color		thiscolor;
+	chr			c;
+	struct carc *ca;
+
+	/*
+	 * The "pre" state must have only BOS/BOL outarcs, else pattern isn't
+	 * anchored left.  If we have both BOS and BOL, they must go to the same
+	 * next state.
+	 */
+	st = cnfa->pre;
+	nextst = -1;
+	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+	{
+		if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
+		{
+			if (nextst == -1)
+				nextst = ca->to;
+			else if (nextst != ca->to)
+				return REG_NOMATCH;
+		}
+		else
+			return REG_NOMATCH;
+	}
+	if (nextst == -1)
+		return REG_NOMATCH;
+
+	/*
+	 * Scan through successive states, stopping as soon as we find one with
+	 * more than one acceptable transition character (either multiple colors
+	 * on out-arcs, or a color with more than one member chr).
+	 *
+	 * We could find a state with multiple out-arcs that are all labeled with
+	 * the same singleton color; this comes from patterns like "^ab(cde|cxy)".
+	 * In that case we add the chr "c" to the output string but then exit the
+	 * loop with nextst == -1.  This leaves a little bit on the table: if the
+	 * pattern is like "^ab(cde|cdy)", we won't notice that "d" could be added
+	 * to the prefix.  But chasing multiple parallel state chains doesn't seem
+	 * worth the trouble.
+	 */
+	do
+	{
+		st = nextst;
+		nextst = -1;
+		thiscolor = COLORLESS;
+		for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+		{
+			/* We can ignore BOS/BOL arcs */
+			if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
+				continue;
+			/* ... but EOS/EOL arcs terminate the search, as do LACONs */
+			if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1] ||
+				ca->co >= cnfa->ncolors)
+			{
+				thiscolor = COLORLESS;
+				break;
+			}
+			if (thiscolor == COLORLESS)
+			{
+				/* First plain outarc */
+				thiscolor = ca->co;
+				nextst = ca->to;
+			}
+			else if (thiscolor == ca->co)
+			{
+				/* Another plain outarc for same color */
+				nextst = -1;
+			}
+			else
+			{
+				/* More than one plain outarc color terminates the search */
+				thiscolor = COLORLESS;
+				break;
+			}
+		}
+		/* Done if we didn't find exactly one color on plain outarcs */
+		if (thiscolor == COLORLESS)
+			break;
+		/* The color must be a singleton */
+		if (cm->cd[thiscolor].nchrs != 1)
+			break;
+
+		/*
+		 * Identify the color's sole member chr and add it to the prefix
+		 * string.  In general the colormap data structure doesn't provide a
+		 * way to find color member chrs, except by trying GETCOLOR() on each
+		 * possible chr value, which won't do at all.  However, for the cases
+		 * we care about it should be sufficient to test the "firstchr" value,
+		 * that is the first chr ever added to the color.  There are cases
+		 * where this might no longer be a member of the color (so we do need
+		 * to test), but none of them are likely to arise for a character that
+		 * is a member of a common prefix.  If we do hit such a corner case,
+		 * we just fall out without adding anything to the prefix string.
+		 */
+		c = cm->cd[thiscolor].firstchr;
+		if (GETCOLOR(cm, c) != thiscolor)
+			break;
+
+		string[(*slength)++] = c;
+
+		/* Advance to next state, but only if we have a unique next state */
+	} while (nextst != -1);
+
+	/*
+	 * If we ended at a state that only has EOS/EOL outarcs leading to the
+	 * "post" state, then we have an exact-match string.  Note this is true
+	 * even if the string is of zero length.
+	 */
+	nextst = -1;
+	for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+	{
+		if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1])
+		{
+			if (nextst == -1)
+				nextst = ca->to;
+			else if (nextst != ca->to)
+			{
+				nextst = -1;
+				break;
+			}
+		}
+		else
+		{
+			nextst = -1;
+			break;
+		}
+	}
+	if (nextst == cnfa->post)
+		return REG_EXACT;
+
+	/*
+	 * Otherwise, if we were unable to identify any prefix characters, say
+	 * NOMATCH --- the pattern is anchored left, but doesn't specify any
+	 * particular first character.
+	 */
+	if (*slength > 0)
+		return REG_PREFIX;
+
+	return REG_NOMATCH;
+}
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index dbb461a..e0b3b97 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -29,7 +29,11 @@
  */
 
 /* headers if any */
-#include "postgres.h"
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+ #include "postgres.h"
+#endif
 
 #include <ctype.h>
 #include <limits.h>
@@ -53,7 +57,11 @@
 #define MALLOC(n)		malloc(n)
 #define FREE(p)			free(VS(p))
 #define REALLOC(p,n)	realloc(VS(p),n)
+
+/* See Section 6 of include/c.h for details */
+#ifndef FRONTEND
 #define assert(x)		Assert(x)
+#endif
 
 /* internal character type and related */
 typedef pg_wchar chr;			/* the type itself */
diff --git a/src/include/regex/regex.h b/src/include/regex/regex.h
index 2f89dc9..5388f89 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -51,8 +51,15 @@ typedef long regoff_t;
  * other interface types
  */
 
+/* call backs for regexp routines */
+typedef struct reg_callbacks
+{
+	int			(*cancel_requested)(void);
+	int			(*stack_too_deep)(void);
+} reg_callbacks;
+
 /* the biggie, a compiled RE (or rather, a front end to same) */
-typedef struct
+typedef struct regex_t
 {
 	int			re_magic;		/* magic number */
 	size_t		re_nsub;		/* number of subexpressions */
@@ -76,7 +83,9 @@ typedef struct
 	Oid			re_collation;	/* Collation that defines LC_CTYPE behavior */
 	/* the rest is opaque pointers to hidden innards */
 	char	   *re_guts;		/* `char *' is more portable than `void *' */
-	char	   *re_fns;
+	void		(*free)(struct regex_t *);
+								/* function to free this struct */
+	reg_callbacks const *re_fns;/* callback functions */
 } regex_t;
 
 /* result reporting (may acquire more fields later) */
@@ -167,7 +176,7 @@ typedef struct
 /*
  * the prototypes for exported functions
  */
-extern int	pg_regcomp(regex_t *, const pg_wchar *, size_t, int, Oid);
+extern int	pg_regcomp(regex_t *, const pg_wchar *, size_t, int, Oid, reg_callbacks const *);
 extern int	pg_regexec(regex_t *, const pg_wchar *, size_t, size_t, rm_detail_t *, size_t, regmatch_t[], int);
 extern int	pg_regprefix(regex_t *, pg_wchar **, size_t *);
 extern void pg_regfree(regex_t *);
diff --git a/src/include/regex/regguts.h b/src/include/regex/regguts.h
index 2ceffa6..e8700e7 100644
--- a/src/include/regex/regguts.h
+++ b/src/include/regex/regguts.h
@@ -453,22 +453,11 @@ struct subre
 
 
 
-/*
- * table of function pointers for generic manipulation functions
- * A regex_t's re_fns points to one of these.
- */
-struct fns
-{
-	void		FUNCPTR(free, (regex_t *));
-	int			FUNCPTR(cancel_requested, (void));
-	int			FUNCPTR(stack_too_deep, (void));
-};
-
 #define CANCEL_REQUESTED(re)  \
-	((*((struct fns *) (re)->re_fns)->cancel_requested) ())
+	(re->re_fns->cancel_requested())
 
 #define STACK_TOO_DEEP(re)	\
-	((*((struct fns *) (re)->re_fns)->stack_too_deep) ())
+	(re->re_fns->stack_too_deep())
 
 
 /*
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 8e91033..69caf1d 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,6 +17,8 @@
 #include <xlocale.h>
 #endif
 
+/* Don't look GUCs elsewhere of server */
+#ifndef FRONTEND
 #include "utils/guc.h"
 
 
@@ -25,6 +27,7 @@ extern char *locale_messages;
 extern char *locale_monetary;
 extern char *locale_numeric;
 extern char *locale_time;
+#endif
 
 /* lc_time localization cache */
 extern char *localized_abbrev_days[];
@@ -32,7 +35,7 @@ extern char *localized_full_days[];
 extern char *localized_abbrev_months[];
 extern char *localized_full_months[];
 
-
+#ifndef FRONTEND
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
@@ -41,6 +44,7 @@ extern bool check_locale_numeric(char **newval, void **extra, GucSource source);
 extern void assign_locale_numeric(const char *newval, void *extra);
 extern bool check_locale_time(char **newval, void **extra, GucSource source);
 extern void assign_locale_time(const char *newval, void *extra);
+#endif
 
 extern bool check_locale(int category, const char *locale, char **canonname);
 extern char *pg_perm_setlocale(int category, const char *locale);
-- 
1.8.3.1

0002-Add-a-parameter-to-COMPLETE_WITH_QUERY.patchtext/x-patch; charset=us-asciiDownload
>From 2d8d23f8588ba354291654ddb1121e906779c5e1 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Wed, 18 Nov 2015 10:15:54 +0900
Subject: [PATCH 2/4] Add a parameter to COMPLETE_WITH_QUERY

COMPLETE_WITH_QUERY is frequently accompanied by assignment to
completion_info_charp. This patch adds new second parameter to
COMPLETE_WITH_QUERY similarly to COMPLETE_WITH_SCHEMA_QUERY so that
its usage looks simpler.
---
 src/bin/psql/tab-complete.c | 184 ++++++++++++++++++++------------------------
 1 file changed, 85 insertions(+), 99 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..55ace8a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -144,9 +144,10 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
-#define COMPLETE_WITH_QUERY(query) \
+#define COMPLETE_WITH_QUERY(query, info) \
 do { \
 	completion_charp = query; \
+	completion_info_charp = info; \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
@@ -1042,7 +1043,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev4_wd, "BY") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -1111,7 +1112,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
@@ -1366,8 +1367,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, prev3_wd);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
@@ -1438,7 +1438,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
 			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
 			  pg_strcasecmp(prev_wd, "RESET") == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
 	/* ALTER VIEW <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
@@ -1483,7 +1483,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
@@ -1507,8 +1507,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -1524,8 +1523,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
 	}
 
 	/*
@@ -1580,32 +1578,28 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev4_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev4_wd);
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
@@ -1637,16 +1631,14 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "RULE") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
 	}
 	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
@@ -1724,8 +1716,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, prev3_wd);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -1793,8 +1784,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev3_wd);
 	}
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
 	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
@@ -1809,7 +1799,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
@@ -1870,8 +1860,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
 			 pg_strcasecmp(prev_wd, "INDEX") == 0)
 	{
-		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev5_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
@@ -2039,7 +2028,7 @@ psql_completion(const char *text, int start, int end)
 			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
 			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
 			 pg_strcasecmp(prev_wd, "USER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* BEGIN, END, ABORT */
 	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
@@ -2106,8 +2095,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
 	}
 
 	/*
@@ -2117,8 +2105,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
 	}
 
 /* COMMENT */
@@ -2160,7 +2147,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, NULL);
 	}
 	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
@@ -2173,8 +2160,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
@@ -2188,7 +2174,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
 			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
@@ -2270,13 +2256,13 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
 			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
 	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
 	/* CREATE EXTENSION <name> */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
@@ -2291,8 +2277,8 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "EXTENSION") == 0 &&
 			 pg_strcasecmp(prev_wd, "VERSION") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
+							prev2_wd);
 	}
 
 	/* CREATE FOREIGN */
@@ -2382,7 +2368,7 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "USING") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
 	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
 			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
 			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
@@ -2473,7 +2459,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
 			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
@@ -2934,7 +2920,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
 			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
@@ -2956,8 +2942,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
@@ -2979,14 +2964,14 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* DROP POLICY <name>  */
 	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev_wd, "POLICY") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
 	}
 	/* DROP POLICY <name> ON */
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
@@ -2999,8 +2984,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, prev2_wd);
 	}
 
 	/* DROP RULE */
@@ -3013,8 +2997,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
 	}
 	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
@@ -3029,7 +3012,7 @@ psql_completion(const char *text, int start, int end)
 /* EXECUTE, but not EXECUTE embedded in other commands */
 	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
 			 prev2_wd[0] == '\0')
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, NULL);
 
 /* EXPLAIN */
 
@@ -3103,7 +3086,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
 			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
 			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 /* FOREIGN TABLE */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
@@ -3114,7 +3097,7 @@ psql_completion(const char *text, int start, int end)
 /* FOREIGN SERVER */
 	else if (pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
 			 pg_strcasecmp(prev_wd, "SERVER") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
@@ -3134,7 +3117,8 @@ psql_completion(const char *text, int start, int end)
 							" UNION SELECT 'TEMPORARY'"
 							" UNION SELECT 'EXECUTE'"
 							" UNION SELECT 'USAGE'"
-							" UNION SELECT 'ALL'");
+							" UNION SELECT 'ALL'",
+							NULL);
 	}
 
 	/*
@@ -3233,21 +3217,21 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev2_wd, "ON") == 0)
 	{
 		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
 		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+			COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
 		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 		else if (pg_strcasecmp(prev_wd, "SEQUENCE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
 		else if (pg_strcasecmp(prev_wd, "TABLE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
@@ -3313,7 +3297,7 @@ psql_completion(const char *text, int start, int end)
 			   pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
 			   pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
 			  pg_strcasecmp(prev_wd, "FROM") == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
 	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
@@ -3331,12 +3315,12 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	}
 	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
 			 pg_strcasecmp(prev_wd, "FROM") == 0)
 	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 	}
 
 /* GROUP BY */
@@ -3433,7 +3417,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY */
 	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
 /* OPTIONS */
 	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
@@ -3442,7 +3426,7 @@ psql_completion(const char *text, int start, int end)
 /* OWNER TO  - complete with available roles */
 	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* ORDER BY */
 	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
@@ -3477,7 +3461,7 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
@@ -3486,7 +3470,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
 			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
 			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
@@ -3551,10 +3535,10 @@ psql_completion(const char *text, int start, int end)
 		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
 				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
 
 /* SECURITY LABEL */
@@ -3600,9 +3584,9 @@ psql_completion(const char *text, int start, int end)
 	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
 			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
 			 pg_strcasecmp(prev_wd, "RESET") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
 	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, NULL);
 	/* Complete "SET TRANSACTION" */
 	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
@@ -3693,7 +3677,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete SET ROLE */
 	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "ROLE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
 			 pg_strcasecmp(prev_wd, "SESSION") == 0)
@@ -3707,7 +3691,8 @@ psql_completion(const char *text, int start, int end)
 	else if (pg_strcasecmp(prev3_wd, "SET") == 0
 			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
 			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
+							NULL);
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
 			 pg_strcasecmp(prev_wd, "SESSION") == 0)
@@ -3741,7 +3726,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+								" UNION SELECT 'DEFAULT' ",
+								NULL);
 		}
 		else
 		{
@@ -3754,7 +3740,7 @@ psql_completion(const char *text, int start, int end)
 				char		querybuf[1024];
 
 				snprintf(querybuf, 1024, Query_for_enum, prev2_wd);
-				COMPLETE_WITH_QUERY(querybuf);
+				COMPLETE_WITH_QUERY(querybuf, NULL);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
 			{
@@ -3787,7 +3773,7 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLESAMPLE */
 	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, NULL);
 
 	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
 		COMPLETE_WITH_CONST("(");
@@ -3798,7 +3784,7 @@ psql_completion(const char *text, int start, int end)
 
 /* UNLISTEN */
 	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
@@ -3835,13 +3821,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+							" UNION SELECT 'USER'", NULL);
 	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
 			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
 			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
 			 pg_strcasecmp(prev_wd, "FOR") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
 	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
 			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
 			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
@@ -3935,44 +3921,44 @@ psql_completion(const char *text, int start, int end)
 	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
 	{
 		if (!recognized_connection_string(text))
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
 	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
 	{
 		if (!recognized_connection_string(prev_wd))
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+			COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
 	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
 	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
 	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
 	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, NULL);
 	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, NULL);
 	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, NULL);
 	/* must be at end of \dF */
 	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, NULL);
 
 	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+		COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
 	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
+		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
 	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
 			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
@@ -3984,17 +3970,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
 			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, NULL);
 	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
+		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
 	/* must be at end of \d list */
 	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
@@ -4006,11 +3992,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, NULL);
 	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (strcmp(prev_wd, "\\password") == 0)
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	else if (strcmp(prev_wd, "\\pset") == 0)
 	{
 		static const char *const my_list[] =
@@ -4156,7 +4142,7 @@ psql_completion(const char *text, int start, int end)
 			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
+					COMPLETE_WITH_QUERY(words_after_create[i].query, NULL);
 				else if (words_after_create[i].squery)
 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
 											   NULL);
-- 
1.8.3.1

0003-Replace-existing-completions-with-regular-expression.patchtext/x-patch; charset=us-asciiDownload
>From 03fcfd46ae1350191d7aad64115933b7fd2c6431 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:18:18 +0900
Subject: [PATCH 3/4] Replace existing completions with regular expressions as
 is.

This patch simply replaces each completions with regular
expressoins. No optimization or enhancement is done.
---
 src/bin/psql/Makefile       |    6 +-
 src/bin/psql/tab-complete.c | 2551 +++++++++++++++++++------------------------
 2 files changed, 1109 insertions(+), 1448 deletions(-)

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..e4a3a2f 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -18,14 +18,12 @@ include $(top_builddir)/src/Makefile.global
 
 REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
 
-override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/pg_dump $(CPPFLAGS)
+override CPPFLAGS := -I. -I$(regex_srcdir) -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/pg_dump $(CPPFLAGS)
 
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
-	$(WIN32RES)
-
+	sql_help.o $(top_srcdir)/src/backend/utils/mb/wstrncmp.o $(WIN32RES)
 
 all: psql
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 55ace8a..89aeddb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -54,6 +54,8 @@
 #include "common.h"
 #include "settings.h"
 #include "stringutils.h"
+#include "regex/regex.h"
+#include "catalog/pg_collation.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -134,6 +136,9 @@ static const char *completion_info_charp2;		/* to pass a third string */
 static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 
+/* set true to stop regexp matching */
+static bool regmatch_stopped = false;;
+
 /*
  * A few macros to ease typing. You can use these to complete the given
  * string with
@@ -789,10 +794,53 @@ typedef struct
 	const bits32 flags;			/* visibility flags, see below */
 } pgsql_thing_t;
 
+/* This is a list of preprocessed (compiled) regular expressions */
+typedef struct re_hashent_t
+{
+	char *repat;				/* pointer to match pattern */
+	int	  cflags;				/* flags for pg_regcomp */
+	regex_t re;					/* compiled regular expression */
+	struct re_hashent_t *next;	/* next entry sharing the same hash */
+#ifdef DEBUG_COMPRE
+	char *restr;				/* intermediate regular expession */
+#endif
+} re_hashent;
+
+#define RE_HASH_SIZE 256		/* size of regexp hash table */
+static re_hashent re_hash[RE_HASH_SIZE];	/* regexp hash table */
+
 #define THING_NO_CREATE		(1 << 0)	/* should not show up after CREATE */
 #define THING_NO_DROP		(1 << 1)	/* should not show up after DROP */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP)
 
+#define CAPBUFLEN 128			/* storage length for each regexp capture
+								 * substr */
+#define MATCHNUM 8				/* maximum number of regexp capture  */
+
+/*
+ * Macros for patter matching.
+ * 
+ * MATCH matches the pattern case-insensitively.
+ * CMATCH matches the pattern case-sensitively.
+ * MATCHBEG returns start index of inebufw for designated capture string
+ * MATCHEND returns index of the next character in inebufw for designated
+ * 			capture string
+ * CAPTURE0 just copies match substr into capbuf.
+ * CAPTURE  captures match substr then returns it. CAPTURE0 then repeated
+ *          CAPBUF would be effective when captured substring is repeatedly
+ *          read.
+ * CAPBUF	returns capture buffer. 
+ */
+#define MATCH(pat) (rematch(linebufw, wstrlen, pat, rmatches, REG_ADVANCED|REG_ICASE))
+#define CMATCH(pat) (rematch(linebufw, wstrlen, pat, rmatches, REG_ADVANCED))
+#define MATCHBEG(n) (rmatches[n].rm_so)
+#define MATCHEND(n) (rmatches[n].rm_eo)
+#define MATCHLEN(n) (MATCHEND(n) - MATCHBEG(n))
+#define CAPCPYLEN(n)(MATCHLEN(n)<CAPBUFLEN ? MATCHLEN(n):CAPBUFLEN - 1)
+#define CAPTURE0(n) (strncpy(capbuf[n], linebuf + MATCHBEG(n), CAPCPYLEN(n)),capbuf[n][CAPCPYLEN(n)] = 0)
+#define CAPTURE(n)  (CAPTURE0(n), capbuf[n])
+#define CAPBUF(n)   (capbuf[n])
+
 static const pgsql_thing_t words_after_create[] = {
 	{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL},		/* Casts have complex structures for names, so
@@ -843,7 +891,6 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL}						/* end of list */
 };
 
-
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
 static char *create_command_generator(const char *text, int state);
@@ -865,15 +912,33 @@ static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
-static void get_previous_words(int point, char **previous_words, int nwords);
-
 static char *get_guctype(const char *varname);
 
+static uint32 hash_uint32(uint32 k);
+static int pathash(char *pat);
+static regex_t *patcomp(char *pat, int cflags);
+static bool rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches, int cflags);
+
+
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
+static int
+pg_ascii2wchar_with_len(const char *from, pg_wchar *to, int len)
+{
+	int			cnt = 0;
+
+	while (len > 0 && *from)
+	{
+		*to++ = *from++;
+		len--;
+		cnt++;
+	}
+	*to = 0;
+	return cnt;
+}
 
 /*
  * Initialize the readline library for our purposes.
@@ -894,6 +959,31 @@ initialize_readline(void)
 	 */
 }
 
+static pg_wchar *
+expand_wchar_buffer(pg_wchar *p, int *buflen, int newlen, int limit)
+{
+	pg_wchar *ret = p;
+	int       len1 = newlen - 1;
+
+	Assert(newlen > 0);
+	if (newlen >= *buflen)
+	{
+		int mask;
+
+		/* Allocate in size of 2^n. Minimum 256 wchars */
+		for (mask = 255 ; mask < limit && (len1 & mask) != len1 ;
+			 mask = (mask << 1) | 1);
+		
+		/* mask is (<new buffer length> - 1) here */
+		if (mask >= limit) return NULL; /* Exceeds limit */
+		
+		*buflen = mask + 1;
+		
+		ret = pg_realloc(p, (*buflen) * sizeof(pg_wchar));
+	}		
+
+	return ret;
+}
 
 /*
  * The completion function.
@@ -906,23 +996,16 @@ initialize_readline(void)
 static char **
 psql_completion(const char *text, int start, int end)
 {
+	const char *linebuf = rl_line_buffer;
+	static pg_wchar	 *linebufw = NULL;
+	static int	     linebufwlen = 0;
+	int len, wstrlen;
+	regmatch_t rmatches[MATCHNUM];
+	char capbuf[MATCHNUM][CAPBUFLEN];
+
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
-	/* This array will contain some scannage of the input line. */
-	char	   *previous_words[9];
-
-	/* For compactness, we use these macros to reference previous_words[]. */
-#define prev_wd   (previous_words[0])
-#define prev2_wd  (previous_words[1])
-#define prev3_wd  (previous_words[2])
-#define prev4_wd  (previous_words[3])
-#define prev5_wd  (previous_words[4])
-#define prev6_wd  (previous_words[5])
-#define prev7_wd  (previous_words[6])
-#define prev8_wd  (previous_words[7])
-#define prev9_wd  (previous_words[8])
-
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
 		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
@@ -957,17 +1040,16 @@ psql_completion(const char *text, int start, int end)
 #endif
 
 	/* Clear a few things. */
+	regmatch_stopped = false;
 	completion_charp = NULL;
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
 	completion_info_charp2 = NULL;
 
-	/*
-	 * Scan the input line before our current position for the last few words.
-	 * According to those we'll make some smart decisions on what the user is
-	 * probably intending to type.
-	 */
-	get_previous_words(start, previous_words, lengthof(previous_words));
+	len = strlen(linebuf);
+	/* Expand string buffer if needed */
+	linebufw = expand_wchar_buffer(linebufw, &linebufwlen, len + 1, 2048); 
+	wstrlen = pg_ascii2wchar_with_len(linebuf, linebufw, strlen(linebuf));
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -985,36 +1067,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (MATCH("^"))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (MATCH("^CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (MATCH("^ALTER TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (MATCH("^ALTER"))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1027,9 +1104,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALL IN TABLESPACE #id"))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1037,38 +1112,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (MATCH("ALL IN TABLESPACE #id OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (MATCH("^ALTER {AGGREGATE|FUNCTION} #id"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](.."))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
+		static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
-		else
-			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+	else if (MATCH("ALTER SCHEMA #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1077,8 +1138,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+	else if (MATCH("ALTER COLLATION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1087,8 +1147,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
+	else if (MATCH("ALTER CONVERSION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1097,8 +1156,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCH("ALTER DATABASE #id"))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1108,17 +1166,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER #id"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
 		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
@@ -1127,10 +1181,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (MATCH("ALTER EVENT TRIGGER #id ENABLE"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
 		{"REPLICA", "ALWAYS", NULL};
@@ -1139,8 +1190,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (MATCH("ALTER EXTENSION #id"))
 	{
 		static const char *const list_ALTEREXTENSION[] =
 		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
@@ -1149,8 +1199,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("ALTER FOREIGN"))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -1159,10 +1208,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (MATCH("ALTER FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_ALTER_FDW[] =
 		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
@@ -1171,9 +1217,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCH("ALTER FOREIGN TABLE #id"))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1184,15 +1228,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
+	else if (MATCH("ALTER INDEX"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 	/* ALTER INDEX <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (MATCH("ALTER INDEX #id"))
 	{
 		static const char *const list_ALTERINDEX[] =
 		{"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
@@ -1200,9 +1242,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEX);
 	}
 	/* ALTER INDEX <name> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER INDEX #id SET"))
 	{
 		static const char *const list_ALTERINDEXSET[] =
 		{"(", "TABLESPACE", NULL};
@@ -1210,25 +1250,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
 	}
 	/* ALTER INDEX <name> RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("ALTER INDEX #id RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER INDEX #id RESET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor", "fastupdate", "gin_pending_list_limit", NULL};
 
 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER INDEX #id SET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
 		{"fillfactor =", "fastupdate =", "gin_pending_list_limit =", NULL};
@@ -1237,8 +1269,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LANGUAGE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
+	else if (MATCH("ALTER LANGUAGE #id"))
 	{
 		static const char *const list_ALTERLANGUAGE[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1247,9 +1278,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OBJECT") == 0)
+	else if (MATCH("ALTER LARGE OBJECT #id"))
 	{
 		static const char *const list_ALTERLARGEOBJECT[] =
 		{"OWNER TO", NULL};
@@ -1258,19 +1287,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (MATCH("ALTER MATERIALIZED VIEW"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 
 	/* ALTER USER,ROLE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
+	else if (MATCH("ALTER {{USER !MAPPING}|{ROLE #id}}"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1284,10 +1308,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "USER") == 0 ||
-			   pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCH("ALTER {USER|ROLE} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1302,16 +1323,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCH("ALTER {ROLE|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
+
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
+	else if (MATCH("ALTER DEFAULT PRIVILEGES"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
 		{"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
@@ -1319,10 +1335,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
 	}
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("ALTER DEFAULT PRIVILEGES FOR"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
 		{"ROLE", "USER", NULL};
@@ -1330,10 +1343,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
 	}
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-			  pg_strcasecmp(prev3_wd, "IN") == 0))
+	else if (MATCH("DEFAULT PRIVILEGES {FOR|IN} #kwd #id"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
 		{"GRANT", "REVOKE", NULL};
@@ -1341,8 +1351,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
 	}
 	/* ALTER DOMAIN <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
+	else if (MATCH("ALTER DOMAIN #id"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
@@ -1350,9 +1359,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> DROP */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("ALTER DOMAIN #id DROP"))
 	{
 		static const char *const list_ALTERDOMAIN2[] =
 		{"CONSTRAINT", "DEFAULT", "NOT NULL", NULL};
@@ -1360,19 +1367,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
 	}
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, prev3_wd);
-	}
+	else if (MATCH("ALTER DOMAIN [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, CAPTURE(1));
+
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCH("ALTER DOMAIN #id RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
 		{"CONSTRAINT", "TO", NULL};
@@ -1380,15 +1379,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+	else if (MATCH("ALTER DOMAIN #id RENAME CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER DOMAIN #id SET"))
 	{
 		static const char *const list_ALTERDOMAIN3[] =
 		{"DEFAULT", "NOT NULL", "SCHEMA", NULL};
@@ -1396,8 +1391,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
 	}
 	/* ALTER SEQUENCE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
+	else if (MATCH("ALTER SEQUENCE #id"))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1406,9 +1400,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("ALTER SEQUENCE #id NO"))
 	{
 		static const char *const list_ALTERSEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -1416,8 +1408,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
 	/* ALTER SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("ALTER SERVER #id"))
 	{
 		static const char *const list_ALTER_SERVER[] =
 		{"VERSION", "OPTIONS", "OWNER TO", NULL};
@@ -1425,8 +1416,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_SERVER);
 	}
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "SYSTEM") == 0)
+	else if (MATCH("ALTER SYSTEM"))
 	{
 		static const char *const list_ALTERSYSTEM[] =
 		{"SET", "RESET", NULL};
@@ -1434,14 +1424,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
 	}
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SYSTEM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCH("ALTER SYSTEM {SET|RESET}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
+
 	/* ALTER VIEW <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("ALTER VIEW #id"))
 	{
 		static const char *const list_ALTERVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1449,9 +1436,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("ALTER MATERIALIZED VIEW #id"))
 	{
 		static const char *const list_ALTERMATVIEW[] =
 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1460,18 +1445,15 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCH("ALTER POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* ALTER POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("ALTER POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id"))
 	{
 		static const char *const list_ALTERPOLICY[] =
 		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
@@ -1479,72 +1461,51 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERPOLICY);
 	}
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
+
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+	else if (MATCH("ALTER POLICY #id ON #id WITH CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCH("ALTER RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
-	}
+	else if (MATCH("ALTER RULE [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
 
 	/* ALTER RULE <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0)
+	else if (MATCH("ALTER RULE #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("ALTER TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
-	}
+	else if (MATCH("ALTER TRIGGER [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
 
-	/*
-	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
-	 */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+//  !!! This duplicates with the entry just above
+//	/*
+//	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
+//	 */
+//	else if (MATCH("ALTER TRIGGER #id ON"))
+//		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("ALTER TRIGGER #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLE") == 0)
+	else if (MATCH("ALTER TABLE #id"))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1554,96 +1515,55 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (MATCH("ALTER TABLE #id ENABLE"))
 	{
 		static const char *const list_ALTERENABLE[] =
 		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
-			  pg_strcasecmp(prev_wd, "ALWAYS") == 0))
+	else if (MATCH("ALTER TABLE #id ENABLE {REPLICA|ALWAYS}"))
 	{
 		static const char *const list_ALTERENABLE2[] =
 		{"RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE2);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev4_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ENABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev4_wd);
-	}
+	else if (MATCH("ALTER TABLE [#id] ENABLE RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
 	/* ALTER TABLE xxx INHERIT */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCH("ALTER TABLE #id INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
+
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "NO") == 0 &&
-			 pg_strcasecmp(prev_wd, "INHERIT") == 0)
-	{
+	else if (MATCH("ALTER TABLE #id NO INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
-	}
+
 	/* ALTER TABLE xxx DISABLE */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
+	else if (MATCH("ALTER TABLE #id DISABLE"))
 	{
 		static const char *const list_ALTERDISABLE[] =
 		{"ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERDISABLE);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RULE") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, prev3_wd);
-	}
-	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (MATCH("ALTER TABLE [#id] DISABLE RULE"))
+		COMPLETE_WITH_QUERY(Query_for_rule_of_table, CAPTURE(1));
+
+	else if (MATCH("ALTER TABLE [#id] DISABLE TRIGGER"))
+		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
+
+	else if (MATCH("DISABLE ROW LEVEL SECURITY"))
 	{
 		static const char *const list_DISABLERLS[] =
 		{"CASCADE", NULL};
@@ -1652,45 +1572,33 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALTER") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+	else if (MATCH("ALTER TABLE [#id] ALTER"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+	else if (MATCH("ALTER TABLE [#id] RENAME"))
+		COMPLETE_WITH_ATTR(CAPTURE(1),
+						   " UNION SELECT 'COLUMN'"
+						   " UNION SELECT 'CONSTRAINT'"
+						   " UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("TABLE [#id] {ALTER|RENAME} COLUMN"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-			 pg_strcasecmp(prev_wd, "TO") != 0)
+	else if (MATCH("TABLE #id RENAME {COLUMN|CONSTRAINT} !TO"))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("TABLE #id DROP"))
 	{
 		static const char *const list_TABLEDROP[] =
 		{"COLUMN", "CONSTRAINT", NULL};
@@ -1698,31 +1606,18 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLEDROP);
 	}
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "COLUMN") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("ALTER TABLE [#id] DROP COLUMN"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
-			  pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, prev3_wd);
-	}
+	else if (MATCH("ALTER TABLE [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
+		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, CAPTURE(1));
+
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id"))
 	{
 		static const char *const list_COLUMNALTER[] =
 		{"TYPE", "SET", "RESET", "DROP", NULL};
@@ -1730,11 +1625,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNALTER);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET"))
 	{
 		static const char *const list_COLUMNSET[] =
 		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
@@ -1742,11 +1633,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSET);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET ("))
 	{
 		static const char *const list_COLUMNOPTIONS[] =
 		{"n_distinct", "n_distinct_inherited", NULL};
@@ -1754,11 +1641,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
-			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET STORAGE"))
 	{
 		static const char *const list_COLUMNSTORAGE[] =
 		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
@@ -1766,29 +1649,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
-			 pg_strcasecmp(prev_wd, "DROP") == 0)
+	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id DROP"))
 	{
 		static const char *const list_COLUMNDROP[] =
 		{"DEFAULT", "NOT NULL", NULL};
 
 		COMPLETE_WITH_LIST(list_COLUMNDROP);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
+	else if (MATCH("ALTER TABLE #id CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev3_wd);
-	}
+
+	else if (MATCH("ALTER TABLE [#id] CLUSTER ON"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
+
 	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "SET") == 0)
+	else if (MATCH("ALTER TABLE #id SET"))
 	{
 		static const char *const list_TABLESET[] =
 		{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
@@ -1796,14 +1671,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET);
 	}
 	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALTER TABLE #id SET TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
+
 	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITHOUT") == 0)
+	else if (MATCH("ALTER TABLE #id SET WITHOUT"))
 	{
 		static const char *const list_TABLESET2[] =
 		{"CLUSTER", "OIDS", NULL};
@@ -1811,14 +1683,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESET2);
 	}
 	/* ALTER TABLE <foo> RESET */
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("ALTER TABLE #id RESET"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLE #id {RE}?SET("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1855,38 +1724,24 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (pg_strcasecmp(prev4_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "INDEX") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev5_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "IDENTITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
+	else if (MATCH("[#id] REPLICA IDENTITY USING INDEX"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
+
+	else if (MATCH("TABLE #id REPLICA IDENTITY USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	}
-	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REPLICA") == 0 &&
-			 pg_strcasecmp(prev_wd, "IDENTITY") == 0)
+
+	else if (MATCH("TABLE #id REPLICA IDENTITY"))
 	{
 		static const char *const list_REPLICAID[] =
 		{"FULL", "NOTHING", "DEFAULT", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_REPLICAID);
 	}
-	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPLICA") == 0)
-	{
+	else if (MATCH("TABLE #id REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
-	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("ALTER TABLESPACE #id"))
 	{
 		static const char *const list_ALTERTSPC[] =
 		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
@@ -1894,17 +1749,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTSPC);
 	}
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev_wd, "RESET") == 0))
+	else if (MATCH("ALTER TABLESPACE #id {RE}?SET"))
 		COMPLETE_WITH_CONST("(");
+
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
+	else if (MATCH("ALTER TABLESPACE #id {RE}?SET("))
 	{
 		static const char *const list_TABLESPACEOPTIONS[] =
 		{"seq_page_cost", "random_page_cost", "effective_io_concurrency", NULL};
@@ -1913,20 +1762,14 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("ALTER TEXT SEARCH"))
 	{
 		static const char *const list_ALTERTEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "PARSER") == 0))
+	else if (MATCH("ALTER TEXT SEARCH {TEMPLATE|PARSER} #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH2[] =
 		{"RENAME TO", "SET SCHEMA", NULL};
@@ -1934,10 +1777,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
+	else if (MATCH("ALTER TEXT SEARCH DICTIONARY #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH3[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1945,10 +1785,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
 	}
 
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (MATCH("ALTER TEXT SEARCH CONFIGURATION #id"))
 	{
 		static const char *const list_ALTERTEXTSEARCH4[] =
 		{"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1957,8 +1794,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TYPE") == 0)
+	else if (MATCH("ALTER TYPE #id"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
@@ -1967,9 +1803,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ADD") == 0)
+	else if (MATCH("ALTER TYPE #id ADD"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "VALUE", NULL};
@@ -1977,9 +1811,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev_wd, "RENAME") == 0)
+	else if (MATCH("ALTER TYPE #id RENAME"))
 	{
 		static const char *const list_ALTERTYPE[] =
 		{"ATTRIBUTE", "TO", NULL};
@@ -1987,30 +1819,22 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+	else if (MATCH("TYPE #id RENAME ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
 	 * attributes
 	 */
-	else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0 ||
-			  pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
-			 pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+	else if (MATCH("TYPE [#id] {ALTER|DROP|RENAME} ATTRIBUTE"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
-	{
+	else if (MATCH("ALTER ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TYPE");
-	}
+
 	/* complete ALTER GROUP <foo> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "GROUP") == 0)
+	else if (MATCH("ALTER GROUP #id"))
 	{
 		static const char *const list_ALTERGROUP[] =
 		{"ADD USER", "DROP USER", "RENAME TO", NULL};
@@ -2018,22 +1842,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGROUP);
 	}
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev_wd, "DROP") == 0))
+	else if (MATCH("ALTER GROUP #id {ADD|DROP}"))
 		COMPLETE_WITH_CONST("USER");
+
 	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
-	else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
-			  pg_strcasecmp(prev2_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev_wd, "USER") == 0)
+	else if (MATCH("ALTER GROUP #id {ADD|DROP} USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* BEGIN, END, ABORT */
-	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
-			 pg_strcasecmp(prev_wd, "END") == 0 ||
-			 pg_strcasecmp(prev_wd, "ABORT") == 0)
+	else if (MATCH("{BEGIN|END|ABORT}"))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", NULL};
@@ -2041,7 +1858,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* COMMIT */
-	else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
+	else if (MATCH("COMMIT"))
 	{
 		static const char *const list_COMMIT[] =
 		{"WORK", "TRANSACTION", "PREPARED", NULL};
@@ -2049,10 +1866,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COMMIT);
 	}
 /* RELEASE SAVEPOINT */
-	else if (pg_strcasecmp(prev_wd, "RELEASE") == 0)
+	else if (MATCH("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
+
 /* ROLLBACK*/
-	else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0)
+	else if (MATCH("ROLLBACK"))
 	{
 		static const char *const list_TRANS[] =
 		{"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL};
@@ -2064,125 +1882,83 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+	else if (MATCH(" !WITHOUT CLUSTER"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
+								   "UNION SELECT 'VERBOSE'");
 
 	/*
 	 * If the previous words are CLUSTER VERBOSE produce list of tables
 	 */
-	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+	else if (MATCH("CLUSTER VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") != 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-	{
+	else if (MATCH("CLUSTER !ON|VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
-	}
+
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
-	{
+	else if (MATCH("CLUSTER VERBOSE #id"))
 		COMPLETE_WITH_CONST("USING");
-	}
 
 	/*
 	 * If we have CLUSTER <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
-	}
+	else if (MATCH("CLUSTER [#id] USING"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
 	/*
 	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_index_of_table, prev2_wd);
-	}
+	else if (MATCH("CLUSTER VERBOSE [#id] USING"))
+		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
 /* COMMENT */
-	else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
+	else if (MATCH("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+
+	else if (MATCH("COMMENT ON"))
 	{
 		static const char *const list_COMMENT[] =
-		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
-			"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
-			"SERVER", "INDEX", "LANGUAGE", "POLICY", "RULE", "SCHEMA", "SEQUENCE",
-			"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
-			"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
-		"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
+		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER",
+		 "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "SERVER",
+		 "INDEX", "LANGUAGE", "POLICY", "RULE", "SCHEMA", "SEQUENCE",
+		 "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE",
+		 "FUNCTION", "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN",
+		 "LARGE OBJECT", "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("COMMENT ON FOREIGN"))
 	{
 		static const char *const list_TRANS2[] =
 		{"DATA WRAPPER", "TABLE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("COMMENT ON TEXT SEARCH"))
 	{
 		static const char *const list_TRANS2[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_TRANS2);
 	}
-	else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
-	{
+	else if (MATCH("COMMENT ON CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
-	{
+
+	else if (MATCH("COMMENT ON CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+
+	else if (MATCH("COMMENT ON CONSTRAINT [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint,
+							CAPTURE(1));
+
+	else if (MATCH("COMMENT ON MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
-	else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+
+	else if (MATCH("COMMENT ON EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
-	}
-	else if (((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev3_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev4_wd, "ON") == 0) ||
-			  (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
-			   pg_strcasecmp(prev5_wd, "ON") == 0)) &&
-			 pg_strcasecmp(prev_wd, "IS") != 0)
+
+	else if (MATCH("COMMENT ON {#kwd }+ !IS"))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2191,15 +1967,11 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (pg_strcasecmp(prev_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev_wd, "\\copy") == 0 ||
-			 (pg_strcasecmp(prev2_wd, "COPY") == 0 &&
-			  pg_strcasecmp(prev_wd, "BINARY") == 0))
+	else if (MATCH("\\?COPY{ BINARY}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
-	else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
-			 pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
-			 pg_strcasecmp(prev2_wd, "BINARY") == 0)
+	else if (MATCH("\\?COPY{ BINARY}? #id"))
 	{
 		static const char *const list_FROMTO[] =
 		{"FROM", "TO", NULL};
@@ -2207,22 +1979,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FROMTO);
 	}
 	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
-	else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev_wd, "TO") == 0))
+	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO}"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename */
-	else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
-			  pg_strcasecmp(prev4_wd, "\\copy") == 0 ||
-			  pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev2_wd, "TO") == 0))
+	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO} #nwb"))
 	{
 		static const char *const list_COPY[] =
 		{"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
@@ -2231,9 +1995,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-	else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
-			  pg_strcasecmp(prev3_wd, "TO") == 0))
+	else if (MATCH("{FROM|TO} #nwb CSV"))
 	{
 		static const char *const list_CSV[] =
 		{"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
@@ -2242,8 +2004,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* CREATE DATABASE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (MATCH("CREATE DATABASE #id"))
 	{
 		static const char *const list_DATABASE[] =
 		{"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS_TEMPLATE",
@@ -2253,19 +2014,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DATABASE);
 	}
 
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
-			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+	else if (MATCH("CREATE DATABASE #id TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+	else if (MATCH("CREATE EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
+
 	/* CREATE EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (MATCH("CREATE EXTENSION #id"))
 	{
 		static const char *const list_CREATE_EXTENSION[] =
 		{"WITH SCHEMA", "CASCADE", "VERSION", NULL};
@@ -2273,17 +2031,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
 	}
 	/* CREATE EXTENSION <name> VERSION */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EXTENSION") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERSION") == 0)
-	{
+	else if (MATCH("CREATE EXTENSION [#id] VERSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
-							prev2_wd);
-	}
+							CAPTURE(1));
 
 	/* CREATE FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("CREATE FOREIGN"))
 	{
 		static const char *const list_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2292,10 +2045,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (MATCH("CREATE FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
 		{"HANDLER", "VALIDATOR", NULL};
@@ -2305,31 +2055,24 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
+	else if (MATCH("CREATE UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
+
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
-	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
 	/* Complete ... INDEX [<name>] ON with a list of tables  */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("INDEX {CONCURRENTLY }? #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
-	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("INDEX #id CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 	{
 		static const char *const list_CREATE_INDEX[] =
 		{"CONCURRENTLY", "ON", NULL};
@@ -2341,54 +2084,38 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("INDEX #id {CONCURRENTLY }? ON #id"))
 	{
 		static const char *const list_CREATE_INDEX2[] =
 		{"(", "USING", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
 	}
-	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("INDEX #id {CONCURRENTLY }? ON [#id]("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* same if you put in USING */
-	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+	else if (MATCH("ON [#id] USING #id("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
+
 	/* Complete USING with an index method */
-	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
-			  pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("INDEX #id {#nwb }* ON #id USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
-	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
-			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
-			 pg_strcasecmp(prev2_wd, "USING") == 0)
+
+	else if (MATCH("^{!POLICY|FOR }+ON #id USING #id"))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+	else if (MATCH("CREATE POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("CREATE POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"FOR", "TO", "USING", "WITH CHECK", NULL};
@@ -2400,10 +2127,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id FOR"))
 	{
 		static const char *const list_POLICYCMDS[] =
 		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
@@ -2411,10 +2135,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYCMDS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id FOR INSERT"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "WITH CHECK", NULL};
@@ -2426,11 +2147,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
 	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
-			  pg_strcasecmp(prev_wd, "DELETE") == 0))
+	else if (MATCH("CREATE POLICY #id ON #id FOR {SELECT|DELETE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", NULL};
@@ -2443,11 +2160,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
 	 * CHECK"
 	 */
-	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
-			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
-			  pg_strcasecmp(prev_wd, "UPDATE") == 0))
+	else if (MATCH("CREATE POLICY #id ON #id FOR {ALL|UPDATE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
 		{"TO", "USING", "WITH CHECK", NULL};
@@ -2455,32 +2168,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
+
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "USING") == 0)
+	else if (MATCH("CREATE POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
+	else if (MATCH("CREATE RULE #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE RULE <sth> AS with "ON" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE RULE #id AS"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
-	else if (pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("RULE #id AS ON"))
 	{
 		static const char *const rule_events[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -2488,24 +2193,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(rule_events);
 	}
 	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
-	else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
-			  pg_toupper((unsigned char) prev_wd[5]) == 'T'))
+	else if (MATCH("AS ON {SELECT|UPDATE|INSERT|DELETE}"))
 		COMPLETE_WITH_CONST("TO");
+
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("AS ON #kwd TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev2_wd, "SEQUENCE") == 0))
+	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id"))
 	{
 		static const char *const list_CREATESEQUENCE[] =
 		{"INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
@@ -2514,13 +2210,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev4_wd, "TEMP") == 0 ||
-			   pg_strcasecmp(prev4_wd, "TEMPORARY") == 0) &&
-			  pg_strcasecmp(prev3_wd, "SEQUENCE") == 0)) &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id NO"))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
 		{"MINVALUE", "MAXVALUE", "CYCLE", NULL};
@@ -2529,8 +2219,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE SERVER <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("CREATE SERVER #id"))
 	{
 		static const char *const list_CREATE_SERVER[] =
 		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
@@ -2540,9 +2229,7 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
-			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
+	else if (MATCH("CREATE {TEMP{ORARY}?}"))
 	{
 		static const char *const list_TEMP[] =
 		{"SEQUENCE", "TABLE", "VIEW", NULL};
@@ -2550,8 +2237,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TEMP);
 	}
 	/* Complete "CREATE UNLOGGED" with TABLE */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
+	else if (MATCH("CREATE UNLOGGED"))
 	{
 		static const char *const list_UNLOGGED[] =
 		{"TABLE", "MATERIALIZED VIEW", NULL};
@@ -2560,8 +2246,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE TABLESPACE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (MATCH("CREATE TABLESPACE #id"))
 	{
 		static const char *const list_CREATETABLESPACE[] =
 		{"OWNER", "LOCATION", NULL};
@@ -2569,32 +2254,23 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
 	}
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNER") == 0)
-	{
+	else if (MATCH("CREATE TABLESPACE #id OWNER"))
 		COMPLETE_WITH_CONST("LOCATION");
-	}
 
 /* CREATE TEXT SEARCH */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+	else if (MATCH("CREATE TEXT SEARCH"))
 	{
 		static const char *const list_CREATETEXTSEARCH[] =
 		{"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
 	}
-	else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+	else if (MATCH("TEXT SEARCH CONFIGURATION #id"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("CREATE TRIGGER #id"))
 	{
 		static const char *const list_CREATETRIGGER[] =
 		{"BEFORE", "AFTER", "INSTEAD OF", NULL};
@@ -2602,10 +2278,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev_wd, "AFTER") == 0))
+	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER}"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
@@ -2613,10 +2286,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev_wd, "OF") == 0)
+	else if (MATCH("CREATE TRIGGER #id INSTEAD OF"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
 		{"INSERT", "DELETE", "UPDATE", NULL};
@@ -2624,13 +2294,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0)) ||
-			 (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			  pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
-			  pg_strcasecmp(prev2_wd, "OF") == 0))
+	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER|INSTEAD_OF} #kwd"))
 	{
 		static const char *const list_CREATETRIGGER2[] =
 		{"ON", "OR", NULL};
@@ -2642,27 +2306,19 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("TRIGGER #id {BEFORE|AFTER} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
-	else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OF") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("INSTEAD OF #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "GRANT") == 0 && prev3_wd[0] == '\0') &&
-			 prev2_wd[0] != '\0')
+	else if (MATCH("CREATE TRIGGER {#nwb }+ EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+	else if (MATCH("CREATE {{USER !MAPPING_}|ROLE |GROUP } #id"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2676,11 +2332,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			  (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			   pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
-			   pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			  pg_strcasecmp(prev_wd, "WITH") == 0))
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2698,18 +2350,11 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
-	{
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
-	}
+
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
-			 pg_strcasecmp(prev_wd, "IN") == 0)
+	else if (MATCH("CREATE {ROLE|GROUP|USER} #id IN"))
 	{
 		static const char *const list_CREATEROLE3[] =
 		{"GROUP", "ROLE", NULL};
@@ -2719,45 +2364,35 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("CREATE VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCH("CREATE MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
+
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("CREATE MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
+
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "AS") == 0)
+	else if (MATCH("CREATE MATERIALIZED VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
+	else if (MATCH("CREATE EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
+
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (MATCH("CREATE EVENT TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
+
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("CREATE EVENT TRIGGER #id ON"))
 	{
 		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
 		{"ddl_command_start", "ddl_command_end", "sql_drop", NULL};
@@ -2766,7 +2401,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DECLARE */
-	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
+	else if (MATCH("DECLARE #id"))
 	{
 		static const char *const list_DECLARE[] =
 		{"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
@@ -2775,7 +2410,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CURSOR */
-	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
+	else if (MATCH("CURSOR"))
 	{
 		static const char *const list_DECLARECURSOR[] =
 		{"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
@@ -2790,19 +2425,15 @@ psql_completion(const char *text, int start, int end)
 	 * Complete DELETE with FROM (only if the word before that is not "ON"
 	 * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
 	 */
-	else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-			 !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-			   pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "AFTER") == 0))
+	else if (MATCH("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
+
 	/* Complete DELETE FROM with a list of tables */
-	else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
+	else if (MATCH("DELETE FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+
 	/* Complete DELETE FROM <table> */
-	else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FROM") == 0)
+	else if (MATCH("DELETE FROM #id"))
 	{
 		static const char *const list_DELETE[] =
 		{"USING", "WHERE", "SET", NULL};
@@ -2812,7 +2443,7 @@ psql_completion(const char *text, int start, int end)
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
+	else if (MATCH("DISCARD"))
 	{
 		static const char *const list_DISCARD[] =
 		{"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
@@ -2825,7 +2456,7 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (pg_strcasecmp(prev_wd, "DO") == 0)
+	else if (MATCH("DO"))
 	{
 		static const char *const list_DO[] =
 		{"LANGUAGE", NULL};
@@ -2835,46 +2466,13 @@ psql_completion(const char *text, int start, int end)
 
 /* DROP (when not the previous word) */
 	/* DROP AGGREGATE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+	else if (MATCH("DROP AGGREGATE #id"))
 		COMPLETE_WITH_CONST("(");
 
 	/* DROP object with CASCADE / RESTRICT */
-	else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
-			   pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
-			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
-			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-			  prev_wd[strlen(prev_wd) - 1] == ')') ||
-			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			  pg_strcasecmp(prev2_wd, "TRIGGER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
-			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
-			  (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
-			   pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
-			   pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
-			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
-		)
-	{
-		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
+	else if (MATCH("DROP {{{COLLATION|CONVERSION|DOMAIN|EXTENSION|[FUNCTION]|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW|{EVENT TRIGGER}|{FOREGN DATA WRAPPER}|{TEXT SEARCH {CONFIGURATION|DICTIONARY|PARSER|TEMPLATE}}} #id }|{AGGREGATE #id(..)}}"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "FUNCTION") == 0)
 		{
 			COMPLETE_WITH_CONST("(");
 		}
@@ -2886,8 +2484,7 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST(list_DROPCR);
 		}
 	}
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("DROP FOREIGN"))
 	{
 		static const char *const drop_CREATE_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -2896,34 +2493,22 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
-	{
+	else if (MATCH("DROP MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
-	{
+
+	else if (MATCH("DROP MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	}
 
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+	else if (MATCH("DROP {AGGREGATE|FUNCTION} [#id]("))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+
 	/* DROP OWNED BY */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "OWNED") == 0)
+	else if (MATCH("DROP OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
+	else if (MATCH("DROP OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
-			 pg_strcasecmp(prev_wd, "SEARCH") == 0)
+
+	else if (MATCH("DROP TEXT SEARCH"))
 	{
 
 		static const char *const list_ALTERTEXTSEARCH[] =
@@ -2933,20 +2518,11 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
-	{
+	else if (MATCH("DROP TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("DROP TRIGGER [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
+	else if (MATCH("DROP TRIGGER #id ON #id"))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -2955,53 +2531,32 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* DROP EVENT TRIGGER */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "EVENT") == 0)
-	{
+	else if (MATCH("DROP EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	}
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
-	{
+
+	else if (MATCH("DROP_EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
-	}
 
 	/* DROP POLICY <name>  */
-	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev_wd, "POLICY") == 0)
-	{
+	else if (MATCH("DROP POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
-	}
+
 	/* DROP POLICY <name> ON */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
-	{
+	else if (MATCH("DROP POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
+
 	/* DROP POLICY <name> ON <table> */
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, prev2_wd);
-	}
+	else if (MATCH("DROP POLICY [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, CAPTURE(1));
 
 	/* DROP RULE */
-	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev2_wd, "RULE") == 0)
-	{
+	else if (MATCH("DROP RULE #id"))
 		COMPLETE_WITH_CONST("ON");
-	}
-	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev3_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
-	{
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, prev2_wd);
-	}
-	else if (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
-			 pg_strcasecmp(prev4_wd, "RULE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+
+	else if (MATCH("DROP RULE [#id] ON"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
+
+	else if (MATCH("DROP RULE #id ON #id"))
 	{
 		static const char *const list_DROPCR[] =
 		{"CASCADE", "RESTRICT", NULL};
@@ -3010,8 +2565,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* EXECUTE, but not EXECUTE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements, NULL);
 
 /* EXPLAIN */
@@ -3019,26 +2573,21 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
+	else if (MATCH("EXPLAIN"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("EXPLAIN ANALYZE"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL};
 
 		COMPLETE_WITH_LIST(list_EXPLAIN);
 	}
-	else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev_wd, "VERBOSE") == 0))
+	else if (MATCH("EXPLAIN{ ANALYZE}? VERBOSE"))
 	{
 		static const char *const list_EXPLAIN[] =
 		{"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
@@ -3048,8 +2597,7 @@ psql_completion(const char *text, int start, int end)
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (pg_strcasecmp(prev_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE}"))
 	{
 		static const char *const list_FETCH1[] =
 		{"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL};
@@ -3057,8 +2605,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FETCH1);
 	}
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev2_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE} #id"))
 	{
 		static const char *const list_FETCH2[] =
 		{"ALL", "NEXT", "PRIOR", NULL};
@@ -3071,8 +2618,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
-			 pg_strcasecmp(prev3_wd, "MOVE") == 0)
+	else if (MATCH("{FETCH|MOVE} #id #id"))
 	{
 		static const char *const list_FROMIN[] =
 		{"FROM", "IN", NULL};
@@ -3082,27 +2628,20 @@ psql_completion(const char *text, int start, int end)
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+	else if (MATCH("{ALTER|DROP} FOREIGN DATA WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
 /* FOREIGN TABLE */
-	else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
-			 pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
+	else if (MATCH("CREATE FOREIGN TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev_wd, "SERVER") == 0)
+	else if (MATCH("FOREIGN SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
+	else if (MATCH("{GRANT|REVOKE}"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
@@ -3125,32 +2664,12 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-			 pg_strcasecmp(prev2_wd, "REVOKE") == 0)
-	{
-		if (pg_strcasecmp(prev_wd, "SELECT") == 0
-			|| pg_strcasecmp(prev_wd, "INSERT") == 0
-			|| pg_strcasecmp(prev_wd, "UPDATE") == 0
-			|| pg_strcasecmp(prev_wd, "DELETE") == 0
-			|| pg_strcasecmp(prev_wd, "TRUNCATE") == 0
-			|| pg_strcasecmp(prev_wd, "REFERENCES") == 0
-			|| pg_strcasecmp(prev_wd, "TRIGGER") == 0
-			|| pg_strcasecmp(prev_wd, "CREATE") == 0
-			|| pg_strcasecmp(prev_wd, "CONNECT") == 0
-			|| pg_strcasecmp(prev_wd, "TEMPORARY") == 0
-			|| pg_strcasecmp(prev_wd, "TEMP") == 0
-			|| pg_strcasecmp(prev_wd, "EXECUTE") == 0
-			|| pg_strcasecmp(prev_wd, "USAGE") == 0
-			|| pg_strcasecmp(prev_wd, "ALL") == 0)
+	else if (MATCH("{GRANT|REVOKE} {SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMP{ORARY}?|EXECUTE|USAGE|ALL}"))
 			COMPLETE_WITH_CONST("ON");
-		else
-		{
-			if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
-				COMPLETE_WITH_CONST("TO");
-			else
-				COMPLETE_WITH_CONST("FROM");
-		}
-	}
+	else if (MATCH("GRANT #id"))
+		COMPLETE_WITH_CONST("TO");
+	else if (MATCH("REVOKE #id"))
+		COMPLETE_WITH_CONST("FROM");
 
 	/*
 	 * Complete GRANT/REVOKE <sth> ON with a list of tables, views, and
@@ -3163,9 +2682,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev_wd, "ON") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -3183,10 +2700,7 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "ALL") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON ALL"))
 	{
 		static const char *const list_privilege_all[] =
 		{"FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA", "TABLES IN SCHEMA",
@@ -3195,10 +2709,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_privilege_all);
 	}
 
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (MATCH("{GRANT|REVOKE} #kwd ON FOREIGN"))
 	{
 		static const char *const list_privilege_foreign[] =
 		{"DATA WRAPPER", "SERVER", NULL};
@@ -3208,74 +2719,40 @@ psql_completion(const char *text, int start, int end)
 
 	/*
 	 * Complete "GRANT/REMOVE * ON DATABASE/DOMAIN/..." with a list of
-	 * appropriate objects.
+	 * appropriate objects later.
 	 *
-	 * Complete "GRANT/REVOKE * ON * " with "TO/FROM".
+	 * Complete only "GRANT/REVOKE * ON * " with "TO/FROM" here.
 	 */
-	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON !DATABASE|DOMAIN|FUNCTION|LANGUAGE|SCHEMA|SEQUENCE|TABLE|TABLESPACE|TYPE"))
 	{
-		if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
-		else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-		else if (pg_strcasecmp(prev_wd, "SEQUENCE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
-		else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if ((pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev8_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev6_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev5_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
-	{
-		if (pg_strcasecmp(prev8_wd, "GRANT") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev7_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev5_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
-	{
-		if (pg_strcasecmp(prev7_wd, "GRANT") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN DATA WRAPPER #id"))
+	{
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if ((pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			  pg_strcasecmp(prev6_wd, "REVOKE") == 0) &&
-			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
-			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN SERVER #id"))
 	{
-		if (pg_strcasecmp(prev6_wd, "GRANT") == 0)
+		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -3285,76 +2762,55 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (((pg_strcasecmp(prev9_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev8_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev7_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev6_wd, "GRANT") == 0 ||
-			   pg_strcasecmp(prev5_wd, "GRANT") == 0) &&
-			  pg_strcasecmp(prev_wd, "TO") == 0) ||
-			 ((pg_strcasecmp(prev9_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev8_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev7_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev6_wd, "REVOKE") == 0 ||
-			   pg_strcasecmp(prev5_wd, "REVOKE") == 0) &&
-			  pg_strcasecmp(prev_wd, "FROM") == 0))
+	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
-	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("GRANT #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("REVOKE #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("FROM");
 
 	/*
 	 * Complete "GRANT/REVOKE * TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
-	{
+	else if (MATCH("GRANT #kwd TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-	}
-	else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
-			 pg_strcasecmp(prev_wd, "FROM") == 0)
-	{
+
+	else if (MATCH("REVOKE #kwd FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
-	}
 
 /* GROUP BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "GROUP") == 0)
+	else if (MATCH("FROM #id GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (pg_strcasecmp(prev_wd, "IMPORT") == 0)
+	else if (MATCH("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (pg_strcasecmp(prev2_wd, "IMPORT") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+
+	else if (MATCH("IMPORT FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
+	else if (MATCH("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
+
 	/* Complete INSERT INTO with table names */
-	else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev_wd, "INTO") == 0)
+	else if (MATCH("INSERT INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 pg_strcasecmp(prev_wd, "(") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("INSERT INTO [#id]("))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "INTO") == 0)
+	else if (MATCH("INSERT INTO #id"))
 	{
 		static const char *const list_INSERT[] =
 		{"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
@@ -3366,9 +2822,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "INTO") == 0 &&
-			 prev_wd[strlen(prev_wd) - 1] == ')')
+	else if (MATCH("INSERT INTO #id(..)"))
 	{
 		static const char *const list_INSERT[] =
 		{"SELECT", "TABLE", "VALUES", NULL};
@@ -3377,33 +2831,26 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (pg_strcasecmp(prev_wd, "VALUES") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
+	else if (MATCH("!DEFAULT VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+	else if (MATCH("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LOCK") == 0)
+
+	else if (MATCH("LOCK TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-			  pg_strcasecmp(prev_wd, "TABLE") != 0) ||
-			 (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
-			  pg_strcasecmp(prev3_wd, "LOCK") == 0))
+	else if (MATCH("LOCK{ TABLE}? !TABLE"))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (pg_strcasecmp(prev_wd, "IN") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "LOCK") == 0 ||
-			  (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
-			   pg_strcasecmp(prev4_wd, "LOCK") == 0)))
+	else if (MATCH("LOCK{ TABLE}? #id IN"))
 	{
 		static const char *const lock_modes[] =
 		{"ACCESS SHARE MODE",
@@ -3416,30 +2863,26 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* NOTIFY */
-	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
+	else if (MATCH("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
 /* OPTIONS */
-	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+	else if (MATCH("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TO") == 0)
+	else if (MATCH("OWNER TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* ORDER BY */
-	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ORDER") == 0)
+	else if (MATCH("FROM #id ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
-			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+
+	else if (MATCH("FROM [#id] ORDER BY"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* PREPARE xx AS */
-	else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
-			 pg_strcasecmp(prev3_wd, "PREPARE") == 0)
+	else if (MATCH("PREPARE #id AS"))
 	{
 		static const char *const list_PREPARE[] =
 		{"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
@@ -3453,116 +2896,68 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED");
-	else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY #id"))
 		COMPLETE_WITH_CONST("TO");
-	else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
-			 pg_strcasecmp(prev3_wd, "BY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+	else if (MATCH("REASSIGN OWNED BY #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+	else if (MATCH("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+	else if (MATCH("REFRESH MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("WITH");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY #id"))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id WITH"))
 	{
 		static const char *const list_WITH_DATA[] =
 		{"NO DATA", "DATA", NULL};
 
 		COMPLETE_WITH_LIST(list_WITH_DATA);
 	}
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
-			 pg_strcasecmp(prev_wd, "WITH") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW CONCURRENTLY #id WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
-			 pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
-			 pg_strcasecmp(prev_wd, "NO") == 0)
+	else if (MATCH("REFRESH MATERIALIZED VIEW #id WITH NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
+	else if (MATCH("REINDEX"))
 	{
 		static const char *const list_REINDEX[] =
 		{"TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE", NULL};
 
 		COMPLETE_WITH_LIST(list_REINDEX);
 	}
-	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
-	{
-		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-		else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
-				 pg_strcasecmp(prev_wd, "DATABASE") == 0)
-			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
-	}
+	/* REINDEX {TABLE|INDEX|SCHEMA|DATABASE} are completed later */
+	else if (MATCH("REINDEX SYSTEM"))
+		COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 
 /* SECURITY LABEL */
-	else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	else if (MATCH("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev_wd, "LABEL") == 0)
+	else if (MATCH("SECURITY LABEL"))
 	{
 		static const char *const list_SECURITY_LABEL_preposition[] =
 		{"ON", "FOR"};
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
 	}
-	else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+	else if (MATCH("SECURITY LABEL FOR #id"))
 		COMPLETE_WITH_CONST("ON");
-	else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0) ||
-			 (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			  pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			  pg_strcasecmp(prev3_wd, "FOR") == 0 &&
-			  pg_strcasecmp(prev_wd, "ON") == 0))
+	else if (MATCH("SECURITY LABEL{ FOR #id}? ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
@@ -3571,88 +2966,54 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
-			 pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "ON") == 0)
+	else if (MATCH("SECURITY LABEL ON #nwb #nwb"))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
-	/* naah . . . */
+	/* naah . . . */ 
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-			 pg_strcasecmp(prev_wd, "RESET") == 0)
+	else if (MATCH("^{RE}?SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
-	else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+	else if (MATCH("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars, NULL);
+
 	/* Complete "SET TRANSACTION" */
-	else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			  pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "START") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "WORK") == 0)
-			 || (pg_strcasecmp(prev2_wd, "BEGIN") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
-			 || (pg_strcasecmp(prev4_wd, "SESSION") == 0
-				 && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0
-				 && pg_strcasecmp(prev2_wd, "AS") == 0
-				 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"))
 	{
 		static const char *const my_list[] =
 		{"ISOLATION LEVEL", "READ", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0
-			  || pg_strcasecmp(prev3_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev3_wd, "START") == 0
-			  || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0
-				  && pg_strcasecmp(prev3_wd, "AS") == 0))
-			 && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev2_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev_wd, "ISOLATION") == 0)
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"
+				" ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if ((pg_strcasecmp(prev4_wd, "SET") == 0
-			  || pg_strcasecmp(prev4_wd, "BEGIN") == 0
-			  || pg_strcasecmp(prev4_wd, "START") == 0
-			  || pg_strcasecmp(prev4_wd, "AS") == 0)
-			 && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0
-				 || pg_strcasecmp(prev3_wd, "WORK") == 0)
-			 && pg_strcasecmp(prev2_wd, "ISOLATION") == 0
-			 && pg_strcasecmp(prev_wd, "LEVEL") == 0)
+	else if (MATCH("{{{SET|START} TRANSACTION}|"
+				 "{BEGIN {TRANSACTION|WORK}}|"
+				 "{SESSION CHARACTERISTICS AS TRANSACTION}}"
+				" ISOLATION LEVEL"))
 	{
 		static const char *const my_list[] =
 		{"READ", "REPEATABLE", "SERIALIZABLE", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (MATCH("{TRANSACTION|WORK} ISOLATION LEVEL READ"))
 	{
 		static const char *const my_list[] =
 		{"UNCOMMITTED", "COMMITTED", NULL};
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev4_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev3_wd, "ISOLATION") == 0 &&
-			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
-			 pg_strcasecmp(prev_wd, "REPEATABLE") == 0)
+	else if (MATCH("{TRANSACTION|WORK} ISOLATION LEVEL REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if ((pg_strcasecmp(prev3_wd, "SET") == 0 ||
-			  pg_strcasecmp(prev3_wd, "BEGIN") == 0 ||
-			  pg_strcasecmp(prev3_wd, "START") == 0 ||
-			  pg_strcasecmp(prev3_wd, "AS") == 0) &&
-			 (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 ||
-			  pg_strcasecmp(prev2_wd, "WORK") == 0) &&
-			 pg_strcasecmp(prev_wd, "READ") == 0)
+	else if (MATCH("{SET|BEGIN|START|AS} {TRANSACTION|WORK} READ"))
 	{
 		static const char *const my_list[] =
 		{"ONLY", "WRITE", NULL};
@@ -3660,14 +3021,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* SET CONSTRAINTS */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
+	else if (MATCH("SET CONSTRAINTS"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	}
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
+	else if (MATCH("SET CONSTRAINTS #id"))
 	{
 		static const char *const constraint_list[] =
 		{"DEFERRED", "IMMEDIATE", NULL};
@@ -3675,12 +3034,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(constraint_list);
 	}
 	/* Complete SET ROLE */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "ROLE") == 0)
+	else if (MATCH("SET ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (MATCH("SET SESSION"))
 	{
 		static const char *const my_list[] =
 		{"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL};
@@ -3688,30 +3045,23 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(my_list);
 	}
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0
-			 && pg_strcasecmp(prev2_wd, "SESSION") == 0
-			 && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
+	else if (MATCH("SET SESSION AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
 							NULL);
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
-			 pg_strcasecmp(prev_wd, "SESSION") == 0)
+	else if (MATCH("RESET SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
+
 	/* Complete SET <var> with "TO" */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
-			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
-			 pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
-			 prev_wd[strlen(prev_wd) - 1] != ')' &&
-			 prev_wd[strlen(prev_wd) - 1] != '=' &&
-			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
+	else if (MATCH("^{!UPDATE }*SET !TABLESPACE|SCHEMA|="))
 		COMPLETE_WITH_CONST("TO");
+
 	/* Suggest possible variable values */
-	else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-			 (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+	else if (MATCH("SET [#id] {TO|=}"))
 	{
+		CAPTURE0(1);
 		/* special cased code for individual GUCs */
-		if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
+		if (pg_strcasecmp(CAPBUF(1), "DateStyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3721,7 +3071,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (pg_strcasecmp(prev2_wd, "search_path") == 0)
+		else if (pg_strcasecmp(CAPBUF(1), "search_path") == 0)
 		{
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
@@ -3733,13 +3083,13 @@ psql_completion(const char *text, int start, int end)
 		{
 			/* generic, type based, GUC support */
 
-			char	   *guctype = get_guctype(prev2_wd);
+			char	   *guctype = get_guctype(CAPBUF(1));
 
 			if (guctype && strcmp(guctype, "enum") == 0)
 			{
 				char		querybuf[1024];
 
-				snprintf(querybuf, 1024, Query_for_enum, prev2_wd);
+				snprintf(querybuf, 1024, Query_for_enum, CAPBUF(1));
 				COMPLETE_WITH_QUERY(querybuf, NULL);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
@@ -3763,35 +3113,34 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* START TRANSACTION */
-	else if (pg_strcasecmp(prev_wd, "START") == 0)
+	else if (MATCH("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
 /* TABLESAMPLE */
-	else if (pg_strcasecmp(prev_wd, "TABLESAMPLE") == 0)
+	else if (MATCH("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods, NULL);
 
-	else if (pg_strcasecmp(prev2_wd, "TABLESAMPLE") == 0)
+	else if (MATCH("TABLESAMPLE #id"))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
+	else if (MATCH("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
+	else if (MATCH("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE #id"))
 		COMPLETE_WITH_CONST("SET");
 
 	/*
@@ -3799,83 +3148,55 @@ psql_completion(const char *text, int start, int end)
 	 * word) the word before it was (hopefully) a table name and we'll now
 	 * make a list of attributes.
 	 */
-	else if (pg_strcasecmp(prev_wd, "SET") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("[#id] SET"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* UPDATE xx SET yy = */
-	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+	else if (MATCH("UPDATE #id SET #id"))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+	else if (MATCH("{ALTER|DROP|CREATE} USER MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+
+	else if (MATCH("CREATE USER MAPPING FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'", NULL);
-	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	else if (MATCH("{ALTER|DROP} USER MAPPING FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
-	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
-			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
-			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
-			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
-			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+
+	else if (MATCH("{CREATE|ALTER|DROP} USER MAPPING FOR #id"))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
+	else if (MATCH("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE}"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE} ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+	else if (MATCH("VACUUM {FULL|FREEZE} VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+	else if (MATCH("VACUUM VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
-			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("VACUUM ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
-			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+	else if (MATCH("{{VERBOSE ANALYZE}|{ANALYZE VERBOSE}}"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3884,120 +3205,115 @@ psql_completion(const char *text, int start, int end)
 	 * Only match when WITH is the first word, as WITH may appear in many
 	 * other contexts.
 	 */
-	else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (MATCH("^WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
-	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+	else if (MATCH("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
-	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+	else if (MATCH("[#id] WHERE"))
+		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
-			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
-			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+	else if (MATCH("^{!\\?COPY }+FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+	else if (MATCH("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (MATCH("\\#?_")) /* _ is necessary due to patcomp()'s design */
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (CMATCH("\\c{onnect}?"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases, NULL);
 	}
-	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
+	else if (CMATCH("\\c{onnect}? [#id]"))
 	{
-		if (!recognized_connection_string(prev_wd))
+		if (!recognized_connection_string(CAPTURE(1)))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (CMATCH("\\da#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (CMATCH("\\db#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (CMATCH("\\dD#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (CMATCH("\\des#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (CMATCH("\\deu#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings, NULL);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (CMATCH("\\dew#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (CMATCH("\\df#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (CMATCH("\\dFd#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries, NULL);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (CMATCH("\\dFp#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers, NULL);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (CMATCH("\\dFt#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates, NULL);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (CMATCH("\\dF#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations, NULL);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (CMATCH("\\di#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (CMATCH("\\dL#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages, NULL);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (CMATCH("\\dn#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas, NULL);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (CMATCH("\\{dp|z}#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (CMATCH("\\ds#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (CMATCH("\\dt#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (CMATCH("\\dT#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (CMATCH("\\d{u|g}#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (CMATCH("\\dv#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (CMATCH("\\dx#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions, NULL);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (CMATCH("\\dm#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (CMATCH("\\dE#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (CMATCH("\\dy#nsp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (CMATCH("\\d#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (CMATCH("\\ef#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (CMATCH("\\ev#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (CMATCH("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings, NULL);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (CMATCH("\\h{elp}?"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (CMATCH("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (CMATCH("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -4008,9 +3324,10 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev2_wd, "\\pset") == 0)
+	else if (CMATCH("\\pset [#kwd]"))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		CAPTURE0(1);
+		if (strcmp(CAPBUF(1), "format") == 0)
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -4018,16 +3335,16 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (strcmp(CAPBUF(1), "linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (strcmp(CAPBUF(1), "unicode_border_linestyle") == 0 ||
+				 strcmp(CAPBUF(1), "unicode_column_linestyle") == 0 ||
+				 strcmp(CAPBUF(1), "unicode_header_linestyle") == 0)
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -4036,72 +3353,74 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (CMATCH("\\unset"))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (CMATCH("\\set"))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (strcmp(prev2_wd, "\\set") == 0)
+	else if (CMATCH("\\set [#kwd]"))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		CAPTURE0(1);
+
+		if (strcmp(CAPBUF(1), "AUTOCOMMIT") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (strcmp(CAPBUF(1), "COMP_KEYWORD_CASE") == 0)
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (strcmp(CAPBUF(1), "ECHO") == 0)
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (strcmp(CAPBUF(1), "ECHO_HIDDEN") == 0)
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (strcmp(CAPBUF(1), "HISTCONTROL") == 0)
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (strcmp(CAPBUF(1), "ON_ERROR_ROLLBACK") == 0)
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (strcmp(CAPBUF(1), "ON_ERROR_STOP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (strcmp(CAPBUF(1), "QUIET") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (strcmp(CAPBUF(1), "SHOW_CONTEXT") == 0)
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (strcmp(CAPBUF(1), "SINGLELINE") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (strcmp(CAPBUF(1), "SINGLESTEP") == 0)
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (strcmp(CAPBUF(1), "VERBOSITY") == 0)
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -4109,20 +3428,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (CMATCH("\\sf#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (CMATCH("\\sv#nsp"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (CMATCH("\\{cd|e|edit|g|i|include|ir|include_relative|o|out|s|w|write|lo_import}"))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
@@ -4133,13 +3443,14 @@ psql_completion(const char *text, int start, int end)
 	 * check if that was the previous word. If so, execute the query to get a
 	 * list of them.
 	 */
-	else
+	else if(MATCH("[#kwd]"))
 	{
 		int			i;
 
+		CAPTURE0(1);
 		for (i = 0; words_after_create[i].name; i++)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
+			if (pg_strcasecmp(CAPBUF(1), words_after_create[i].name) == 0)
 			{
 				if (words_after_create[i].query)
 					COMPLETE_WITH_QUERY(words_after_create[i].query, NULL);
@@ -4164,19 +3475,10 @@ psql_completion(const char *text, int start, int end)
 #endif
 	}
 
-	/* free storage */
-	{
-		int			i;
-
-		for (i = 0; i < lengthof(previous_words); i++)
-			free(previous_words[i]);
-	}
-
 	/* Return our Grand List O' Matches */
 	return matches;
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4747,92 +4049,6 @@ exec_query(const char *query)
 	return result;
 }
 
-
-/*
- * Return the nwords word(s) before point.  Words are returned right to left,
- * that is, previous_words[0] gets the last word before point.
- * If we run out of words, remaining array elements are set to empty strings.
- * Each array element is filled with a malloc'd string.
- */
-static void
-get_previous_words(int point, char **previous_words, int nwords)
-{
-	const char *buf = rl_line_buffer;	/* alias */
-	int			i;
-
-	/* first we look for a non-word char before the current point */
-	for (i = point - 1; i >= 0; i--)
-		if (strchr(WORD_BREAKS, buf[i]))
-			break;
-	point = i;
-
-	while (nwords-- > 0)
-	{
-		int			start,
-					end;
-		char	   *s;
-
-		/* now find the first non-space which then constitutes the end */
-		end = -1;
-		for (i = point; i >= 0; i--)
-		{
-			if (!isspace((unsigned char) buf[i]))
-			{
-				end = i;
-				break;
-			}
-		}
-
-		/*
-		 * If no end found we return an empty string, because there is no word
-		 * before the point
-		 */
-		if (end < 0)
-		{
-			point = end;
-			s = pg_strdup("");
-		}
-		else
-		{
-			/*
-			 * Otherwise we now look for the start. The start is either the
-			 * last character before any word-break character going backwards
-			 * from the end, or it's simply character 0. We also handle open
-			 * quotes and parentheses.
-			 */
-			bool		inquotes = false;
-			int			parentheses = 0;
-
-			for (start = end; start > 0; start--)
-			{
-				if (buf[start] == '"')
-					inquotes = !inquotes;
-				if (!inquotes)
-				{
-					if (buf[start] == ')')
-						parentheses++;
-					else if (buf[start] == '(')
-					{
-						if (--parentheses <= 0)
-							break;
-					}
-					else if (parentheses == 0 &&
-							 strchr(WORD_BREAKS, buf[start - 1]))
-						break;
-				}
-			}
-
-			point = start - 1;
-
-			/* make a copy of chars from start to end inclusive */
-			s = pg_malloc(end - start + 2);
-			strlcpy(s, &buf[start], end - start + 2);
-		}
-
-		*previous_words++ = s;
-	}
-}
-
 /*
  * Look up the type for the GUC variable with the passed name.
  *
@@ -4909,4 +4125,451 @@ dequote_file_name(char *text, char quote_char)
 }
 #endif   /* NOT_USED */
 
+/* Element regular expressions */
+#define WB0  "[\\t\\n@$><=;|&{\\(\\) ]"			/* WORD BREAKS */
+#define NWB0 "[^\\t\\n@$><=;|&{\\(\\) ]"		/* ^WORD BREAKS */
+#define WB  "[\\t\\n@$><=;|&{ ]"				/* WORD BREAKS  - "()"*/
+#define WB2  "[\\t\\n@$><;|&{ ]"				/* WORD BREAKS  - "()="*/
+#define NWB "[^\\t\\n@$><=;|&{ ]"				/* ^(WORD BREAKS - "()") */
+#define PB  "^(?:.*;)?\\s*"			/* bind to the beginning of the line */
+#define PM  "^(?:.*"NWB0 WB0")?\\s*"					/* floating head */
+#define ID  NWB0"+"									/* identifier */
+#define KWD "\\w+"									/* any keywords */
+#define PSIZE_IS_64 (sizeof(void*) == 8)
+
+/* See hashfun.c for details */
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+#define final(a,b,c) \
+{ \
+  c ^= b; c -= rot(b,14); \
+  a ^= c; a -= rot(c,11); \
+  b ^= a; b -= rot(a,25); \
+  c ^= b; c -= rot(b,16); \
+  a ^= c; a -= rot(c, 4); \
+  b ^= a; b -= rot(a,14); \
+  c ^= b; c -= rot(b,24); \
+}
+
+/* See hashfunc.c for details */
+static uint32
+hash_uint32(uint32 k)
+{
+	register uint32 a,
+				b,
+				c;
+
+	a = b = c = 0x9e3779b9 + (uint32) sizeof(uint32) + 3923095;
+	a += k;
+
+	final(a, b, c);
+
+	/* report the result */
+	return c;
+}
+
+/*
+ * Calculate hash code for the *pointer* of the pattern string, not the
+ * content of it.
+ */
+static int
+pathash(char *pat)
+{
+	uint32 t = 0;
+#if SIZEOF_VOID_P == 8
+	t = hash_uint32((uint32) ((uint64) pat >> 32));
+	t ^= hash_uint32((uint32) (uint64) pat);
+#else
+	t = hash_uint32((uint32) pat);
+#endif
+	return t % RE_HASH_SIZE;
+}
+
+/* callback functions for pg_regcomp */
+static int
+tc_cancel_requested(void)
+{
+	return cancel_pressed;
+}
+
+static int
+tc_stack_too_deep(void)
+{
+	return 0;
+}
+
+static const reg_callbacks tc_regcb =
+{
+	tc_cancel_requested,
+	tc_stack_too_deep
+};
+
+/*
+ * Returns compiled regular expression for the given pattern and cflags.
+ *
+ * Converts the given pattern into regular expression and store in hash table
+ * for the first time, then returns the stored one for the same pattern.
+ *
+ * NOTE: pattern is not well-defined so it may yeild unexpected regular
+ * expression. Regular expressions is a intermediate data so it won't be
+ * stored usually but defining DEBUG_COMPRE will store them. The stored
+ * regular expressions can be examined using perl.
+ */
+#define INTBUF_LEN 4096
+static regex_t *
+patcomp(char *pat, int cflags)
+{
+	int n = pathash(pat);
+	re_hashent *ent = &re_hash[n];
+
+	/* Search for a stored entry for the pattern and cflags */
+	while (ent && ent->next &&
+		   !(ent->repat == pat && ent->cflags == cflags) )
+		ent = ent->next;
+
+	/* Create new entry if not found */
+	if (ent->repat != pat)
+	{
+		re_hashent *newent = pg_malloc(sizeof(re_hashent));
+		int ret;
+		char restr[INTBUF_LEN];			/* generated regular expression */
+		pg_wchar rewstr[INTBUF_LEN];	/* pg_regcomp requires RE in pg_wchar */
+		char *p, *rp;
+		int relen;
+		int nest_level = 1;				/* grouping nest level  */
+		int neg_nest_level = 0;			/* nest level for negative lookahead */
+		bool prev_is_ws = true;			/* previous character was white space */
+		bool no_ws = false;				/* inhibit prefixing white space */
+		bool prev_is_id = false;		/* previous item was identifier (#id)*/
+		/*
+		 * the following two strings cannot be defined as macos because the
+		 * pointers of them have significant in this function
+		 */
+		char *NORMAL_TAIL = NWB0"*$"; /* terminal string outside parens */
+		char *INPAREN_TAIL = "[^\\)]*$";/* terminal string inside parens */
+		char *tail;						/* terminal string of regexp */
+
+		Assert(ent->next == NULL);
+
+		newent->repat = pat;
+		newent->cflags = cflags;
+		newent->next = NULL;
+
+		restr[0] = 0;
+
+		/*
+		 * If the pattern is not a whole-matching, prefix an appropriate
+		 * regular expression.
+		 */
+		if (*pat != '^')
+			strcpy(restr, PM);
+		rp = restr + strlen(restr);
+		no_ws = false;
+
+		/* Loop through the given pattern */
+		for (p = pat ; *p ; p++)
+		{
+			tail = NORMAL_TAIL;
+			prev_is_id = false;
+
+			/* @ inhibits automatic white-spacing */
+			if (*p ==  '@')
+			{
+				no_ws = true;
+				p++;
+			}
+			switch (*p)
+			{
+			case '.':
+				/* .. means the stuff of a pair of parens */
+				if (strncmp(p+1, ".", 1) == 0)
+				{
+					p++;
+					strcpy(rp, "[^\\)]*");
+				}
+				else
+				{
+					/* unknown pattern. restore terminal modes then exit */
+					rl_deprep_terminal();
+					exit(1);
+				}
+				break;
+			case '#':
+				/*
+				 * #id yields a regexp matches to any identifier including
+				 * double-quotes.
+				 */
+				if (strncmp(p+1, "id", 2) == 0)
+				{
+					p += 2;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, ID);
+					prev_is_id = true;
+					prev_is_ws = false;
+				}
+				/* #kwd matches keywords, which consists of regular word chars*/
+				else if (strncmp(p+1, "kwd", 3) == 0)
+				{
+					p += 3;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, "\\w+");
+					prev_is_ws = false;
+				}
+				/* #nwb matches a sequence of any non-word-breaking chars */
+				else if (strncmp(p+1, "nwb", 3) == 0)
+				{
+					p += 3;
+					if (!no_ws && !prev_is_ws)
+						strcpy(rp, WB"+");
+					strcpy(rp, NWB"+");
+					prev_is_ws = false;
+				}
+				/* #nsp matches a sequence of any non-whitespaces */
+				else if (strncmp(p+1, "nsp", 3) == 0)
+				{
+					p += 3;
+					strcpy(rp, "[^ ]*");
+					prev_is_ws = false;
+				}
+				/*  #? matches literally a '?' */
+				else if (strncmp(p+1, "?", 1) == 0)
+				{
+					p += 1;
+					strcpy(rp, "\\?");
+					prev_is_ws = true;
+				}
+				else
+				{
+					/* unknown pattern. restore terminal modes then exit */
+					rl_deprep_terminal();
+					exit(1);
+				}
+				break;
+			case '^':
+				/* force prefix matching in a statement */
+				strcpy(rp, PB);
+				prev_is_ws = true;
+				break;
+			case ' ':
+				/* add a white space, if necessary or requested by @ */
+				if (!no_ws && !prev_is_ws)
+					strcpy(rp, WB"+");
+				prev_is_ws = true;
+				break;
+			case '(':
+				/* ( means literally an open paren  */
+				strcpy(rp, WB"*\\(");
+				prev_is_ws = true;
+				tail = INPAREN_TAIL;
+				break;
+			case ')':
+				/* ) means literally a close paren */
+				strcpy(rp, "\\)"WB"*");
+				prev_is_ws = true;
+				break;
+			case '%':
+				/* % inhibits suffix matching. this is used to filter the same
+                   kind of commands. */
+				tail = "";
+				break;
+			case '|':
+			case '?':
+			case '*':
+			case '+':
+				/* these characters have the same as regular expression */
+				rp[0] = *p;
+				rp[1] = 0;
+				break;
+			case '{':
+				/* { opens non-capturing group */
+				strcpy(rp, "(?:");
+				nest_level++;
+				break;
+			case '[':
+				/* [ opens capturing group */
+				strcpy(rp, "(");
+				nest_level++;
+				break;
+			case '}':
+			case ']':
+				/* closing groups. close negative lookahead if needed */
+				if (nest_level-- == neg_nest_level)
+				{
+					strcpy(rp, ")" NWB "+");
+					while(*rp) rp++;
+					neg_nest_level = 0;
+				}
+				strcpy(rp, ")");
+				break;
+			case '!':
+				/*
+				 * ! means negative lookahead. This is a bit a complex than
+				 * other elements. This group will be closed automatically by
+				 * some conditions. One is a space, and the another is closing
+				 * of outer parens.
+				 */
+				strcpy(rp, "(?!");
+				neg_nest_level = nest_level;
+				break;
+			default:
+				if (*p)
+				{
+					if (*p == '=')
+					{
+						/* Treat this as a word, not a character */
+						if (!no_ws && !prev_is_ws && neg_nest_level > 0)
+						{
+							strcpy(rp, WB"*");
+							while(*rp) rp++;
+						}
+						
+						strcpy(rp, "=");
+						while(*rp) rp++;
+					}
+					else
+					{
+						/* loop through a literal word */
+						do {
+							if (*p == '\\')
+							{
+								/* backslashes are treated literally in a
+								 * word */
+								strcpy(rp, "\\\\");
+								rp += 2;
+								p++;
+								prev_is_ws = false;
+							}
+							else if(*p == '_')
+							{
+								/* underscore means white space in word, which
+								 * is usable for multi-word token or so */
+								strcpy(rp, WB"+");
+								while(*rp) rp++;
+								p++;
+								prev_is_ws = true;
+							}
+							else if(isalnum(*p))
+							{
+								*rp++ = *p++;
+								prev_is_ws = false;
+							}
+							else
+								break;	/* end of a word. break this loop */
+						} while (1);
+						*rp = 0;
+						p--;
+					}
+				}
+
+				/* Close negative lookahread if no alternative successives */
+				if (neg_nest_level == nest_level &&
+					(p[1] == ' ' || p[1] == 0))
+				{
+					strcpy(rp, ")" NWB "+");
+					neg_nest_level = 0;
+				}
+			}
+
+			no_ws = false;
+			while (*rp) rp++;
+		}
+
+		/*
+		 * A special treat. If pattern ends with '?', forciblly add white space
+		 */
+		if (!no_ws && (!prev_is_ws || p[-1] == '?') && tail == NORMAL_TAIL)
+		{
+			if (prev_is_id)
+				strcpy(rp, WB2"+");
+			else
+				strcpy(rp, WB2"*");
+			while (*rp) rp++;
+		}
+		strcpy(rp, tail);
+
+		/* now we have a regular expression to use */
+#ifdef DEBUG_COMPRE
+		newent->restr = strdup(restr);
+#endif		
+		relen = pg_ascii2wchar_with_len(restr, rewstr, strlen(restr));
+
+		/* disable longjump while compiling. this enable */
+		sigint_interrupt_enabled = false;
+		ret = pg_regcomp(&newent->re, rewstr, relen, cflags,
+						 C_COLLATION_OID, &tc_regcb);
+		sigint_interrupt_enabled = true;
+		if (ret != 0)
+		{
+			char	errstr[100];
+
+				pg_regerror(ret, &newent->re, errstr, sizeof(errstr));
+				fprintf(stderr, "Failed to compile regular expression(%s): %s : /%s/\n",
+						errstr, pat, restr);
+
+			/* exit immediately for the case other than cancel */
+			if (ret != REG_CANCEL)
+			{
+				rl_deprep_terminal();
+				exit(1);
+			}
+
+			/* compilation has been cancled by interrupt.  */
+			pg_free(newent);
+			newent = NULL;
+
+		}
+		ent->next = newent;
+		ent = newent;
+	}
+
+	return (ent ? &ent->re : NULL);
+}
+
+/* Apply given patterns to line buffer */
+bool
+rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches,
+		int cflags)
+{
+	int ret;
+	regex_t *re;
+
+	/* immediately return failure if already stopped */
+	if (regmatch_stopped)
+		return 1;
+
+	re = patcomp(pat, cflags);
+
+	/* don't regmatch after this if this patcomp has been canceled */
+	if (!re)
+	{
+		regmatch_stopped = true;
+		return 1;
+	}
+
+	ret = pg_regexec(re, line, linelen, 0, NULL, MATCHNUM, rmatches, 0);
+
+	return ret == 0;
+}
+
+#ifdef DEBUG_COMPRE
+void dump_regexp(void);
+void
+dump_regexp(void)
+{
+	int i;
+
+	for (i = 0 ; i < RE_HASH_SIZE ; i++)
+	{
+		re_hashent *p = re_hash + i;
+
+		while (p) {
+			if (p->repat)
+				fprintf(stderr, "%03d: \"%s\" : /%s/\n", i, p->repat, p->restr);
+			p = p->next;
+		}
+	}
+}
+#endif /* DEBUG_COMPRE */
+
 #endif   /* USE_READLINE */
-- 
1.8.3.1

0004-Remove-less-informative-comments.patchtext/x-patch; charset=us-asciiDownload
>From 7bc15025af64dd689795466cf4661ccf5a69f2bd Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 19 Nov 2015 16:03:48 +0900
Subject: [PATCH 4/4] Remove less informative comments.

Regular expressions made each matchings self-descriptive so most of
the comments just repeating the matching description are no longer
needed.
---
 src/bin/psql/tab-complete.c | 307 +++-----------------------------------------
 1 file changed, 19 insertions(+), 288 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 89aeddb..1b3c188 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1071,18 +1071,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
-	/* complete with something you can create */
 	else if (MATCH("^CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
-	/* complete with something you can drop */
 	else if (MATCH("^DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
-
-	/* ALTER TABLE */
 	else if (MATCH("^ALTER TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
@@ -1094,12 +1090,13 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("^ALTER"))
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
-			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
-		"USER", "USER MAPPING FOR", "VIEW", NULL};
+		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE",
+		 "DEFAULT PRIVILEGES", "DOMAIN", "EVENT TRIGGER", "EXTENSION",
+		 "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "GROUP",
+		 "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW",
+		 "OPERATOR", "POLICY", "ROLE", "RULE", "SCHEMA", "SERVER",
+		 "SEQUENCE", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH",
+		 "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
@@ -1114,12 +1111,13 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
 	else if (MATCH("ALL IN TABLESPACE #id OWNED BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	/* ALTER AGGREGATE,FUNCTION <name> */
+
 	else if (MATCH("^ALTER {AGGREGATE|FUNCTION} #id"))
 		COMPLETE_WITH_CONST("(");
-	/* ALTER AGGREGATE,FUNCTION <name> (...) */
+
 	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](.."))
 		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+
 	else if (MATCH("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
 		static const char *const list_ALTERAGG[] =
@@ -1128,7 +1126,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
-	/* ALTER SCHEMA <name> */
 	else if (MATCH("ALTER SCHEMA #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1137,7 +1134,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER COLLATION <name> */
 	else if (MATCH("ALTER COLLATION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1146,7 +1142,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER CONVERSION <name> */
 	else if (MATCH("ALTER CONVERSION #id"))
 	{
 		static const char *const list_ALTERGEN[] =
@@ -1155,7 +1150,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERGEN);
 	}
 
-	/* ALTER DATABASE <name> */
 	else if (MATCH("ALTER DATABASE #id"))
 	{
 		static const char *const list_ALTERDATABASE[] =
@@ -1165,13 +1159,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDATABASE);
 	}
 
-	/* ALTER EVENT TRIGGER */
 	else if (MATCH("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 	}
 
-	/* ALTER EVENT TRIGGER <name> */
 	else if (MATCH("ALTER EVENT TRIGGER #id"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
@@ -1180,7 +1172,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER);
 	}
 
-	/* ALTER EVENT TRIGGER <name> ENABLE */
 	else if (MATCH("ALTER EVENT TRIGGER #id ENABLE"))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
@@ -1189,7 +1180,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_EVENT_TRIGGER_ENABLE);
 	}
 
-	/* ALTER EXTENSION <name> */
 	else if (MATCH("ALTER EXTENSION #id"))
 	{
 		static const char *const list_ALTEREXTENSION[] =
@@ -1198,7 +1188,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTEREXTENSION);
 	}
 
-	/* ALTER FOREIGN */
 	else if (MATCH("ALTER FOREIGN"))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
@@ -1207,7 +1196,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
 	}
 
-	/* ALTER FOREIGN DATA WRAPPER <name> */
 	else if (MATCH("ALTER FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_ALTER_FDW[] =
@@ -1216,7 +1204,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER_FDW);
 	}
 
-	/* ALTER FOREIGN TABLE <name> */
 	else if (MATCH("ALTER FOREIGN TABLE #id"))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
@@ -1226,14 +1213,10 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
 	}
-
-	/* ALTER INDEX */
 	else if (MATCH("ALTER INDEX"))
-	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
-	/* ALTER INDEX <name> */
+
 	else if (MATCH("ALTER INDEX #id"))
 	{
 		static const char *const list_ALTERINDEX[] =
@@ -1241,7 +1224,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERINDEX);
 	}
-	/* ALTER INDEX <name> SET */
 	else if (MATCH("ALTER INDEX #id SET"))
 	{
 		static const char *const list_ALTERINDEXSET[] =
@@ -1249,10 +1231,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
 	}
-	/* ALTER INDEX <name> RESET */
 	else if (MATCH("ALTER INDEX #id RESET"))
 		COMPLETE_WITH_CONST("(");
-	/* ALTER INDEX <foo> SET|RESET ( */
+
 	else if (MATCH("ALTER INDEX #id RESET("))
 	{
 		static const char *const list_INDEXOPTIONS[] =
@@ -1268,7 +1249,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
 	}
 
-	/* ALTER LANGUAGE <name> */
 	else if (MATCH("ALTER LANGUAGE #id"))
 	{
 		static const char *const list_ALTERLANGUAGE[] =
@@ -1277,7 +1257,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
 	}
 
-	/* ALTER LARGE OBJECT <oid> */
 	else if (MATCH("ALTER LARGE OBJECT #id"))
 	{
 		static const char *const list_ALTERLARGEOBJECT[] =
@@ -1286,14 +1265,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
 	}
 
-	/* ALTER MATERIALIZED VIEW */
 	else if (MATCH("ALTER MATERIALIZED VIEW"))
 	{
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	}
 
-	/* ALTER USER,ROLE <name> */
 	else if (MATCH("ALTER {{USER !MAPPING}|{ROLE #id}}"))
 	{
 		static const char *const list_ALTERUSER[] =
@@ -1307,7 +1284,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERUSER);
 	}
 
-	/* ALTER USER,ROLE <name> WITH */
 	else if (MATCH("ALTER {USER|ROLE} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
@@ -1322,11 +1298,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
 	}
 
-	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
 	else if (MATCH("ALTER {ROLE|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 
-	/* ALTER DEFAULT PRIVILEGES */
 	else if (MATCH("ALTER DEFAULT PRIVILEGES"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
@@ -1334,7 +1308,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
 	}
-	/* ALTER DEFAULT PRIVILEGES FOR */
+
 	else if (MATCH("ALTER DEFAULT PRIVILEGES FOR"))
 	{
 		static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
@@ -1350,7 +1324,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
 	}
-	/* ALTER DOMAIN <name> */
 	else if (MATCH("ALTER DOMAIN #id"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
@@ -1358,7 +1331,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
-	/* ALTER DOMAIN <sth> DROP */
 	else if (MATCH("ALTER DOMAIN #id DROP"))
 	{
 		static const char *const list_ALTERDOMAIN2[] =
@@ -1366,11 +1338,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
 	}
-	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
 	else if (MATCH("ALTER DOMAIN [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type, CAPTURE(1));
 
-	/* ALTER DOMAIN <sth> RENAME */
 	else if (MATCH("ALTER DOMAIN #id RENAME"))
 	{
 		static const char *const list_ALTERDOMAIN[] =
@@ -1378,11 +1348,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN);
 	}
-	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
 	else if (MATCH("ALTER DOMAIN #id RENAME CONSTRAINT #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* ALTER DOMAIN <sth> SET */
 	else if (MATCH("ALTER DOMAIN #id SET"))
 	{
 		static const char *const list_ALTERDOMAIN3[] =
@@ -1390,7 +1358,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
 	}
-	/* ALTER SEQUENCE <name> */
 	else if (MATCH("ALTER SEQUENCE #id"))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
@@ -1399,7 +1366,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
-	/* ALTER SEQUENCE <name> NO */
 	else if (MATCH("ALTER SEQUENCE #id NO"))
 	{
 		static const char *const list_ALTERSEQUENCE2[] =
@@ -1407,7 +1373,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
-	/* ALTER SERVER <name> */
 	else if (MATCH("ALTER SERVER #id"))
 	{
 		static const char *const list_ALTER_SERVER[] =
@@ -1415,7 +1380,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER_SERVER);
 	}
-	/* ALTER SYSTEM SET, RESET, RESET ALL */
 	else if (MATCH("ALTER SYSTEM"))
 	{
 		static const char *const list_ALTERSYSTEM[] =
@@ -1423,11 +1387,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSYSTEM);
 	}
-	/* ALTER SYSTEM SET|RESET <name> */
 	else if (MATCH("ALTER SYSTEM {SET|RESET}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars, NULL);
 
-	/* ALTER VIEW <name> */
 	else if (MATCH("ALTER VIEW #id"))
 	{
 		static const char *const list_ALTERVIEW[] =
@@ -1435,7 +1397,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERVIEW);
 	}
-	/* ALTER MATERIALIZED VIEW <name> */
 	else if (MATCH("ALTER MATERIALIZED VIEW #id"))
 	{
 		static const char *const list_ALTERMATVIEW[] =
@@ -1444,15 +1405,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
 	}
 
-	/* ALTER POLICY <name> ON */
 	else if (MATCH("ALTER POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* ALTER POLICY <name> ON <table> */
 	else if (MATCH("ALTER POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* ALTER POLICY <name> ON <table> - show options */
 	else if (MATCH("ALTER POLICY #id ON #id"))
 	{
 		static const char *const list_ALTERPOLICY[] =
@@ -1460,51 +1418,33 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERPOLICY);
 	}
-	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (MATCH("ALTER POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (MATCH("ALTER POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
 	else if (MATCH("ALTER POLICY #id ON #id WITH CHECK"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER RULE <name>, add ON */
 	else if (MATCH("ALTER RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* If we have ALTER RULE <name> ON, then add the correct tablename */
 	else if (MATCH("ALTER RULE [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule, CAPTURE(1));
 
-	/* ALTER RULE <name> ON <name> */
 	else if (MATCH("ALTER RULE #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
-	/* ALTER TRIGGER <name>, add ON */
 	else if (MATCH("ALTER TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
 	else if (MATCH("ALTER TRIGGER [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger, CAPTURE(1));
 
-//  !!! This duplicates with the entry just above
-//	/*
-//	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
-//	 */
-//	else if (MATCH("ALTER TRIGGER #id ON"))
-//		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-
-	/* ALTER TRIGGER <name> ON <name> */
 	else if (MATCH("ALTER TRIGGER #id ON #id"))
 		COMPLETE_WITH_CONST("RENAME TO");
 
-	/*
-	 * If we detect ALTER TABLE <name>, suggest sub commands
-	 */
 	else if (MATCH("ALTER TABLE #id"))
 	{
 		static const char *const list_ALTER2[] =
@@ -1514,7 +1454,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
-	/* ALTER TABLE xxx ENABLE */
 	else if (MATCH("ALTER TABLE #id ENABLE"))
 	{
 		static const char *const list_ALTERENABLE[] =
@@ -1541,15 +1480,12 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TABLE [#id] ENABLE #kwd TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table, CAPTURE(1));
 
-	/* ALTER TABLE xxx INHERIT */
 	else if (MATCH("ALTER TABLE #id INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-	/* ALTER TABLE xxx NO INHERIT */
 	else if (MATCH("ALTER TABLE #id NO INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
-	/* ALTER TABLE xxx DISABLE */
 	else if (MATCH("ALTER TABLE #id DISABLE"))
 	{
 		static const char *const list_ALTERDISABLE[] =
@@ -1571,33 +1507,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DISABLERLS);
 	}
 
-	/* ALTER TABLE xxx ALTER */
 	else if (MATCH("ALTER TABLE [#id] ALTER"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
-	/* ALTER TABLE xxx RENAME */
 	else if (MATCH("ALTER TABLE [#id] RENAME"))
 		COMPLETE_WITH_ATTR(CAPTURE(1),
 						   " UNION SELECT 'COLUMN'"
 						   " UNION SELECT 'CONSTRAINT'"
 						   " UNION SELECT 'TO'");
 
-	/*
-	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
-	 * columns
-	 */
 	else if (MATCH("TABLE [#id] {ALTER|RENAME} COLUMN"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* ALTER TABLE xxx RENAME yyy */
 	else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
 	else if (MATCH("TABLE #id RENAME {COLUMN|CONSTRAINT} !TO"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (MATCH("TABLE #id DROP"))
 	{
 		static const char *const list_TABLEDROP[] =
@@ -1605,18 +1532,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEDROP);
 	}
-	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (MATCH("ALTER TABLE [#id] DROP COLUMN"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/*
-	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
-	 * provide list of constraints
-	 */
 	else if (MATCH("ALTER TABLE [#id] {DROP|RENAME|VALIDATE} CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table, CAPTURE(1));
 
-	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id"))
 	{
 		static const char *const list_COLUMNALTER[] =
@@ -1624,7 +1545,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNALTER);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET"))
 	{
 		static const char *const list_COLUMNSET[] =
@@ -1632,7 +1552,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNSET);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET ("))
 	{
 		static const char *const list_COLUMNOPTIONS[] =
@@ -1640,7 +1559,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id SET STORAGE"))
 	{
 		static const char *const list_COLUMNSTORAGE[] =
@@ -1648,7 +1566,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
 	}
-	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
 	else if (MATCH("ALTER TABLE #id ALTER {COLUMN }? #id DROP"))
 	{
 		static const char *const list_COLUMNDROP[] =
@@ -1662,7 +1579,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("ALTER TABLE [#id] CLUSTER ON"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
-	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
 	else if (MATCH("ALTER TABLE #id SET"))
 	{
 		static const char *const list_TABLESET[] =
@@ -1670,11 +1586,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLESET);
 	}
-	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
 	else if (MATCH("ALTER TABLE #id SET TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces, NULL);
 
-	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
 	else if (MATCH("ALTER TABLE #id SET WITHOUT"))
 	{
 		static const char *const list_TABLESET2[] =
@@ -1682,11 +1596,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLESET2);
 	}
-	/* ALTER TABLE <foo> RESET */
 	else if (MATCH("ALTER TABLE #id RESET"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER TABLE <foo> SET|RESET ( */
 	else if (MATCH("ALTER TABLE #id {RE}?SET("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
@@ -1740,7 +1652,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("TABLE #id REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
-	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
 	else if (MATCH("ALTER TABLESPACE #id"))
 	{
 		static const char *const list_ALTERTSPC[] =
@@ -1748,11 +1659,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTSPC);
 	}
-	/* ALTER TABLESPACE <foo> SET|RESET */
 	else if (MATCH("ALTER TABLESPACE #id {RE}?SET"))
 		COMPLETE_WITH_CONST("(");
 
-	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (MATCH("ALTER TABLESPACE #id {RE}?SET("))
 	{
 		static const char *const list_TABLESPACEOPTIONS[] =
@@ -1761,7 +1670,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
 	}
 
-	/* ALTER TEXT SEARCH */
 	else if (MATCH("ALTER TEXT SEARCH"))
 	{
 		static const char *const list_ALTERTEXTSEARCH[] =
@@ -1793,7 +1701,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
 	}
 
-	/* complete ALTER TYPE <foo> with actions */
 	else if (MATCH("ALTER TYPE #id"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1802,7 +1709,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (MATCH("ALTER TYPE #id ADD"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1810,7 +1716,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* ALTER TYPE <foo> RENAME	*/
 	else if (MATCH("ALTER TYPE #id RENAME"))
 	{
 		static const char *const list_ALTERTYPE[] =
@@ -1818,22 +1723,15 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERTYPE);
 	}
-	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
 	else if (MATCH("TYPE #id RENAME ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TO");
 
-	/*
-	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
-	 * attributes
-	 */
 	else if (MATCH("TYPE [#id] {ALTER|DROP|RENAME} ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	else if (MATCH("ALTER ATTRIBUTE #id"))
 		COMPLETE_WITH_CONST("TYPE");
 
-	/* complete ALTER GROUP <foo> */
 	else if (MATCH("ALTER GROUP #id"))
 	{
 		static const char *const list_ALTERGROUP[] =
@@ -1841,11 +1739,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERGROUP);
 	}
-	/* complete ALTER GROUP <foo> ADD|DROP with USER */
 	else if (MATCH("ALTER GROUP #id {ADD|DROP}"))
 		COMPLETE_WITH_CONST("USER");
 
-	/* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */
 	else if (MATCH("ALTER GROUP #id {ADD|DROP} USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
@@ -1878,37 +1774,22 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* CLUSTER */
-
-	/*
-	 * If the previous word is CLUSTER and not WITHOUT produce list of tables
-	 */
 	else if (MATCH(" !WITHOUT CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   "UNION SELECT 'VERBOSE'");
 
-	/*
-	 * If the previous words are CLUSTER VERBOSE produce list of tables
-	 */
 	else if (MATCH("CLUSTER VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
-	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (MATCH("CLUSTER !ON|VERBOSE"))
 		COMPLETE_WITH_CONST("USING");
 
-	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
 	else if (MATCH("CLUSTER VERBOSE #id"))
 		COMPLETE_WITH_CONST("USING");
 
-	/*
-	 * If we have CLUSTER <sth> USING, then add the index as well.
-	 */
 	else if (MATCH("CLUSTER [#id] USING"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
-	/*
-	 * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
-	 */
 	else if (MATCH("CLUSTER VERBOSE [#id] USING"))
 		COMPLETE_WITH_QUERY(Query_for_index_of_table, CAPTURE(1));
 
@@ -1962,15 +1843,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
-
-	/*
-	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
-	 * list of tables (Also cover the analogous backslash command)
-	 */
 	else if (MATCH("\\?COPY{ BINARY}?"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
 	else if (MATCH("\\?COPY{ BINARY}? #id"))
 	{
 		static const char *const list_FROMTO[] =
@@ -1978,14 +1853,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_FROMTO);
 	}
-	/* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
 	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO}"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
-	/* Handle COPY|BINARY <sth> FROM|TO filename */
 	else if (MATCH("\\?COPY{ BINARY}? #id {FROM|TO} #nwb"))
 	{
 		static const char *const list_COPY[] =
@@ -1994,7 +1867,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_COPY);
 	}
 
-	/* Handle COPY|BINARY <sth> FROM|TO filename CSV */
 	else if (MATCH("{FROM|TO} #nwb CSV"))
 	{
 		static const char *const list_CSV[] =
@@ -2003,7 +1875,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CSV);
 	}
 
-	/* CREATE DATABASE */
 	else if (MATCH("CREATE DATABASE #id"))
 	{
 		static const char *const list_DATABASE[] =
@@ -2017,12 +1888,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE DATABASE #id TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases, NULL);
 
-	/* CREATE EXTENSION */
-	/* Complete with available extensions rather than installed ones. */
 	else if (MATCH("CREATE EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions, NULL);
 
-	/* CREATE EXTENSION <name> */
 	else if (MATCH("CREATE EXTENSION #id"))
 	{
 		static const char *const list_CREATE_EXTENSION[] =
@@ -2030,12 +1898,11 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATE_EXTENSION);
 	}
-	/* CREATE EXTENSION <name> VERSION */
+
 	else if (MATCH("CREATE EXTENSION [#id] VERSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions,
 							CAPTURE(1));
 
-	/* CREATE FOREIGN */
 	else if (MATCH("CREATE FOREIGN"))
 	{
 		static const char *const list_CREATE_FOREIGN[] =
@@ -2044,7 +1911,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
 	}
 
-	/* CREATE FOREIGN DATA WRAPPER */
 	else if (MATCH("CREATE FOREIGN DATA WRAPPER #id"))
 	{
 		static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
@@ -2053,25 +1919,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATE_FOREIGN_DATA_WRAPPER);
 	}
 
-	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (MATCH("CREATE UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
-	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
 	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
-	/* Complete ... INDEX [<name>] ON with a list of tables  */
+
 	else if (MATCH("INDEX {CONCURRENTLY }? #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
-	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
 	else if (MATCH("INDEX #id CONCURRENTLY"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
 	else if (MATCH("CREATE {UNIQUE }? INDEX #id"))
 	{
 		static const char *const list_CREATE_INDEX[] =
@@ -2094,11 +1956,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("INDEX #id {CONCURRENTLY }? ON [#id]("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* same if you put in USING */
 	else if (MATCH("ON [#id] USING #id("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/* Complete USING with an index method */
 	else if (MATCH("INDEX #id {#nwb }* ON #id USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods, NULL);
 
@@ -2106,15 +1966,12 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
-	/* Complete "CREATE POLICY <name> ON" */
 	else if (MATCH("CREATE POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (MATCH("CREATE POLICY #id ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
 	else if (MATCH("CREATE POLICY #id ON #id"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2122,11 +1979,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR
-	 * ALL|SELECT|INSERT|UPDATE|DELETE"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR"))
 	{
 		static const char *const list_POLICYCMDS[] =
@@ -2134,7 +1986,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYCMDS);
 	}
-	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
 	else if (MATCH("CREATE POLICY #id ON #id FOR INSERT"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2142,11 +1993,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" Complete
-	 * "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR {SELECT|DELETE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2154,12 +2000,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-
-	/*
-	 * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK"
-	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH
-	 * CHECK"
-	 */
 	else if (MATCH("CREATE POLICY #id ON #id FOR {ALL|UPDATE}"))
 	{
 		static const char *const list_POLICYOPTIONS[] =
@@ -2167,24 +2007,19 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
 	}
-	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (MATCH("CREATE POLICY #id ON #id TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (MATCH("CREATE POLICY #id ON #id USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
-	/* Complete "CREATE RULE <sth>" with "AS" */
 	else if (MATCH("CREATE RULE #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE RULE <sth> AS with "ON" */
 	else if (MATCH("CREATE RULE #id AS"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */
 	else if (MATCH("RULE #id AS ON"))
 	{
 		static const char *const rule_events[] =
@@ -2192,15 +2027,12 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(rule_events);
 	}
-	/* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
 	else if (MATCH("AS ON {SELECT|UPDATE|INSERT|DELETE}"))
 		COMPLETE_WITH_CONST("TO");
 
-	/* Complete "AS ON <sth> TO" with a table name */
 	else if (MATCH("AS ON #kwd TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* CREATE TEMP/TEMPORARY SEQUENCE <name> */
 	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id"))
 	{
 		static const char *const list_CREATESEQUENCE[] =
@@ -2209,7 +2041,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE);
 	}
-/* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
 	else if (MATCH("CREATE {TEMP{ORARY}? }? SEQUENCE #id NO"))
 	{
 		static const char *const list_CREATESEQUENCE2[] =
@@ -2218,7 +2049,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATESEQUENCE2);
 	}
 
-/* CREATE SERVER <name> */
 	else if (MATCH("CREATE SERVER #id"))
 	{
 		static const char *const list_CREATE_SERVER[] =
@@ -2228,7 +2058,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE TABLE */
-	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (MATCH("CREATE {TEMP{ORARY}?}"))
 	{
 		static const char *const list_TEMP[] =
@@ -2236,7 +2065,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TEMP);
 	}
-	/* Complete "CREATE UNLOGGED" with TABLE */
 	else if (MATCH("CREATE UNLOGGED"))
 	{
 		static const char *const list_UNLOGGED[] =
@@ -2253,7 +2081,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETABLESPACE);
 	}
-	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
 	else if (MATCH("CREATE TABLESPACE #id OWNER"))
 		COMPLETE_WITH_CONST("LOCATION");
 
@@ -2269,7 +2096,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
-	/* complete CREATE TRIGGER <name> with BEFORE,AFTER */
 	else if (MATCH("CREATE TRIGGER #id"))
 	{
 		static const char *const list_CREATETRIGGER[] =
@@ -2277,7 +2103,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER);
 	}
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
 	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER}"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
@@ -2285,7 +2110,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
-	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
 	else if (MATCH("CREATE TRIGGER #id INSTEAD OF"))
 	{
 		static const char *const list_CREATETRIGGER_EVENTS[] =
@@ -2293,7 +2117,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
 	}
-	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
 	else if (MATCH("CREATE TRIGGER #id {BEFORE|AFTER|INSTEAD_OF} #kwd"))
 	{
 		static const char *const list_CREATETRIGGER2[] =
@@ -2301,23 +2124,15 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
 	}
-
-	/*
-	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
-	 * tables
-	 */
 	else if (MATCH("TRIGGER #id {BEFORE|AFTER} #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (MATCH("INSTEAD OF #kwd ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (MATCH("CREATE TRIGGER {#nwb }+ EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
 
-/* CREATE ROLE,USER,GROUP <name> */
 	else if (MATCH("CREATE {{USER !MAPPING_}|ROLE |GROUP } #id"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2331,7 +2146,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATEROLE);
 	}
 
-/* CREATE ROLE,USER,GROUP <name> WITH */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
@@ -2346,14 +2160,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
 	}
 
-	/*
-	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
-	 * PASSWORD
-	 */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id {UN}?ENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 
-	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
 	else if (MATCH("CREATE {ROLE|GROUP|USER} #id IN"))
 	{
 		static const char *const list_CREATEROLE3[] =
@@ -2363,11 +2172,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE VIEW */
-	/* Complete CREATE VIEW <name> with AS */
 	else if (MATCH("CREATE VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (MATCH("CREATE VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
@@ -2375,11 +2182,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
-	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (MATCH("CREATE MATERIALIZED VIEW #id"))
 		COMPLETE_WITH_CONST("AS");
 
-	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
 	else if (MATCH("CREATE MATERIALIZED VIEW #id AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
@@ -2387,11 +2192,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("CREATE EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 
-	/* Complete CREATE EVENT TRIGGER <name> with ON */
 	else if (MATCH("CREATE EVENT TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
 	else if (MATCH("CREATE EVENT TRIGGER #id ON"))
 	{
 		static const char *const list_CREATE_EVENT_TRIGGER_ON[] =
@@ -2428,11 +2231,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 
-	/* Complete DELETE FROM with a list of tables */
 	else if (MATCH("DELETE FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 
-	/* Complete DELETE FROM <table> */
 	else if (MATCH("DELETE FROM #id"))
 	{
 		static const char *const list_DELETE[] =
@@ -2452,10 +2253,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DO */
-
-	/*
-	 * Complete DO with LANGUAGE.
-	 */
 	else if (MATCH("DO"))
 	{
 		static const char *const list_DO[] =
@@ -2465,11 +2262,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* DROP (when not the previous word) */
-	/* DROP AGGREGATE */
 	else if (MATCH("DROP AGGREGATE #id"))
 		COMPLETE_WITH_CONST("(");
 
-	/* DROP object with CASCADE / RESTRICT */
 	else if (MATCH("DROP {{{COLLATION|CONVERSION|DOMAIN|EXTENSION|[FUNCTION]|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW|{EVENT TRIGGER}|{FOREGN DATA WRAPPER}|{TEXT SEARCH {CONFIGURATION|DICTIONARY|PARSER|TEMPLATE}}} #id }|{AGGREGATE #id(..)}}"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "FUNCTION") == 0)
@@ -2492,7 +2287,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
 	}
 
-	/* DROP MATERIALIZED VIEW */
 	else if (MATCH("DROP MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 
@@ -2502,7 +2296,6 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("DROP {AGGREGATE|FUNCTION} [#id]("))
 		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
 
-	/* DROP OWNED BY */
 	else if (MATCH("DROP OWNED"))
 		COMPLETE_WITH_CONST("BY");
 	else if (MATCH("DROP OWNED BY"))
@@ -2517,7 +2310,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
 	}
 
-	/* DROP TRIGGER */
 	else if (MATCH("DROP TRIGGER #id"))
 		COMPLETE_WITH_CONST("ON");
 	else if (MATCH("DROP TRIGGER [#id] ON"))
@@ -2530,26 +2322,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_DROPCR);
 	}
 
-	/* DROP EVENT TRIGGER */
 	else if (MATCH("DROP EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 
 	else if (MATCH("DROP_EVENT TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers, NULL);
 
-	/* DROP POLICY <name>  */
 	else if (MATCH("DROP POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies, NULL);
 
-	/* DROP POLICY <name> ON */
 	else if (MATCH("DROP POLICY #id"))
 		COMPLETE_WITH_CONST("ON");
 
-	/* DROP POLICY <name> ON <table> */
 	else if (MATCH("DROP POLICY [#id] ON"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy, CAPTURE(1));
 
-	/* DROP RULE */
 	else if (MATCH("DROP RULE #id"))
 		COMPLETE_WITH_CONST("ON");
 
@@ -2596,7 +2383,6 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* FETCH && MOVE */
-	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	else if (MATCH("{FETCH|MOVE}"))
 	{
 		static const char *const list_FETCH1[] =
@@ -2604,7 +2390,6 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_FETCH1);
 	}
-	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
 	else if (MATCH("{FETCH|MOVE} #id"))
 	{
 		static const char *const list_FETCH2[] =
@@ -2627,20 +2412,16 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* FOREIGN DATA WRAPPER */
-	/* applies in ALTER/DROP FDW and in CREATE SERVER */
 	else if (MATCH("{ALTER|DROP} FOREIGN DATA WRAPPER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws, NULL);
 
-/* FOREIGN TABLE */
 	else if (MATCH("CREATE FOREIGN TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
-/* FOREIGN SERVER */
 	else if (MATCH("FOREIGN SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers, NULL);
 
 /* GRANT && REVOKE */
-	/* Complete GRANT/REVOKE with a list of roles and privileges */
 	else if (MATCH("{GRANT|REVOKE}"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
@@ -2659,11 +2440,6 @@ psql_completion(const char *text, int start, int end)
 							" UNION SELECT 'ALL'",
 							NULL);
 	}
-
-	/*
-	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
-	 * TO/FROM
-	 */
 	else if (MATCH("{GRANT|REVOKE} {SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMP{ORARY}?|EXECUTE|USAGE|ALL}"))
 			COMPLETE_WITH_CONST("ON");
 	else if (MATCH("GRANT #id"))
@@ -2731,7 +2507,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON ALL #kwd IN SCHEMA #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2740,7 +2515,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN DATA WRAPPER #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2749,7 +2523,6 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
 	else if (MATCH("[GRANT|REVOKE] #kwd ON FOREIGN SERVER #id"))
 	{
 		if (pg_strcasecmp(CAPTURE(1), "GRANT") == 0)
@@ -2758,35 +2531,24 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CONST("FROM");
 	}
 
-	/*
-	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
-	 * CURRENT_USER, or SESSION_USER.
-	 */
 	else if (MATCH("{GRANT|REVOKE}{ #nwb}+ {TO|FROM}"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-	/* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
 	else if (MATCH("GRANT #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("TO");
 
 	else if (MATCH("REVOKE #kwd ON #kwd #id"))
 		COMPLETE_WITH_CONST("FROM");
 
-	/*
-	 * Complete "GRANT/REVOKE * TO/FROM" with username, PUBLIC,
-	 * CURRENT_USER, or SESSION_USER.
-	 */
 	else if (MATCH("GRANT #kwd TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
 	else if (MATCH("REVOKE #kwd FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles, NULL);
 
-/* GROUP BY */
 	else if (MATCH("FROM #id GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
-/* IMPORT FOREIGN SCHEMA */
 	else if (MATCH("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
 
@@ -2794,22 +2556,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
-	/* Complete INSERT with "INTO" */
 	else if (MATCH("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 
-	/* Complete INSERT INTO with table names */
 	else if (MATCH("INSERT INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 
-	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (MATCH("INSERT INTO [#id]("))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-	/*
-	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
-	 * "TABLE" or "DEFAULT VALUES"
-	 */
 	else if (MATCH("INSERT INTO #id"))
 	{
 		static const char *const list_INSERT[] =
@@ -2818,10 +2573,6 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INSERT);
 	}
 
-	/*
-	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
-	 * "TABLE"
-	 */
 	else if (MATCH("INSERT INTO #id(..)"))
 	{
 		static const char *const list_INSERT[] =
@@ -2830,12 +2581,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_INSERT);
 	}
 
-	/* Insert an open parenthesis after "VALUES" */
 	else if (MATCH("!DEFAULT VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
-	/* Complete LOCK [TABLE] with a list of tables */
 	else if (MATCH("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
@@ -2845,11 +2594,9 @@ psql_completion(const char *text, int start, int end)
 
 	/* For the following, handle the case of a single table only for now */
 
-	/* Complete LOCK [TABLE] <table> with "IN" */
 	else if (MATCH("LOCK{ TABLE}? !TABLE"))
 		COMPLETE_WITH_CONST("IN");
 
-	/* Complete LOCK [TABLE] <table> IN with a lock mode */
 	else if (MATCH("LOCK{ TABLE}? #id IN"))
 	{
 		static const char *const lock_modes[] =
@@ -2862,26 +2609,21 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(lock_modes);
 	}
 
-/* NOTIFY */
 	else if (MATCH("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'", NULL);
 
-/* OPTIONS */
 	else if (MATCH("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
-/* OWNER TO  - complete with available roles */
 	else if (MATCH("OWNER TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
 
-/* ORDER BY */
 	else if (MATCH("FROM #id ORDER"))
 		COMPLETE_WITH_CONST("BY");
 
 	else if (MATCH("FROM [#id] ORDER BY"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-/* PREPARE xx AS */
 	else if (MATCH("PREPARE #id AS"))
 	{
 		static const char *const list_PREPARE[] =
@@ -2973,7 +2715,6 @@ psql_completion(const char *text, int start, int end)
 	/* naah . . . */ 
 
 /* SET, RESET, SHOW */
-	/* Complete with a variable name */
 	else if (MATCH("^{RE}?SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars, NULL);
 	else if (MATCH("SHOW"))
@@ -3033,10 +2774,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(constraint_list);
 	}
-	/* Complete SET ROLE */
 	else if (MATCH("SET ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles, NULL);
-	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
+
 	else if (MATCH("SET SESSION"))
 	{
 		static const char *const my_list[] =
@@ -3044,15 +2784,14 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(my_list);
 	}
-	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (MATCH("SET SESSION AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'",
 							NULL);
-	/* Complete RESET SESSION with AUTHORIZATION */
+
 	else if (MATCH("RESET SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 
-	/* Complete SET <var> with "TO" */
+
 	else if (MATCH("^{!UPDATE }*SET !TABLESPACE|SCHEMA|="))
 		COMPLETE_WITH_CONST("TO");
 
@@ -3112,7 +2851,6 @@ psql_completion(const char *text, int start, int end)
 		}
 	}
 
-/* START TRANSACTION */
 	else if (MATCH("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
@@ -3127,19 +2865,16 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("TABLESAMPLE #id"))
 		COMPLETE_WITH_CONST("(");
 
-/* TRUNCATE */
 	else if (MATCH("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* UNLISTEN */
 	else if (MATCH("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'", NULL);
 
 /* UPDATE */
-	/* If prev. word is UPDATE suggest a list of tables */
 	else if (MATCH("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
-	/* Complete UPDATE <table> with "SET" */
+
 	else if (MATCH("UPDATE #id"))
 		COMPLETE_WITH_CONST("SET");
 
@@ -3151,11 +2886,9 @@ psql_completion(const char *text, int start, int end)
 	else if (MATCH("[#id] SET"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-/* UPDATE xx SET yy = */
 	else if (MATCH("UPDATE #id SET #id"))
 		COMPLETE_WITH_CONST("=");
 
-/* USER MAPPING */
 	else if (MATCH("{ALTER|DROP|CREATE} USER MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
 
@@ -3209,12 +2942,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
-	/* If the previous word is ANALYZE, produce list of tables */
 	else if (MATCH("ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
-	/* Simple case of the word before the where being the table name */
 	else if (MATCH("[#id] WHERE"))
 		COMPLETE_WITH_ATTR(CAPTURE(1), "");
 
-- 
1.8.3.1

#47Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Michael Paquier (#42)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Mon, Dec 7, 2015 at 8:41 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Nov 17, 2015 at 12:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

+       /* TODO:TM -- begin temporary, not part of the patch! */
+       Assert(!word_matches(NULL, ""));
+ [...]
+       Assert(!word_matches("foo", ""));
+       /* TODO:TM -- end temporary */

Be sure to not forget to remove that later.

Thanks for looking at this Michael. It's probably not much fun to
review! Here is a new version with that bit removed. More responses
inline below.

-       else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
-                        pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
-                        (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
-                         pg_strcasecmp(prev3_wd, "IN") == 0))
-       {
-               static const char *const
list_ALTER_DEFAULT_PRIVILEGES_REST[] =
-               {"GRANT", "REVOKE", NULL};
-
-               COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
-       }
+       else if (TailMatches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE",
MatchAny) ||
+                        TailMatches5("DEFAULT", "PRIVILEGES", "IN",
"SCHEMA", MatchAny))
+               CompleteWithList2("GRANT", "REVOKE");
For this chunk I think that you need to check for ROLE|USER and not only
ROLE.

Right, done.

+ else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
{
static const char *const list_ALTERDOMAIN[] =
{"CONSTRAINT", "TO", NULL};
I think you should remove COMPLETE_WITH_LIST here for consistency with the
rest.

Right, done.

-       else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
-                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-                        pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+       else if (TailMatches3("RENAME", "CONSTRAINT", MatchAny))
COMPLETE_WITH_CONST("TO");
Perhaps you are missing DOMAIN here?

Right, done.

-       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-                        pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
-                        pg_strcasecmp(prev_wd, "NO") == 0)
-       {
-               static const char *const list_ALTERSEQUENCE2[] =
-               {"MINVALUE", "MAXVALUE", "CYCLE", NULL};
-
-               COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
-       }
+       else if (TailMatches4("ALTER", "SEQUEMCE", MatchAny, "NO"))
+               CompleteWithList3("MINVALUE", "MAXVALUE", "CYCLE");
Typo here: s/SEQUEMCE/SEQUENCE.

Oops, fixed.

-       else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
-                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-                        (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
-                         pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
-                        pg_strcasecmp(prev_wd, "TO") != 0)
+       else if (TailMatches6("ALTER", "TABLE", MatchAny, "RENAME",
"COLUMN|CONSTRAINT", MatchAny) &&
+                        !TailMatches1("TO"))
This should use TailMatches5 without ALTER for consistency with the existing
code?

Ok, done.

-       else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
-                        pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
+       else if (TailMatches1("CLUSTER") && !TailMatches2("WITHOUT",
"CLUSTER"))
Here removing CLUSTER should be fine.

Ok.

-       else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-                        pg_strcasecmp(prev_wd, "ON") != 0 &&
-                        pg_strcasecmp(prev_wd, "VERBOSE") != 0)
-       {
+       else if (TailMatches2("CLUSTER", MatchAny) &&
!TailMatches1("VERBOSE"))
Handling of ON has been forgotten.

Right, fixed.

-       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
-                        !(pg_strcasecmp(prev2_wd, "USER") == 0 &&
pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
-                        (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
-                         pg_strcasecmp(prev2_wd, "GROUP") == 0 ||
pg_strcasecmp(prev2_wd, "USER") == 0))
+       else if (TailMatches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+                        !TailMatches3("CREATE", "USER", "MAPPING"))
!TailMatches2("USER", "MAPPING")?

Ok.

-       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
-                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
-                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+       else if (TailMatches3("CREATE", "MATERIALIZED", "VIEW"))
Forgot a MatchAny here?

Right, fixed.

-       else if (pg_strcasecmp(prev_wd, "DELETE") == 0 &&
-                        !(pg_strcasecmp(prev2_wd, "ON") == 0 ||
-                          pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
-                          pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
-                          pg_strcasecmp(prev2_wd, "AFTER") == 0))
+       else if (TailMatches1("DELETE") &&
!TailMatches2("ON|GRANT|BEFORE|AFTER", "DELETE"))
COMPLETE_WITH_CONST("FROM");
In the second clause checking for DELETE is not necessary.

Ok.

-       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
-                        prev2_wd[0] == '\0')
+       else if (Matches1("EXECUTE"))
This looks not complete.

I think it's correct. The existing code has prev2_wd[0] == '\0' as a
way of checking that EXECUTE is the first word. Matches1("EXECUTE")
does the same.

+       else if (TailMatches1("EXPLAIN"))
+               CompleteWithList7("SELECT", "INSERT", "DELETE", "UPDATE",
"DECLARE",
+                                                       "ANALYZE",
"VERBOSE");
+       else if (TailMatches2("EXPLAIN", "ANALYZE|ANALYSE"))
ANALYSE should be removed, former code only checked for ANALYZE => I heard
about the grammar issues here :)

Right. Removed.

-               else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
+               else if (TailMatches4("GRANT", MatchAny, MatchAny,
MatchAny))
+                       COMPLETE_WITH_CONST("TO");
+               else
+                       COMPLETE_WITH_CONST("FROM");
+       }
+
+       /* Complete "GRANT/REVOKE * ON * *" with TO/FROM */
+       else if (TailMatches5("GRANT|REVOKE", MatchAny, "ON", MatchAny,
MatchAny))
+       {
+               if (TailMatches5("GRANT", MatchAny, MatchAny, MatchAny,
MatchAny))
Isn't the first check with TailMatches4 enough here?

I don't think so: the first handles GRANT * ON * where the final word
isn't one of the known keywords that will be followed by an
appropriate list of objects (in other words when it was a table name).
The TailMatches5 case is for supplying TO or FROM after you write eg
GRANT * ON TABLE *.

-               if (pg_strcasecmp(prev6_wd, "GRANT") == 0)
+               if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny,
MatchAny, MatchAny))
HeadMatches1 perhaps?

I agree that would probably be better but Alvaro suggested following
the existing logic in the first pass, which was mostly based on tails,
and then considering simpler head-based patterns in a future pass.

Thanks!

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-v9.patch.gzapplication/x-gzip; name=tab-complete-v9.patch.gzDownload
#48Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#47)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 9, 2015 at 8:17 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

On Mon, Dec 7, 2015 at 8:41 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Nov 17, 2015 at 12:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

+       /* TODO:TM -- begin temporary, not part of the patch! */
+       Assert(!word_matches(NULL, ""));
+ [...]
+       Assert(!word_matches("foo", ""));
+       /* TODO:TM -- end temporary */

Be sure to not forget to remove that later.

Thanks for looking at this Michael. It's probably not much fun to
review! Here is a new version with that bit removed. More responses
inline below.

I had a hard time not sleeping when reading it... That's very mechanical.

I agree that would probably be better but Alvaro suggested following
the existing logic in the first pass, which was mostly based on tails,
and then considering simpler head-based patterns in a future pass.

Fine with me.

So what do we do now? There is your patch, which is already quite big,
but as well a second patch based on regexps, which is far bigger. And
at the end they provide a similar result:

Here is for example what the regexp patch does for some complex
checks, like ALTER TABLE RENAME:
     /* ALTER TABLE xxx RENAME yyy */
-    else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-             pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-             pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-             pg_strcasecmp(prev_wd, "TO") != 0)
+    else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
And what Thomas's patch does:
+    else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAny) &&
+             !TailMatches1("CONSTRAINT|TO"))

The regexp patch makes the negative checks somewhat easier to read
(there are 19 positions in tab-complete.c doing that), still inventing
a new langage and having a heavy refactoring just tab completion of
psql seems a bit too much IMO, so my heart balances in favor of
Thomas' stuff. Thoughts from others?
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49David Fetter
david@fetter.org
In reply to: Michael Paquier (#48)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 09, 2015 at 08:49:22PM +0900, Michael Paquier wrote:

On Wed, Dec 9, 2015 at 8:17 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

On Mon, Dec 7, 2015 at 8:41 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Nov 17, 2015 at 12:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thomas Munro wrote:

New version attached, merging recent changes.

I wonder about the TailMatches and Matches macros --- wouldn't it be
better to have a single one, renaming TailMatches to Matches and
replacing the current Matches() with an initial token that corresponds
to anchoring to start of command? Just wondering, not terribly attached
to the idea.

+       /* TODO:TM -- begin temporary, not part of the patch! */
+       Assert(!word_matches(NULL, ""));
+ [...]
+       Assert(!word_matches("foo", ""));
+       /* TODO:TM -- end temporary */

Be sure to not forget to remove that later.

Thanks for looking at this Michael. It's probably not much fun to
review! Here is a new version with that bit removed. More responses
inline below.

I had a hard time not sleeping when reading it... That's very mechanical.

I agree that would probably be better but Alvaro suggested following
the existing logic in the first pass, which was mostly based on tails,
and then considering simpler head-based patterns in a future pass.

Fine with me.

So what do we do now? There is your patch, which is already quite big,
but as well a second patch based on regexps, which is far bigger. And
at the end they provide a similar result:

Here is for example what the regexp patch does for some complex
checks, like ALTER TABLE RENAME:
/* ALTER TABLE xxx RENAME yyy */
-    else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
-             pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
-             pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
-             pg_strcasecmp(prev_wd, "TO") != 0)
+    else if (MATCH("TABLE #id RENAME !CONSTRAINT|TO"))
And what Thomas's patch does:
+    else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAny) &&
+             !TailMatches1("CONSTRAINT|TO"))

The regexp patch makes the negative checks somewhat easier to read
(there are 19 positions in tab-complete.c doing that), still inventing
a new langage and having a heavy refactoring just tab completion of
psql seems a bit too much IMO, so my heart balances in favor of
Thomas' stuff. Thoughts from others?

Agreed that the "whole new language" aspect seems like way too big a
hammer, given what it actually does.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Greg Stark
stark@mit.edu
In reply to: David Fetter (#49)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 9, 2015 at 2:27 PM, David Fetter <david@fetter.org> wrote:

Agreed that the "whole new language" aspect seems like way too big a
hammer, given what it actually does.

Which would be easier to update when things change?
Which would be possible to automatically generate from gram.y?

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51David Fetter
david@fetter.org
In reply to: Greg Stark (#50)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 09, 2015 at 03:49:20PM +0000, Greg Stark wrote:

On Wed, Dec 9, 2015 at 2:27 PM, David Fetter <david@fetter.org> wrote:

Agreed that the "whole new language" aspect seems like way too big a
hammer, given what it actually does.

Which would be easier to update when things change?

This question seems closer to being on point with the patch sets
proposed here.

Which would be possible to automatically generate from gram.y?

This seems like it goes to a wholesale context-aware reworking of tab
completion rather than the myopic ("What has happened within the past N
tokens?", for slowly increasing N) versions of tab completions in both
the current code and in the two proposals here.

A context-aware tab completion wouldn't care how many columns you were
into a target list, or a FROM list, or whatever, as it would complete
based on the (possibly nested) context ("in a target list", e.g.)
rather than on inferences made from some slightly variable number of
previous tokens.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Michael Paquier
michael.paquier@gmail.com
In reply to: Greg Stark (#50)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 10, 2015 at 12:49 AM, Greg Stark <stark@mit.edu> wrote:

On Wed, Dec 9, 2015 at 2:27 PM, David Fetter <david@fetter.org> wrote:

Agreed that the "whole new language" aspect seems like way too big a
hammer, given what it actually does.

Which would be easier to update when things change?

Regarding that both patches are equal compared to the current methods
with strcmp.

Which would be possible to automatically generate from gram.y?

None of those patches take this approach.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: David Fetter (#51)
Re: Making tab-complete.c easier to maintain

Hello,

At Wed, 9 Dec 2015 10:31:06 -0800, David Fetter <david@fetter.org> wrote in <20151209183106.GC10778@fetter.org>

On Wed, Dec 09, 2015 at 03:49:20PM +0000, Greg Stark wrote:

On Wed, Dec 9, 2015 at 2:27 PM, David Fetter <david@fetter.org> wrote:

Agreed that the "whole new language" aspect seems like way too big a
hammer, given what it actually does.

Which would be easier to update when things change?

This question seems closer to being on point with the patch sets
proposed here.

I agree to some extent.

I'm unhappy with context matching using previous_words in two
points. Current code needs human-readable comments describing
almost all matchings. It is hard to maintain and some of them
actually are wrong. The hardness is largely alleviated by
Thomas's approach exept for complex ones. Another is that
previous_words method is not-enough adaptable for optional words
in syntax. For example, CREATE INDEX has a complex syntax and
current rather complex code does not cover it fully (or enough).

On the other hand, regexp is quite heavy-weight. Current code
does one completion in 1 milliseconds but regexps simplly
replaced with current matching code takes nearly 100ms on my
environment. But appropriate refactoring reduces it to under 10
ms.

If we need more powerful completion (which means it covers more
wide area of syntax including more optional words), Thomas's
approach would face difficulties of another level of
complexity. I'd like to overcome it.

Which would be possible to automatically generate from gram.y?

This seems like it goes to a wholesale context-aware reworking of tab
completion rather than the myopic ("What has happened within the past N
tokens?", for slowly increasing N) versions of tab completions in both
the current code and in the two proposals here.

A context-aware tab completion wouldn't care how many columns you were
into a target list, or a FROM list, or whatever, as it would complete
based on the (possibly nested) context ("in a target list", e.g.)
rather than on inferences made from some slightly variable number of
previous tokens.

It's unknown to me how much the full-context-aware completion
makes me happy. But it would be another high-wall to overcome..

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#53)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 10, 2015 at 5:38 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

I'm unhappy with context matching using previous_words in two
points. Current code needs human-readable comments describing
almost all matchings. It is hard to maintain and some of them
actually are wrong. The hardness is largely alleviated by
Thomas's approach exept for complex ones. Another is that
previous_words method is not-enough adaptable for optional words
in syntax. For example, CREATE INDEX has a complex syntax and
current rather complex code does not cover it fully (or enough).

Yep.

On the other hand, regexp is quite heavy-weight. Current code
does one completion in 1 milliseconds but regexps simplly
replaced with current matching code takes nearly 100ms on my
environment. But appropriate refactoring reduces it to under 10
ms.

That's quite a difference in performance. A good responsiveness is
always nice for such things to make the user confortable.

If we need more powerful completion (which means it covers more
wide area of syntax including more optional words), Thomas's
approach would face difficulties of another level of
complexity. I'd like to overcome it.

That's a valid concern for sure because the patch of Thomas is not
much smart in emulating negative checks, still the main idea to not
rely anymore on some checks based on pg_strcmp or similar but have
something that is list-based, with a primitive sub-language in it is
very appealing.

As a next step, more committer and hacker input (people who have
worked on tab completion of psql) would be a nice next step. IMO, as
someone who has hacked tab-complete.c a couple of times I think that
Thomas' patch has merit, now it would make backpatch harder. Also, if
we prioritize a dynamically generated tab completion using gram.y, so
be it and let's reject both patches then...
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Greg Stark
stark@mit.edu
In reply to: Michael Paquier (#54)
Re: Making tab-complete.c easier to maintain

On Fri, Dec 11, 2015 at 8:12 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Also, if
we prioritize a dynamically generated tab completion using gram.y, so
be it and let's reject both patches then...

Fwiw I poked at the bison output to see if it would be possible to do.
I think it's theoretically possible but a huge project and would
create dependencies on bison internals that we would be unlikelly to
accept. (Unless we can get new API methods added to bison which is not
entirely unreasonable). The problem is that bison is only a small part
of the problem.

You would need

a) A new protocol message to send the partial query to the server and
get back a list of completions
b) Machinery in bison to return both all terminals that could come
next as well as all possible terminals it could reduce to
c) Some kind of reverse lexer to determine for each terminal what the
current partial input would have to match to be accepted
d) Some way to deal with the partially parsed query to find out what
schemas, tables, column aliases, etc should be considered for possible
completion

The machinery to do (b) is actually there in bison for the error
reporting. It's currently hard coded to limit the output to 5 and
there's no API for it, just a function that returns an error string.
But it might be possible to get bison to add an API method for it. But
that's as far as I got. I have no idea what (c) and (d) would look
like.

So I don't think it makes sense to hold up improvements today hoping
for something like this. What might be more realistic is making sure
to design the minilanguage to be easily generated by perl scripts or
the like and then write something picking up easy patterns in gram.y
or possibly poking through the bison table to generate a table of
minilanguage matchers. My instinct is that would be easier to do with
a real minilanguage instead of a regular expression system.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#55)
Re: Making tab-complete.c easier to maintain

Greg Stark <stark@mit.edu> writes:

Fwiw I poked at the bison output to see if it would be possible to do.
I think it's theoretically possible but a huge project and would
create dependencies on bison internals that we would be unlikelly to
accept.

That's the impression I got when I looked at it briefly, too. Without
some new APIs from bison it seems like it'd be way too messy/fragile.

You would need
a) A new protocol message to send the partial query to the server and
get back a list of completions

As far as that goes, I'd imagined the functionality continuing to be
on the psql side. If we make it wait for a protocol upgrade, that
makes it even more improbable that it will ever happen. psql already
has its own copy of the lexer, so making it have something derived
from the grammar doesn't seem like a maintainability problem.

b) Machinery in bison to return both all terminals that could come
next as well as all possible terminals it could reduce to

Yeah, this is the hard part.

d) Some way to deal with the partially parsed query to find out what
schemas, tables, column aliases, etc should be considered for possible
completion

I was imagining that some of that knowledge could be pushed back into the
grammar. That is, instead of just using generic nonterminals like ColId,
we'd need to have TableId, SchemaId, etc and be careful to use the
appropriate one(s) in each production of the grammar. Then, psql would
know which completion query to issue by noting which of these particular
nonterminals is a candidate for the next token right now. However, that
moves the goalposts in terms of what we'd have to be able to get back from
the alternate bison machinery.

Also, it's not just a SMOP to modify the grammar like that: it's not
at all unlikely that attempting to introduce such a finer categorization
would lead to a broken grammar, ie shift/reduce or reduce/reduce
conflicts. We couldn't be sure it would work till we've tried it.

So I don't think it makes sense to hold up improvements today hoping
for something like this.

Yeah, it's certainly a wishlist item rather than something that should
block shorter-term improvements.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#56)
Re: Making tab-complete.c easier to maintain

On Sat, Dec 12, 2015 at 12:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

So I don't think it makes sense to hold up improvements today hoping
for something like this.

Yeah, it's certainly a wishlist item rather than something that should
block shorter-term improvements.

OK, hence it seems that the next move is to move on with Thomas' stuff.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#47)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 9, 2015 at 8:17 PM, Thomas Munro wrote:

Thanks for looking at this Michael. It's probably not much fun to
review! Here is a new version with that bit removed. More responses
inline below.

Could this patch be rebased? There are now conflicts with 8b469bd7 and
it does not apply cleanly.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Michael Paquier (#58)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Sun, Dec 13, 2015 at 1:08 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Wed, Dec 9, 2015 at 8:17 PM, Thomas Munro wrote:

Thanks for looking at this Michael. It's probably not much fun to
review! Here is a new version with that bit removed. More responses
inline below.

Could this patch be rebased? There are now conflicts with 8b469bd7 and
it does not apply cleanly.

New version attached.

I've also add (very) primitive negative pattern support and used it in
5 places. Improvement? Examples:

        /* ALTER TABLE xxx RENAME yyy */
-       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAny) &&
-                        !TailMatches1("CONSTRAINT|TO"))
+       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME",
"!CONSTRAINT|TO"))
                COMPLETE_WITH_CONST("TO");
        /* If we have CLUSTER <sth>, then add "USING" */
-       else if (TailMatches2("CLUSTER", MatchAny) &&
!TailMatches1("VERBOSE|ON"))
+       else if (TailMatches2("CLUSTER", "!VERBOSE|ON"))
                COMPLETE_WITH_CONST("USING");

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v10.patch.gzapplication/x-gzip; name=tab-complete-macrology-v10.patch.gzDownload
�J�mVtab-complete-macrology-v10.patch�}kwI��g�W���^�V�=<3�{1�2�1h��q�p�P��F�P�n���}�]YU��,�m��9g��������Onn��'��
���������E����U���h~��F���}���h2G���Q������i���8<?E����Z���'O��?:::r��_���T*��):"���#��x���~}������S��2������*�}����r~7��}�|8�+����i2�P���zt�����d�o�v������J).(��>G|���x�&#<�,^���p�~`������!�/@��v��W�O��Z,pK�%�kG�8�����s�[���8b���&3P��T���HWK��z�W���]"_+�����g�%��#"���H�'�I��$�������!H|�]��@����&>�.u���8����%P|�]J�s����o�P���R���j����wC2<��L7���G���q�}�>�	�qy?"��f�D1�.D��D1i��Z�����=�����`�T��!f|��=|�/���J�8Hw���Qz�(����4���:��W��(���p����VS����
�'����d�n��p��>O������&�Z�����t��3�x��#_n>���x����H��s���w�Ay��J,���'��	a�d��&�N�o4\F�x�WF�`�M�=�@���1��0����ohr�HW8{@��������8Z��p�cH�gA�p������u���,HRv�,��I<����C���|$��0��{H��2��G�r�[��L���nR}�n�����:x|��m��f�-�)�������|�����X8*���i!%����]��b�+�@{������(����%��x�B_"|�p�Pt�X=H�~�F�}��0z�0���}�s1���>h�j�gx��rN��Z>���h:������7�H�r%��X.0Dl���)&��� ��?R|0�{{�hu��"��?����~=>��W�l�'�`L�4�eT�#u��)N�vG�XJu6�k<�'�k��19&�J�&�����?���_����X�Ed��+Y���^c�N�_������'��������&���S`�V���Lh}����L��gB(�c���#�5<OaN�I������QT��!(�`�i~t4�-��<�s%����_����!���)]F�J�� > �|0:���h6�����nB>��������u4����G"��s)�r�a�?���	�����'���?��#�I>E�~�n�gt�|Ju��r����q�<':��e�I����t?]��#����gd"L�p����
���;��G�l�Fb)�5LT<�$�G��C��^����_�	�:����(N���=�H��fP�|g+��������P�B��v��G�Yc�|�z����K�TL���e4a���C@�r����1���ix��0��B�e��q3��J����4x^��7����)���=�6;��r��&j��w	�jUkvN���N,�e�R)�5%V��P��gm�Ji��T�d�(��H&�����$�U��j�VZ`;����oQc-j6`OJ|`���8a-Nl(��������oq�Z��?+-����y}�3���F�������D����T�D|^Z<������BJB�[<���&uj������!Fa�����s�T�+��*��d��hU�a�e�jf�T���5�g"H��k��s]S�|������'N�<���*F�vb�'O�`��9�u��bF!�y�o
��p�Pe�@f�+7���!:fnX�zo�����h��4{�C��~��&���y����W���W�V�6m���&�p����P����M�l�OW����f}@;]4��v�!�j��=��8N�S��\>?a���gA���0��cg�i��R_�|(������xv��hS�->�}�"��!�0:�b9�2/�
� }XDO�c���������.�iy~���Cr���1��2:b���
��p�[<
�O�K������z�V��}�N��CB�M�W�9��?!���*9"V��Au�#��
x��D�;�����3�����0���g����7o	4����S{���!bK�U�9h�����VPRW#"��3��S\07*��'"%�{4
gh���+2u����n!�M��!����hG��lP .��h-��|)���u���u6_��Pt�1�#���c�/S��>^�@���o�q��=ia�������R��e���G�%�{P�n:��0l�J��c@��,y��:
u|��U��h��5������������%��f���Y���p��YS����������o���:�J�:��U��<���y"�)S�i�S�)�N�_�D�e�{�e����������~�A��N^Fl0,b*�c<"�[�X��)^M���}��A��8e#���J  ,���:tH[���4��~������;c��n��%����f��������}����_����z�j7/�}�m�u�l�TI��+�A��J�t�88�hg�i��|��dVL��\4	^c�z�z����z�j�E���;�I������x�oLg��"���|b(�	[J������~s� H8�}��DxA�#����-��J]90_�z���(U�i\Qe�.��C���3V�����O��8�

9xB0�6?��JN�G�,C��_���z;��T4���N�(�7r1���)U�����R��.�5�b&�L�����~(�Y��)���
�U���K�6��D�.�n���O���jP"e�E5�jJ��@�7~��T�Q�!:��RhG��xc��%������&��h#f�d��CQ^^R�g�����SZfv��GAD�����D���z�R���Uac�mc�����{'�lv�=�D���G�n\�R�LwnXz���.����vJ��f���bk�W�N;�=�I����1/���=3C��w�
q��)/�����k��^������V8hbF��Kl��[��w�T9�9�����bnK0/t`�*G7;;�JPDY4�_a��6i��:+�7�>
�Z����}��S�$�Q���M�v������l5\-'���2N�?�D.!�yh�,�V3�`�mH'��Uw��so�>=�ic� =��Xk��k5�����2���/f���]�1���8����������v�Qg��m�]�� ����p�a��<'����[,r���}~�/���z����W|_(f���{]�`yB����e��l]vv���cY&���;������WW���gu�!��Wo$�&���d"���
)�4����^_�",�x���U�s�fd{So�0]�G�J���������`�'%h��Xl>�d�]�������w\���4���x]���9�
)
8�����MJ���"iu^5{���n����9�����Au�����J=�;Q�td��L���*���(~����.��<]`�����#�w�X�����:��d`p�=�)&��'�Ee�S�)��x��&l+��w�����<h���,l�P�}l��V���!��B�7�9�5�Ei7���<9��+�	4����<�L���p��/	yo�xu�����u;�
,P�)���n���:��?��m��i�����������4. ���']@Lhdp|���k�n�;�����z��$��u��wbH�� ��^�
]~�v��I�}������|2�������b�������5r ���pL��1I�!�����u�B&�x^���C��M����I�e�@KNa9�����6?6���<Is#U_���Z�KAX@�:�;��`��CF_�N� gC6���P����I��x���U����M��<>��E���N�|�/�����f�\�BD5#.���"_Z��U���dx���}b�tR ���&y&��>�t�OC"~��X�;K1"��Y_���O��"��P~W������sV	j��p\�i`}������B\w�g�M��m�w��h6��$
D~�ZD�]q�C��;����
 Gr�ujl�R�X�n��m�(����-�0�nr>�0%1l^v{���~��}��Q���!�"lMg7�i'�����NU0s+�B���#�I��s�rbn�w20����^�i� V�ur�_H�y���HJ
���������f]��m����hqY�5�����W�p�����~���]^
���k��31'1�H���B����Y:d$�>��m�{5T H�v4��}�5��l"�g����1<�N
W��a�����o���W�~b/+w�!�t*w���?�+	�S�S�C�;���gh������vt7�����5l��]��������p�f�n/���������^����%�~fy�S^�Nf7s�5�(���J%��~����"�?pLI�]~k;��Mtyz��9&�����M3�e�r�(��}5�������x�X�	��M��M�mg�~]�"��QU�����������t�o��%�i9�7���4v{!%&qh���R0�J�O���N���f^�:x�n2��/�39�{�ttM�1���w��[;>
�U����O�������"���Y�u��Yl�9����j�(���E���&��8��q�LK�lv�����a.82E��pA��i�*����x���1C�w�A�5������Yv��&,�&h�T.������8�p�%�C���<�i�2���}���XN*�1����M���P��_��f?���nG����� S�L��wI���7L^I7��Camt���;z?�F�a�d`UB��%)s�7�x�]���5l�]<�We0��gkc/v�s��W�v����"���a?�Z\t�P=��t2�Dm����~4UH�������S��8;N���v�JS�99���s}�
|�/��e�s�����<��$F�>A���4_�2G;�uB�\������UEA��^�IxP�@M�������Z��t��t��H�(�:����o����t�����|@�{�g+�mBi�$���o��L��Kj>&�������	%U
N*��4	^s���E��+��t�dHYU���e�X���xgf���H��}L�H�K�>���<~�����(�+Z�h��F+De�������]"C��cA,���J� I�5������@�e~����������^=�)��}��^���D��`��?�����zT*����,(o�-R'����"z���O���[��v��6A�PT��X�2���k&X���VX&��Y����Y�����!I���)�!WL�27�T�&�*�
���i���Q�<
'���r�k��c�!���]%VI�ive`V�bzA����Le�J�\Rj��^�-j7�4���l\c>�S�ve����	� �������C`
��i���s`�u��4�@�XL�n��F�����E�,cT�{�q��Q�/���5m-�DY��BN�����^b�g�*��G�r���p���Uw$k�@���_pJ�vD�onU��`aN���<����-�0B�j?��@r�A���o���jR���@��\���	�:]0������l[��Ce�����Y$���eS�
�������-��|�o�,MS�+��rBS���EMA��
�����b�(D�fl��'9d���`RS��?�����8������Q�7����dW��T�$��#����G��������������B�����*$(�n�E���Of,����./fw�`��j�$��2������������������_�����d�e��7�x��������)s`t��SJ��H����i��G_l(;���%���a#z��z�}����tl�	='��
�@��s9�2�<S^BH\���'�Y�S2��;��p����o={���oX�c�Y�,h��y�]������O���rE��w�9�+�^gj�������D�V��;�
��j�[�&hKTo��]�=�y,��M��-w��j���l��A;��������!Q��8���%�<���{&�xJmg�B[l�m�RW�d��Ic?u��s��h���+5�����UD�+��|d:����b�B����i
K�%$���;���p2���af�!-)�Ox�0�W0%�XrZ�>���c�����S� 'fC}Z-���
'��{Ib6�\'��=O')5�,���&Y;Oh� �e��
����lD�(���S��������&�7	�O����S�c����q���B��*,��X�D���,�
�B��[����`Ky2e�����%��J��i�hax�EP����y��?�3�G����� ��E|<���w��cN�Mvn�#�����S!1u�v���3M��D�[<���r����g���,Y�@�Z-'�WQ����v�N���Y������^^2)��v�_w�6����{��zs��zy6��g�m �q�e8����%�Y��"EnG]�U�aI���
�,����x<V�)'���o�K�m]������R<��05I���0^�������E���i��Lg1kV��������^-xW�S��bW�!����b�6�JE��:]��t���k�"�����Tj���|zv��7I#h{������yL��@l]4;�$P��L���K���q�		O���P��:m��
l�]���Q�eG��V�6����d8?v�~
L4-�����u�-��B|y�%O9�i�ldr�]��$r��9o�2@��y\��[�L��)����lo�U�����`��4r��Y��'< z��j2�9��f�$����R��TDFX5�����"�]����?9����)+����sGE����5-B�S
�>w_G�.���p1��/��x~��.���F���h8������r�F^gez�k�-5�K
��7�.���e�E��kd�nK��	�� G��'a�S�^�.�{�AK��5�=j]�{����Y��+��XN���) �U5��c�3x1sz��JdhU1�����*�
&|�l�<7����+��V��;%qZlB�Z�D�Eh/:����L��H��v���*$�V�h���'��Yzz��Zi�U+%#�
B���>y��M\�,����2g�0�M��g9S��D����pK,[�� !hV/��^�$�����S�/d��U�wJ;�2���\����/]�g����/��8d����f^7���K>�
�p�`���\�T*	�n��53�&���,s�Lx�X���s�p�3S�
�l�W��,d�(�dk��F z+o����l7�&5�wi���C*��+�gDy=����0�m�y|�!������Y����c_�9B���7-q�lC�}E�#����n��KEDI���/{��+-�[2���(��6�D�tj�?T#�[��`+��0�@��
x*��F!C��@Q9�v['&��m��=B��Y���"��$�����������')�&���}-�Ofn(�*>�k[���V�^f���*��C��!L���V'@��E��/�=���������mJa��������m��35����?��<���U��K�5�h�A5Yf:8�1�1�Y����4�
?&}\4���5��=�C&��$�5iW,�F�f�Y�7Q���y������XW�R�������n�����
o�q�&lB���z����������1�IJsB���>F_��1���9���Wjl��~eBM!��g<�R���<�����8�����������J����{��7u�Sr��Q�LGy�����)�n!�o)�(F�t�w�h'hAOwJ"}���V$���$��cG�N�^�y��!e�4�x�M�~VN���Zlm���q���
��&�����V�)Sr�F�/�h:}����Sn U�R�P,D%2o[�0r��$��R�e������O�\C+�[bJ��)UO�gb�!�_���\�O�`���v[\��FO�*�Y7LUI�2/���6����h��l��������)��/����:e�~�
!u���=�d-����SH~~\	*������s< �UOd�8����k�.=o����"�����U:#
�>�,�y�����W�s��mW�Mp��\�4u�GJ3�j|��>[���B@��l^ ��N�lGQ����B����#��j{S�f� >0�E'��7�/O��������������_�vnZfk��%S[����.\}�D_��i������o&q��T�0���s4[����g��a��%�E�+?���Y�b��{�����{�V_�rs�2Q?�f����]]���
�n{:���a����y���+�F��9���>���srX���J�d�y�0N�y��E�����d�	=��������jG����on�%(�oD��4�c��s����pN���u�1�O���(0x���� Bo�~�u4_<d[��W������\���L|�n-AY�Q�HzE�81���0�n?2?]����d��XI�I<����}
���xQ�1��<M^�1�?�*�8	2�G8�ziRI1��x�a"�����d���D[��v��3�����dn�f�;8�R�0���W��
>�L��5O�e��Om�;6*�Bi��,15*�1�Y����q��+����zp�����'k��x����!�b��]��%GE����f����=�"�c���w�F��4�L�
��� �� .�=\��������z��Z���7	�_5��0�����f�Qg�d_v{�&�?�?}S�������3e�����^���.d���@&<�^{���y6�q�+1����&
)���v������&uk�������"!��C�ro��X�)T����;�d��o_��K��?�@hcjP9���R������~&j���;5�O2�3(��8�2�.�-0y
C�T�)[+���$�5�[4���e�3�D��A
/��4��,����f ��sD^���$!�N4������+�#��G���Y���d��NY)����K;�����J�K?w��\_x;�%��2�������`�H��7kV��s�LV����v��]�r	�}�sY��y@�,,~�������	J�����m_�1�������s���h��V��-�j���$�iAY�sD}N
h�1�W��,���:Q���7������-��l7K�M��5���j9/d������=�im������e!S����	�d���K���	HS�I*��~��������*F{���U5�v��
0W��^�3h�;H�]!qO�>����m�����d|�`�]�n�z6W1�eqn�U!���a]��
o�N$W��������M����+p
T*�(/�g��a?����s����!5��
�9U����L%)���i�T�UV��6c:�l�S���V����
�z�|d�
r����'�������!)�4����2�'�����E�������|@��3�eY-���hd��IB��S45�&�hC��I�Z!"z��������
�Q��j���Ia�-y���V�_ �Z;�:K������-�WdaR4�'+��4�T�.>��r���'F-C��	�z��Ww���|��U�~�f�U���y�p�d�H�1�ck�2���M�|���Q�C��X&O��c�\\u��F^5"]��	��dd�hI����,a�s��O�IB�n�1�3n�G� �;����K{������f5�?��-�wb(,67��\�)L�R��B���CM��t���t�t���o����M�b>H(I��l�\�c��el�la�ijt���j@�q'���
���K��^3w����7{�������h�C�O�D��O�7{ec�����Ed���*%
;��PF\����F>�Cf������tMQ>hR�
����:�a;�F*Y!�o�$���a+u��Eo�heB��t�����>^e�����l0�L�1<��W�%Ki�����b�yE�Y�����+���@�.~��P\���C:#��XL-����$c~OI��8�=a���(Eb�<MA*v��o�t��H�2�1�3��0�m�*"Z���]��U�������U����U��Y�����b��]��fo������X�zW2�����y�}��N0D�>��U)�kPptg���x2���==0L!	(1�&�:.�3$�F0�+�}<1�
lyO����8����RrFJ�M�C�2��)��E�!��
���3�r�����<���e�s�<��\�aW���"Z�J��xr;���;��3�}��D`T�W�����J91Yu�l[,��7���/	�.�}��B���]���yK�tZ�	�F�����@�&�C
����}7���y�%��Y�U���_b�r����� B�b��������V�Bj����[Y4�u����e������
��]�-�e\���A��*�{E��A���+��^I�N�nF���Ki%���%��VI��](�blK��b�&����Z��b@�����
��*q1&�"��0����+C�����\|0����{v3�����r�Or���XW���*E(Oo�u�~�Pl�G�<�������c��0���Ds���xB� ��
�?��h�rq[���~/��5N�h�9�O=8�� ��tF�,sx4���b42����{�iw//��u���xe�g�Z}����bj6)�A{1$��	�F�(��b��w���V�f�$�5����F��TAvD�b�(�])i���{���x�
�����#G�QX�����<M�SY	E0UY���Tb>I��
��]7�W�w�P��W�����#q��/Y�_?�')��S7�������}�'~��)�/���
�/���W�&S T�YW%
�����	[Z�������a6��>�������\&={R�q��E�#�~�On��c	�Z)�8z8`C�4���{rnn��<�]w��-5���I�5G��Z�X�&�3O�)h�e������y����������SFA8�&�b��:�7*�^�KN�����y��S,m)�p��`�<a�����}���So���L  ��P.S����>��r�qH��	��r��r���@�jLv�1[I��������
�al���~��}�A�hbl���Q�����7&�.L�tRp'5��4��.��U��h^\��^� ������,N[W�O#A����������~����Gs~�<���N��fs�`��F��@�n��R��w�_��`J�QJ/'^����@*@��@�h�
���x��^�������{�o�*���4����L���_�n������z��k����/�����������\FG���j�I����� o4H�y���^+f���g{UH���u�]�j0���I����O�&S����7�8����
����U�>Y_����S�$K�.T�������������F��1j2��4z������j~6Yo����l�[*�`A��S������:���������!m��-�]���lI�;���v����Df��0�������@^M�N�e#����JI��s/
a�N!�n�;k�G��[���w���0������t43!���S�s�����f�&+#�K���pk�)����������q���b��� _5#���P4+�j�y�(���v�A��K/U7tG����[���o�]������H��lw��a�����L���b��x��$�U�
)��������n�NR	�	LCq�f��������Sa��%>����1i��/��
������,�V�U���T�F�p����Cx']�rY����7,���-L�Y��E���^|��gs��	L\��RQp�L���W���u�d��m�������_���.�h,��8�D�6���I����2����R%��r�yP>~��7C*Q>U���*�g����|�/��ct3_F���
Mb4����d�F7O������*�*�3��`Md!H��{�K�4�xt��K������~��O��H43�uz��\�k���y�d����'�;e�>�2X/�xc��R�bB%�����
����21��R�����s(��``�F&H�Y�5�U����`F�uPe����$���/��MH�;r{���T�C�Q���zL������o�{P��Z{�u��f�j�Y	!����Pj����&]m
7r��l]N�|��]�x����e�����|�t����Aif/"I�%��h���t	Iz�+�V9����>��MH=U��J�mY���5/�p�<�d.Fw{����.��)B��=�����^����F�)���
���V�k�����@:]t_�[>rx}^^w���S���'����ytH�H�uQ��A:�:4���������<����9�'�<�.L�����L��C��Y���������@d{�3��
?f�3%~���5��s���?h)��Y�����25�N�'�s�
�(�,��[���'�H�5��^�f~-��ZL�f����\3}�����)�5UIk�h���,'��Hf<��M87C��������D��))���5��]a���8���\3�[+��R��=�#E#k�X�,=����%"}�������*)(���1j�F�X����Y-�������r���4c��P�(j��~�|F`��m�{U�hoU�Q(
����N�*r��������Q�P�#��g��U:��=��\h��b�'����3�m�Vr��Z�S!�vX�]J�_]!"��.���(���e�����bD��������@!��#���2�uL7}��v���x��'��	��J�)�����
,(�N��[7�����7�0�7��v�J�]���������&$fF���1}���0����[|� %�������r��������|�����U}6@|#n~ev��t��]p5SZ�V����|:M"��C@�V1#Y�!��}X�^��d�_6�H����Pyy�;��"D����H*�5HB�9�wh^�$���{>?�W�:	���U����{1)�\�������Le�����x��}���hLj{�W��%�1�����������[w��Z��g�
�4�qE�-bU|�i�M�D�U�k���1:�T����F1d�`1��z��~�����M�������M�/�
��"�������O�y']Bv{f�$Vr��E�^1����!*������!��$��	avhr��U�*:b{��������N$��=+�C����,+cV�J��	-T���!a��T�Z��)��p��_6�W�j�u��&�.��j��,"��e�������z�g���$�o��)��Y����������e%R�E���f���E��(�6�k	�Vk�1j��\@*�
{�r��nc<��t�k��+^|�l��J.���=�V�s!UR��j�1��Kh_���S�<`��4����h�A�6�P�����p����
F��/�P�O�)	{}"��&�_�"Z~
1��w���eL��lkbv��>��I�
-<)�\��((r����ja�c��;��oA���\.~-���N�5��v��$���Oo!����������t�x�gw�A�\C������2)�L������cM��
����y�f�%�1�.<�z�A��'������P�����Uy��u�Tgr;�����r1ej@($�x�	V���S��F_����|��Yc������/���.��r�y2�n�_,��L�����Kl��h-�R��4������CR,�R����}�(����:�T��A���Ge)Z�M�����]�iC�Y*ir�gD�C�=�+.�;��J�g�%�R��r/��'U3��8UU��8�^��8Iej�k�l������-��u�1��1K($S���4�t����p�M*�jB/������e*�	����9;���kN������)�k
�%�S��g1�H�p�W/E-��X��XR{�
��H��O��DV�6K0�U6�I�|�/j�����g�R#��
'�E���n+����`6��Z�w,)]��5K��6���K��w7���H�{vc#�E�\�$�bC�;{���T/*�c,T j'���&a�!)������S
(%!Tj�r`��	Q[�3���a8�*G�<���c���S��Q@G�������Et�8��� a5�5�8A�w�Ve �������|���u��s!��.�>�5�$eJ�;�ytI��E��|���<{���~6}��~v4�B��p�X�1�B</��6#��? Q�����O������������8$��!�d���V:�����	xF�y�Z�x=�3_EC0��~<�#���,p�}�f�,iZ�����l�~�e^���K���q�n��[0So�m���>����]%S/��lZ�sq���h6��8����@S}uE�Zo��5����>Ld��E8�����
mVD-`UE��A��
�p��'K�������77G�#b`��>sm�b/�[!��1S�m���F��Ny��pG�"i#
9����y:B���s?s���\8��'�#����Ka2Z�a6z6ir2<����]g������&��G_)=���3O)=�K�=�9DtM��a�3�Ls	�T
�u�z�
I���S��J���d�zJf
.�/�A���^��+�n1
u"vZ\��(X�Z=>�����V���va��P�N���\�H��$@W�/����i\�z$�,-�@f��>�B���
4Lxn�d�)no����n�"wT�@�wKbd��I�[u�t{�>�'V��Ty�w!�N*��p�J�~��E�|���L�/�~L'��(�'�������B�0�9��.�����#�����,�q/$8���P>�e�Q�X�U�3!(4h	e��$c�<)�K�����zD��	""}Hi@��ji'��Z9�@�t����+���E.��
����x#��B$�Y���w���h����2���a�(����'�v��|���uB��%Jd5�Fd��*�5��#?2���
����P�I�Kf.�����.����7�%q��Z-'�WPR��HQ��b�}2D���=����t��4�XP��z���g�E�������������u{�x{�hr(m+9�8�	�k�{���7���g��G��>��tn�����7�>�.�r��
a�����#��|~�����-��!h�}��a�u���/�"oxg+�$F��*R7l�@0��Q��=N��/J �Pfwg���������5��=%���%	��+��w�V�Ki�#��&U����7r�\��3��+{z�_$��������6@���x�o	��U!��O�Kj���G��6��$�7p=���:Y�7[C
Y�N���7����<�*�<z,�_�3�G�\������Dn%%������|���e�2�*c����\Oj��8Ek�V�1Lc��b����Q�)��K�F�i�{���o���K�}�'�c������5A����7���Cp0s?�6C���������3�26�f`)�z^��Z��[���y��}`(�����Z/�	&���{���.nf��1�������?���h8cS���~6�������$j>�a==����!�G��B����?bp��c�����������t�X�&�*���,�!�� �;{���$�Oq������[{�<v�����7.�]�����x��)F�.��7�[����x�L�53j�1'�<�WSG|���^���������!�^�Jc����y'��+I6�IE9���]���[S	V�l���#��z�N�N�@I=��IL,Vl����r1�����: ���$�����Z��8����*�vv�����rR>N+\�&��z�z�O��2���XN��zxx����I����n,�2� L�\���������J����e��z!V�!fV(%���Q�z�\�B�fFt����#5��[����E�/��@h��u�k��5��
�Q���������z���X9��a��:B�9@�i��o���E��
�P����T���wn�X��n�"t�sY������
��B�Y7"`c ��"u��{��U��x����L�	�X��wGE<LE���C�'8'��N��a�/��#����z����j>������H�b��!M�g�2�i<�d�(`�h�|{������q����:KZ>�����������Z��x�D��[6�(����S�^���0]�g���E�b{���PO�l)P�\���sO*������)e��� /NUo�&�q�����o�u.�����YQ����`�#�7��dTh��5yk���/�m�����+iiVYtf�i63�S
��}4��bH��D��xB�����X�]��<�G�8��<��T�����!�dn$�1*��n@���x ���z�h�����=���S�2�r��C�������� S7�gA�EH��%yR��x(ad��4��M���MZvr^�(iY^DM����	|������x�0���`NZ�O�Q��u5N�kA�J����'����$;�����6,�#wh�h:��,�(�.3M�����w2�u;%o*YSW�,Ry��,1��+7�j�)�����@L[��i�E@�	�������(7����L����#af��t�=������y)#��s���~����H+��#�"|+��=<>��F�Q��5������&�`��m�"���|�4e�Y�p�n�H��.��4sQ�&>��jo��cdg��CV�Jr���-��9.�9��TLK�"��Kx��t]3r����E�cE�>BP,
q�����p�HM���5<C+= ��#Y�~��_�Y5��Q�-c�D�m"^-"�U/��e��(8�����V�*�{��K[�����-*�������*%�z2�<@PH��Z��Z���L��������?���:bK�t^�f�:��4:2����	�Nl���R�L;�Qn�5yBg��{��)����z@sU��'�����h��f��3;� ��������:j]X�]N���s"9R��[��;D�|6����S�����~��*�G�p2[����:�8�9�R���9K���������i�k^�[�_7/Z�I0D���r6�L����j�[H3��5�;@-=l9WaH�(��/�dX�f_6.�C��[�T����=�u���|�2���j�����^
l�	��<H`��nH�M����Qi�����C����<��:�33��+��8L�h� OT����?P�U���[B��e��24�I��>���d�y�#{�U\���au�DfgP*�n���dxLS2���x�FS���%�*p�?���oo#���8��;y��9��^���w������_]2��4�!�?*7��x�&����cu>����l<�<��_.�={���p�W��RC6s�,�����;LW���9�j�n�QL>_F��p&n$�j���Q��V��
�$�n��#��S+�����p�>����U0��.i��E�$������@�xA����\
�������W�0^���}h��n�k���������)?�`���f�r2
���Hx
�C�/��J^u��b������j�|��H:������
��E�M�s{?���/O�
����|��!��c`��H�|F�<��!����t���xz���	�lN�[&�9�!s2�jF9
�=jJ��-�����P����(ic:�(�
G�D�,�H~��������	��jP�m"s2Z���SA�c�/�vY��Y���qK��@@I�e�j�Fw�1y�:��R��;V�ch6}z���4M�XL�$���M�t��~����	��fA�������D��w���|g���a`+�k
������#�r���6����s��|�!J�	]`������� #�=��d���LD�O������t���T����0.�+U;�F`�nI 9"�tO�r��@8T4�D���M�3=���<�������g����F;�����������sh�B_��>����t�p����U��D�)������p_f��5)��A`f�B��,a��wz�~x@?������2���$�G�~��^���Z�Kp=�z{��#q�����_��2g���M0�:g8�n��P�����Q����O���q��������d7vK�WC1��KO��rO����J)�l3�$�������@���%��
��d�D��ozx.�l��4>�T	'~<9��I��'UO���p��5����+EXy��7����k���&Zc+���G}����/�}��m��]��z��Vt}��1�����)}��VXZ P�W2����
�w��[G�jF	W1nFG+NIw���>n0A�rxc���-\�����E���b�4��hh��b0H^��y���U�U�O��R����RV�C��h��(��nZ���w�izu�t��_/�T�z�bIC�f��e�u�Yi�	j�(:�Id/~�xM��q4���yG��~��:��O�
��!<��
]]���� ��|�fl�I���O�x�	P����Rh>
�5(�k����������p�pz'��+���_�V���#��9'��X��x��2���b><�.�6[�7Y��NO�aB$C�}������{����$�Lo�l$3������S$t�:~yu�w/�A�4�c1M����^��
���V���z������u4_<�b,tU�2O*�Lk>B��kr�KF�lyB����:��"���e����p�[<
�O�K4��jgg��\AG���qP�����h4���)�G��Y[�L�+�����d�D��F+���t���X���|J�c%�%B����S%@�h��r������h�E3<`����
��(�[��~��p���T�qn&�i4/����t>:��O���hu��1xg�w���0��u|3���Q�~A���_��c*�[�<���?0���i���VA�p���n�6>�#DbT���A?��sz
�_7���D!��I3�� ��$��}��
X�d��a6����0����'�at>E�����H�MDz�&xH��T�+����h���!���yP>��uv|Jc����e�n����	�HG�����m�TRfc����������o�LJ����Ux	-Jx���t~O��|�#j�)d��9��?����<f9���T�P�+�����F�>)
#60Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#59)
Re: Making tab-complete.c easier to maintain

On Mon, Dec 14, 2015 at 8:10 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

I've also add (very) primitive negative pattern support and used it in
5 places. Improvement? Examples:

/* ALTER TABLE xxx RENAME yyy */
-       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAny) &&
-                        !TailMatches1("CONSTRAINT|TO"))
+       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME",
"!CONSTRAINT|TO"))
COMPLETE_WITH_CONST("TO");
/* If we have CLUSTER <sth>, then add "USING" */
-       else if (TailMatches2("CLUSTER", MatchAny) &&
!TailMatches1("VERBOSE|ON"))
+       else if (TailMatches2("CLUSTER", "!VERBOSE|ON"))
COMPLETE_WITH_CONST("USING");
+       /* Handle negated patterns. */
+       if (*pattern == '!')
+               return !word_matches(pattern + 1, word);

Yeah, I guess that's an improvement for those cases, and there is no
immediate need for a per-keyword NOT operator in our cases to allow
things of the type (foo1 OR NOT foo2). Still, in the case of this
patch !foo1|foo2 actually means (NOT foo1 AND NOT foo2). It does not
seem that much intuitive. Reading straight this expression it seems
that !foo1|foo2 means actually (NOT foo1 OR foo2) because the lack of
parenthesis. Thoughts?
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Michael Paquier (#60)
Re: Making tab-complete.c easier to maintain

Hello.

Ok, I withdraw the minilang solution and I'll go on Thomas's way,
which is still good to have.

At Mon, 14 Dec 2015 14:13:02 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqSggjPA8U1WV7ivW44xzboC8pci_Etmffr+ZEzxSX_ahA@mail.gmail.com>

On Mon, Dec 14, 2015 at 8:10 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

I've also add (very) primitive negative pattern support and used it in
5 places. Improvement? Examples:

/* ALTER TABLE xxx RENAME yyy */
-       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAny) &&
-                        !TailMatches1("CONSTRAINT|TO"))
+       else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME",
"!CONSTRAINT|TO"))
COMPLETE_WITH_CONST("TO");
/* If we have CLUSTER <sth>, then add "USING" */
-       else if (TailMatches2("CLUSTER", MatchAny) &&
!TailMatches1("VERBOSE|ON"))
+       else if (TailMatches2("CLUSTER", "!VERBOSE|ON"))
COMPLETE_WITH_CONST("USING");
+       /* Handle negated patterns. */
+       if (*pattern == '!')
+               return !word_matches(pattern + 1, word);

Yeah, I guess that's an improvement for those cases, and there is no
immediate need for a per-keyword NOT operator in our cases to allow
things of the type (foo1 OR NOT foo2). Still, in the case of this
patch !foo1|foo2 actually means (NOT foo1 AND NOT foo2). It does not
seem that much intuitive. Reading straight this expression it seems
that !foo1|foo2 means actually (NOT foo1 OR foo2) because the lack of
parenthesis. Thoughts?

I used just the same expression as Thomas in my patch since it
was enough intuitive in this context in my view. The expression
"(!FOO1)|FOO2" is a nonsence in the context of tab-completion and
won't be used in future. But it is true that "!(FOO|BAR|BAZ)" is
clearer than without the parenthesis.

We could use other characters as the operator, but it also might
make it a bit difficalt to read the meaning.

"~FOO|BAR|BAZ", "-FOO|BAR|BAZ"

"TailMatches2("CLUSTER", NEG "VERBOSE|ON")" is mere a syntax
sugar but reduces the uneasiness. But rather longer than adding
parenthesis.

As the result, I vote for "!(FOO|BAR|BAZ)", then "-FOO|BAR|BAZ"
for now.

Addition to that, I feel that successive "MatchAny"s are a bit
bothersome.

TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAny)) &&
!TailMatches1("IS")

Is MachAny<n> acceptable? On concern is the two n's
(TailMatches<n> and MatchAny<n>) looks a bit confising.

TailMatches4("COMMENT", "ON", MatchAny3, "!IS")

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Michael Paquier
michael.paquier@gmail.com
In reply to: Kyotaro HORIGUCHI (#61)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 17, 2015 at 3:06 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

At Mon, 14 Dec 2015 14:13:02 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqSggjPA8U1WV7ivW44xzboC8pci_Etmffr+ZEzxSX_ahA@mail.gmail.com>

On Mon, Dec 14, 2015 at 8:10 AM, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
Yeah, I guess that's an improvement for those cases, and there is no
immediate need for a per-keyword NOT operator in our cases to allow
things of the type (foo1 OR NOT foo2). Still, in the case of this
patch !foo1|foo2 actually means (NOT foo1 AND NOT foo2). It does not
seem that much intuitive. Reading straight this expression it seems
that !foo1|foo2 means actually (NOT foo1 OR foo2) because the lack of
parenthesis. Thoughts?

I used just the same expression as Thomas in my patch since it
was enough intuitive in this context in my view. The expression
"(!FOO1)|FOO2" is a nonsence in the context of tab-completion and
won't be used in future. But it is true that "!(FOO|BAR|BAZ)" is
clearer than without the parenthesis.

Yeah that's my whole point. If we decide to support that I think that
we had better go all the way through it, with stuff like:
- & for AND operator
- Support of parenthesis to prioritize expressions
- ! for NOT operator
- | for OR OPERATOR
But honestly at this stage this would just benefit 5 places (citing
Thomas' quote), hence let's just go to the most simple approach which
is the one without all this fancy operator stuff... That will be less
bug prone, and still benefits more than 95% of the tab completion code
path. At least I think that's the most realistic thing to do if we
want to get something for this commit fest. If someone wants those
operators, well I guess that he could add them afterwards.. Thomas,
what do you think?

Addition to that, I feel that successive "MatchAny"s are a bit
bothersome.

TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAny)) &&
!TailMatches1("IS")

Is MachAny<n> acceptable? On concern is the two n's
(TailMatches<n> and MatchAny<n>) looks a bit confising.

TailMatches4("COMMENT", "ON", MatchAny3, "!IS")

Ah, OK, so you would want to be able to have an inner list, MatchAnyN
meaning actually a list of MatchAny items repeated N times. I am not
sure if that's worth it.. I would just keep it simple, and we are just
discussing about a couple of places only that would benefit from that.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Michael Paquier (#62)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 17, 2015 at 7:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Thu, Dec 17, 2015 at 3:06 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:

At Mon, 14 Dec 2015 14:13:02 +0900, Michael Paquier <michael.paquier@gmail.com> wrote in <CAB7nPqSggjPA8U1WV7ivW44xzboC8pci_Etmffr+ZEzxSX_ahA@mail.gmail.com>

On Mon, Dec 14, 2015 at 8:10 AM, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
Yeah, I guess that's an improvement for those cases, and there is no
immediate need for a per-keyword NOT operator in our cases to allow
things of the type (foo1 OR NOT foo2). Still, in the case of this
patch !foo1|foo2 actually means (NOT foo1 AND NOT foo2). It does not
seem that much intuitive. Reading straight this expression it seems
that !foo1|foo2 means actually (NOT foo1 OR foo2) because the lack of
parenthesis. Thoughts?

I used just the same expression as Thomas in my patch since it
was enough intuitive in this context in my view. The expression
"(!FOO1)|FOO2" is a nonsence in the context of tab-completion and
won't be used in future. But it is true that "!(FOO|BAR|BAZ)" is
clearer than without the parenthesis.

Yeah that's my whole point. If we decide to support that I think that
we had better go all the way through it, with stuff like:
- & for AND operator
- Support of parenthesis to prioritize expressions
- ! for NOT operator
- | for OR OPERATOR
But honestly at this stage this would just benefit 5 places (citing
Thomas' quote), hence let's just go to the most simple approach which
is the one without all this fancy operator stuff... That will be less
bug prone, and still benefits more than 95% of the tab completion code
path. At least I think that's the most realistic thing to do if we
want to get something for this commit fest. If someone wants those
operators, well I guess that he could add them afterwards.. Thomas,
what do you think?

I agree with both of you that using "!" without parentheses is not
ideal. I also don't think it's worth trying to make a clever language
here: in future it will be a worthy and difficult project to do
something far cleverer based on the productions of the real grammar as
several people have said. (Presumably with some extra annotations to
enable/disable productions and mark out points where database object
names are looked up?). In the meantime, I think we should just do the
simplest thing that will replace the repetitive strcasecmp-based code
with something readable/writable, and avoid the
fully-general-pattern-language rabbit hole.

Kyotaro's suggestion of using a macro NEG x to avoid complicating the
string constants seems good to me. But perhaps like this?

TailMatches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS"))

See attached patch which does it that way.

Addition to that, I feel that successive "MatchAny"s are a bit
bothersome.

TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAny)) &&
!TailMatches1("IS")

Is MachAny<n> acceptable? On concern is the two n's
(TailMatches<n> and MatchAny<n>) looks a bit confising.

TailMatches4("COMMENT", "ON", MatchAny3, "!IS")

Ah, OK, so you would want to be able to have an inner list, MatchAnyN
meaning actually a list of MatchAny items repeated N times. I am not
sure if that's worth it.. I would just keep it simple, and we are just
discussing about a couple of places only that would benefit from that.

+1 for simple.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

tab-complete-macrology-v11.patch.gzapplication/x-gzip; name=tab-complete-macrology-v11.patch.gzDownload
#64Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#63)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 17, 2015 at 6:04 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

On Thu, Dec 17, 2015 at 7:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Kyotaro's suggestion of using a macro NEG x to avoid complicating the
string constants seems good to me. But perhaps like this?

TailMatches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS"))

See attached patch which does it that way.

Fine for me.

Addition to that, I feel that successive "MatchAny"s are a bit
bothersome.

TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAny)) &&
!TailMatches1("IS")

Is MachAny<n> acceptable? On concern is the two n's
(TailMatches<n> and MatchAny<n>) looks a bit confising.

TailMatches4("COMMENT", "ON", MatchAny3, "!IS")

Ah, OK, so you would want to be able to have an inner list, MatchAnyN
meaning actually a list of MatchAny items repeated N times. I am not
sure if that's worth it.. I would just keep it simple, and we are just
discussing about a couple of places only that would benefit from that.

+1 for simple.

+#define CompleteWithList10(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)    \
+do { \
+       static const char *const list[] = { s1, s2, s3, s4, s5, s6,
s7, s8, s9, s10, NULL }; \
+       completion_charpp = list; \
+       completion_case_sensitive = false; \
+       matches = completion_matches(text, complete_from_list); \
+} while (0)
Actually we are not using this one. I am fine if this is kept, just
worth noting.

OK, I am marking that as ready for committer. Let's see what happens next.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#64)
Re: Making tab-complete.c easier to maintain

Michael Paquier <michael.paquier@gmail.com> writes:

OK, I am marking that as ready for committer. Let's see what happens next.

I'll pick this up, as penance for not having done much in this commitfest.
I think it's important to get it pushed quickly so that Thomas doesn't
have to keep tracking unrelated changes in tab-complete.c.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thomas Munro (#63)
Re: Making tab-complete.c easier to maintain

Thomas Munro <thomas.munro@enterprisedb.com> writes:

[ tab-complete-macrology-v11.patch.gz ]

A couple of stylistic reactions after looking through the patch for the
first time in a long time:

1. It seems inconsistent that all the new macros are named in CamelCase
style, whereas there is still plenty of usage of the existing macros like
COMPLETE_WITH_LIST. It looks pretty jarring IMO. I think we should
either rename the new macros back to all-upper-case style, or rename the
existing macros in CamelCase style.

I slightly favor the latter option; we're already pretty much breaking any
hope of tab-complete fixes applying backwards over this patch, so changing
the code even more doesn't seem like a problem. Either way, it's a quick
search-and-replace. Thoughts?

2. Why does MatchAnyExcept use "'" as the inversion flag, rather than
say "!" or "~" ? Seems pretty random.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#66)
Re: Making tab-complete.c easier to maintain

On Sat, Dec 19, 2015 at 5:42 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Thomas Munro <thomas.munro@enterprisedb.com> writes:

[ tab-complete-macrology-v11.patch.gz ]

A couple of stylistic reactions after looking through the patch for the
first time in a long time:

1. It seems inconsistent that all the new macros are named in CamelCase
style, whereas there is still plenty of usage of the existing macros like
COMPLETE_WITH_LIST. It looks pretty jarring IMO. I think we should
either rename the new macros back to all-upper-case style, or rename the
existing macros in CamelCase style.

I slightly favor the latter option; we're already pretty much breaking any
hope of tab-complete fixes applying backwards over this patch, so changing
the code even more doesn't seem like a problem. Either way, it's a quick
search-and-replace. Thoughts?

Both would be fine, honestly. Now if we look at the current code there
are already a lot of macros IN_UPPER_CASE, so it would make more sense
on the contrary to have MATCHES_N and MATCHES_EXCEPT?

2. Why does MatchAnyExcept use "'" as the inversion flag, rather than
say "!" or "~" ? Seems pretty random.

Actually, "'" is not that much a good idea, no? There could be single
quotes in queries so there is a risk of interfering with the
completion... What do you think would be good candidates? "?", "!",
"#" or "&"?
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#67)
Re: Making tab-complete.c easier to maintain

Michael Paquier <michael.paquier@gmail.com> writes:

On Sat, Dec 19, 2015 at 5:42 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

2. Why does MatchAnyExcept use "'" as the inversion flag, rather than
say "!" or "~" ? Seems pretty random.

Actually, "'" is not that much a good idea, no? There could be single
quotes in queries so there is a risk of interfering with the
completion... What do you think would be good candidates? "?", "!",
"#" or "&"?

We don't care whether the character appears in queries; it only matters
that it not be something we'd need to use in patterns. My inclination
is to use "!".

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#67)
Re: Making tab-complete.c easier to maintain

Michael Paquier <michael.paquier@gmail.com> writes:

On Sat, Dec 19, 2015 at 5:42 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

1. It seems inconsistent that all the new macros are named in CamelCase
style, whereas there is still plenty of usage of the existing macros like
COMPLETE_WITH_LIST. It looks pretty jarring IMO. I think we should
either rename the new macros back to all-upper-case style, or rename the
existing macros in CamelCase style.

I slightly favor the latter option; we're already pretty much breaking any
hope of tab-complete fixes applying backwards over this patch, so changing
the code even more doesn't seem like a problem. Either way, it's a quick
search-and-replace. Thoughts?

Both would be fine, honestly. Now if we look at the current code there
are already a lot of macros IN_UPPER_CASE, so it would make more sense
on the contrary to have MATCHES_N and MATCHES_EXCEPT?

After some contemplation I decided that what was bugging me was mainly
the inconsistency of having some completion actions in camelcase and
immediately adjacent actions in all-upper-case. Making the test macros
camelcase isn't such a problem as long as they all look similar, and
I think it's more readable anyway. So I changed CompleteWithListN to
COMPLETE_WITH_LISTN and otherwise left the naming alone.

I've committed this now with a number of changes, many of them just
stylistic. Notable is that I rewrote word_matches to rely on
pg_strncasecmp rather than using toupper/tolower directly, so as to avoid
any possible change in semantics. (The code as submitted was flat wrong
anyway, since it wasn't being careful about passing only unsigned chars to
those functions.) I also got rid of its inconsistent treatment of empty
strings, in favor of not ever calling word_matches() on nonexistent words
in the first place. That just requires testing previous_words_count in
the TailMatches macros. I think it'd now be possible for
get_previous_words to skip generating empty strings for word positions
before the start of line, but have not experimented.

I found a couple more small errors in the converted match rules too,
but I have to admit my eyes started to glaze over while looking through
them. It's possible there are some remaining errors there. On the
other hand, it's at least as likely that we've gotten rid of some bugs.

There's a good deal more that could be done here:

1. I think it would be a good idea to convert the matching rules for
backslash commands too. To do that, we'd need to provide a case-sensitive
equivalent to word_match and the matching macros. I think we'd also have
to extend word_match to allow a trailing wildcard character, maybe "*".

2. I believe that a very large fraction of the TailMatches() rules really
ought to be Matches(), ie, they should not consider matches that don't
start at the start of the line. And there's another bunch that could
be Matches() if the author hadn't been unaccountably lazy about checking
all words of the expected command. If we converted as much as we could
that way, it would make psql_completion faster because many inapplicable
rules could be discarded after a single integer comparison on
previous_words_count, and it would greatly reduce the risk of inapplicable
matches. We can't do that for rules meant to apply to DML statements,
since they can be buried in WITH, EXPLAIN, etc ... but an awful lot of
the DDL rules could be changed.

3. The HeadMatches macros are pretty iffy because they can only look back
nine words. I'm tempted to redesign get_previous_words so it just
tokenizes the whole line rather than having an arbitrary limitation.
(For that matter, it's long overdue for it to be able to deal with
multiline input...)

I might go look at #3, but I can't currently summon the energy to tackle
#1 or #2 --- any volunteers?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#69)
Re: Making tab-complete.c easier to maintain

On Sun, Dec 20, 2015 at 6:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

1. I think it would be a good idea to convert the matching rules for
backslash commands too. To do that, we'd need to provide a case-sensitive
equivalent to word_match and the matching macros. I think we'd also have
to extend word_match to allow a trailing wildcard character, maybe "*".

2. I believe that a very large fraction of the TailMatches() rules really
ought to be Matches(), ie, they should not consider matches that don't
start at the start of the line. And there's another bunch that could
be Matches() if the author hadn't been unaccountably lazy about checking
all words of the expected command. If we converted as much as we could
that way, it would make psql_completion faster because many inapplicable
rules could be discarded after a single integer comparison on
previous_words_count, and it would greatly reduce the risk of inapplicable
matches. We can't do that for rules meant to apply to DML statements,
since they can be buried in WITH, EXPLAIN, etc ... but an awful lot of
the DDL rules could be changed.

3. The HeadMatches macros are pretty iffy because they can only look back
nine words. I'm tempted to redesign get_previous_words so it just
tokenizes the whole line rather than having an arbitrary limitation.
(For that matter, it's long overdue for it to be able to deal with
multiline input...)

I might go look at #3, but I can't currently summon the energy to tackle
#1 or #2 --- any volunteers?

I could have a look at both of them and submit patch for next CF, both
things do not seem that much complicated.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#69)
1 attachment(s)
Re: Making tab-complete.c easier to maintain

I wrote:

3. The HeadMatches macros are pretty iffy because they can only look back
nine words. I'm tempted to redesign get_previous_words so it just
tokenizes the whole line rather than having an arbitrary limitation.
(For that matter, it's long overdue for it to be able to deal with
multiline input...)

I poked into this and found that it's really not hard, if you don't mind
still another global variable associated with tab-completion. See
attached patch.

The main objection to this change might be speed, but I experimented with
the longest query in information_schema.sql (CREATE VIEW usage_privileges,
162 lines) and found that pressing tab at the end of that seemed to take
well under a millisecond on my workstation. So I think it's probably
insignificant, especially compared to any of the code paths that send a
query to the server for tab completion.

regards, tom lane

Attachments:

multiline-tab-completion.patchtext/x-diff; charset=us-ascii; name=multiline-tab-completion.patchDownload
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index 2bc065a..ccd9a3e 100644
*** a/src/bin/psql/input.c
--- b/src/bin/psql/input.c
*************** static void finishInput(void);
*** 53,64 ****
   * gets_interactive()
   *
   * Gets a line of interactive input, using readline if desired.
   * The result is a malloc'd string.
   *
   * Caller *must* have set up sigint_interrupt_jmp before calling.
   */
  char *
! gets_interactive(const char *prompt)
  {
  #ifdef USE_READLINE
  	if (useReadline)
--- 53,69 ----
   * gets_interactive()
   *
   * Gets a line of interactive input, using readline if desired.
+  *
+  * prompt: the prompt string to be used
+  * query_buf: buffer containing lines already read in the current command
+  * (query_buf is not modified here, but may be consulted for tab completion)
+  *
   * The result is a malloc'd string.
   *
   * Caller *must* have set up sigint_interrupt_jmp before calling.
   */
  char *
! gets_interactive(const char *prompt, PQExpBuffer query_buf)
  {
  #ifdef USE_READLINE
  	if (useReadline)
*************** gets_interactive(const char *prompt)
*** 76,81 ****
--- 81,89 ----
  		rl_reset_screen_size();
  #endif
  
+ 		/* Make current query_buf available to tab completion callback */
+ 		tab_completion_query_buf = query_buf;
+ 
  		/* Enable SIGINT to longjmp to sigint_interrupt_jmp */
  		sigint_interrupt_enabled = true;
  
*************** gets_interactive(const char *prompt)
*** 85,90 ****
--- 93,101 ----
  		/* Disable SIGINT again */
  		sigint_interrupt_enabled = false;
  
+ 		/* Pure neatnik-ism */
+ 		tab_completion_query_buf = NULL;
+ 
  		return result;
  	}
  #endif
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index abd7012..c9d30b9 100644
*** a/src/bin/psql/input.h
--- b/src/bin/psql/input.h
***************
*** 38,44 ****
  #include "pqexpbuffer.h"
  
  
! char	   *gets_interactive(const char *prompt);
  char	   *gets_fromFile(FILE *source);
  
  void		initializeInput(int flags);
--- 38,44 ----
  #include "pqexpbuffer.h"
  
  
! char	   *gets_interactive(const char *prompt, PQExpBuffer query_buf);
  char	   *gets_fromFile(FILE *source);
  
  void		initializeInput(int flags);
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index b6cef94..cb86457 100644
*** a/src/bin/psql/mainloop.c
--- b/src/bin/psql/mainloop.c
*************** MainLoop(FILE *source)
*** 133,139 ****
  			/* May need to reset prompt, eg after \r command */
  			if (query_buf->len == 0)
  				prompt_status = PROMPT_READY;
! 			line = gets_interactive(get_prompt(prompt_status));
  		}
  		else
  		{
--- 133,139 ----
  			/* May need to reset prompt, eg after \r command */
  			if (query_buf->len == 0)
  				prompt_status = PROMPT_READY;
! 			line = gets_interactive(get_prompt(prompt_status), query_buf);
  		}
  		else
  		{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 731df2a..fec7c38 100644
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
*************** extern char *filename_completion_functio
*** 70,75 ****
--- 70,82 ----
  #define WORD_BREAKS		"\t\n@$><=;|&{() "
  
  /*
+  * Since readline doesn't let us pass any state through to the tab completion
+  * callback, we have to use this global variable to let get_previous_words()
+  * get at the previous lines of the current command.  Ick.
+  */
+ PQExpBuffer tab_completion_query_buf = NULL;
+ 
+ /*
   * This struct is used to define "schema queries", which are custom-built
   * to obtain possibly-schema-qualified names of database objects.  There is
   * enough similarity in the structure that we don't want to repeat it each
*************** static char *pg_strdup_keyword_case(cons
*** 923,929 ****
  static char *escape_string(const char *text);
  static PGresult *exec_query(const char *query);
  
! static int	get_previous_words(int point, char **previous_words, int nwords);
  
  static char *get_guctype(const char *varname);
  
--- 930,936 ----
  static char *escape_string(const char *text);
  static PGresult *exec_query(const char *query);
  
! static char **get_previous_words(int point, char **buffer, int *nwords);
  
  static char *get_guctype(const char *varname);
  
*************** psql_completion(const char *text, int st
*** 1027,1039 ****
  	/* This is the variable we'll return. */
  	char	  **matches = NULL;
  
! 	/* This array will contain some scannage of the input line. */
! 	char	   *previous_words[9];
  
  	/* The number of words found on the input line. */
  	int			previous_words_count;
  
! 	/* For compactness, we use these macros to reference previous_words[]. */
  #define prev_wd   (previous_words[0])
  #define prev2_wd  (previous_words[1])
  #define prev3_wd  (previous_words[2])
--- 1034,1055 ----
  	/* This is the variable we'll return. */
  	char	  **matches = NULL;
  
! 	/* Workspace for parsed words. */
! 	char	   *words_buffer;
! 
! 	/* This array will contain pointers to parsed words. */
! 	char	  **previous_words;
  
  	/* The number of words found on the input line. */
  	int			previous_words_count;
  
! 	/*
! 	 * For compactness, we use these macros to reference previous_words[].
! 	 * Caution: do not access a previous_words[] entry without having checked
! 	 * previous_words_count to be sure it's valid.  In most cases below, that
! 	 * check is implicit in a TailMatches() or similar macro, but in some
! 	 * places we have to check it explicitly.
! 	 */
  #define prev_wd   (previous_words[0])
  #define prev2_wd  (previous_words[1])
  #define prev3_wd  (previous_words[2])
*************** psql_completion(const char *text, int st
*** 1133,1141 ****
  
  	/*
  	 * Macros for matching N words at the start of the line, regardless of
! 	 * what is after them.  These are pretty broken because they can only look
! 	 * back lengthof(previous_words) words, but we'll worry about improving
! 	 * their implementation some other day.
  	 */
  #define HeadMatches1(p1) \
  	(previous_words_count >= 1 && \
--- 1149,1155 ----
  
  	/*
  	 * Macros for matching N words at the start of the line, regardless of
! 	 * what is after them.
  	 */
  #define HeadMatches1(p1) \
  	(previous_words_count >= 1 && \
*************** psql_completion(const char *text, int st
*** 1194,1206 ****
  	completion_info_charp2 = NULL;
  
  	/*
! 	 * Scan the input line before our current position for the last few words.
  	 * According to those we'll make some smart decisions on what the user is
  	 * probably intending to type.
  	 */
! 	previous_words_count = get_previous_words(start,
! 											  previous_words,
! 											  lengthof(previous_words));
  
  	/* If current word is a backslash command, offer completions for that */
  	if (text[0] == '\\')
--- 1208,1220 ----
  	completion_info_charp2 = NULL;
  
  	/*
! 	 * Scan the input line to extract the words before our current position.
  	 * According to those we'll make some smart decisions on what the user is
  	 * probably intending to type.
  	 */
! 	previous_words = get_previous_words(start,
! 										&words_buffer,
! 										&previous_words_count);
  
  	/* If current word is a backslash command, offer completions for that */
  	if (text[0] == '\\')
*************** psql_completion(const char *text, int st
*** 1692,1698 ****
  
  		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
  	}
! 	else if (TailMatches4("REPLICA", "IDENTITY", "USING", "INDEX"))
  	{
  		completion_info_charp = prev5_wd;
  		COMPLETE_WITH_QUERY(Query_for_index_of_table);
--- 1706,1712 ----
  
  		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
  	}
! 	else if (TailMatches5(MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
  	{
  		completion_info_charp = prev5_wd;
  		COMPLETE_WITH_QUERY(Query_for_index_of_table);
*************** psql_completion(const char *text, int st
*** 2806,2812 ****
  		if (!recognized_connection_string(text))
  			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
  	}
! 	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
  	{
  		if (!recognized_connection_string(prev_wd))
  			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
--- 2820,2828 ----
  		if (!recognized_connection_string(text))
  			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
  	}
! 	else if (previous_words_count >= 2 &&
! 			 (strcmp(prev2_wd, "\\connect") == 0 ||
! 			  strcmp(prev2_wd, "\\c") == 0))
  	{
  		if (!recognized_connection_string(prev_wd))
  			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
*************** psql_completion(const char *text, int st
*** 2891,2897 ****
  
  		COMPLETE_WITH_LIST_CS(my_list);
  	}
! 	else if (strcmp(prev2_wd, "\\pset") == 0)
  	{
  		if (strcmp(prev_wd, "format") == 0)
  		{
--- 2907,2914 ----
  
  		COMPLETE_WITH_LIST_CS(my_list);
  	}
! 	else if (previous_words_count >= 2 &&
! 			 strcmp(prev2_wd, "\\pset") == 0)
  	{
  		if (strcmp(prev_wd, "format") == 0)
  		{
*************** psql_completion(const char *text, int st
*** 2927,2933 ****
  	{
  		matches = complete_from_variables(text, "", "", false);
  	}
! 	else if (strcmp(prev2_wd, "\\set") == 0)
  	{
  		static const char *const boolean_value_list[] =
  		{"on", "off", NULL};
--- 2944,2951 ----
  	{
  		matches = complete_from_variables(text, "", "", false);
  	}
! 	else if (previous_words_count >= 2 &&
! 			 strcmp(prev2_wd, "\\set") == 0)
  	{
  		static const char *const boolean_value_list[] =
  		{"on", "off", NULL};
*************** psql_completion(const char *text, int st
*** 3048,3059 ****
  	}
  
  	/* free storage */
! 	{
! 		int			i;
! 
! 		for (i = 0; i < lengthof(previous_words); i++)
! 			free(previous_words[i]);
! 	}
  
  	/* Return our Grand List O' Matches */
  	return matches;
--- 3066,3073 ----
  	}
  
  	/* free storage */
! 	free(previous_words);
! 	free(words_buffer);
  
  	/* Return our Grand List O' Matches */
  	return matches;
*************** exec_query(const char *query)
*** 3632,3661 ****
  
  
  /*
!  * Return the nwords word(s) before point.  Words are returned right to left,
!  * that is, previous_words[0] gets the last word before point.
!  * If we run out of words, remaining array elements are set to empty strings.
!  * Each array element is filled with a malloc'd string.
!  * Returns the number of words that were actually found (up to nwords).
   */
! static int
! get_previous_words(int point, char **previous_words, int nwords)
  {
! 	const char *buf = rl_line_buffer;	/* alias */
  	int			words_found = 0;
  	int			i;
  
! 	/* first we look for a non-word char before the current point */
  	for (i = point - 1; i >= 0; i--)
  		if (strchr(WORD_BREAKS, buf[i]))
  			break;
  	point = i;
  
! 	while (nwords-- > 0)
  	{
  		int			start,
  					end;
! 		char	   *s;
  
  		/* now find the first non-space which then constitutes the end */
  		end = -1;
--- 3646,3725 ----
  
  
  /*
!  * Parse all the word(s) before point.
!  *
!  * Returns a malloc'd array of character pointers that point into the malloc'd
!  * data array returned to *buffer; caller must free() both of these when done.
!  * *nwords receives the number of words found, ie, the valid length of the
!  * return array.
!  *
!  * Words are returned right to left, that is, previous_words[0] gets the last
!  * word before point, previous_words[1] the next-to-last, etc.
   */
! static char **
! get_previous_words(int point, char **buffer, int *nwords)
  {
! 	char	  **previous_words;
! 	char	   *buf;
! 	int			buflen;
  	int			words_found = 0;
  	int			i;
  
! 	/*
! 	 * Construct a writable buffer including both preceding and current lines
! 	 * of the query, up to "point" which is where the currently completable
! 	 * word begins.  Because our definition of "word" is such that a non-word
! 	 * character must end each word, we can use this buffer to return the word
! 	 * data as-is, by placing a '\0' after each word.
! 	 */
! 	buflen = point + 1;
! 	if (tab_completion_query_buf)
! 		buflen += tab_completion_query_buf->len + 1;
! 	*buffer = buf = pg_malloc(buflen);
! 	i = 0;
! 	if (tab_completion_query_buf)
! 	{
! 		memcpy(buf, tab_completion_query_buf->data,
! 			   tab_completion_query_buf->len);
! 		i += tab_completion_query_buf->len;
! 		buf[i++] = '\n';
! 	}
! 	memcpy(buf + i, rl_line_buffer, point);
! 	i += point;
! 	buf[i] = '\0';
! 
! 	/* Readjust point to reference appropriate offset in buf */
! 	point = i;
! 
! 	/*
! 	 * Allocate array of word start points.  There can be at most length/2 + 1
! 	 * words in the buffer.
! 	 */
! 	previous_words = (char **) pg_malloc((point / 2 + 1) * sizeof(char *));
! 
! 	/*
! 	 * First we look for a non-word char before the current point.  (This is
! 	 * probably useless, if readline is on the same page as we are about what
! 	 * is a word, but if so it's cheap.)
! 	 */
  	for (i = point - 1; i >= 0; i--)
+ 	{
  		if (strchr(WORD_BREAKS, buf[i]))
  			break;
+ 	}
  	point = i;
  
! 	/*
! 	 * Now parse words, working backwards, until we hit start of line.  The
! 	 * backwards scan has some interesting but intentional properties
! 	 * concerning parenthesis handling.
! 	 */
! 	while (point >= 0)
  	{
  		int			start,
  					end;
! 		bool		inquotes = false;
! 		int			parentheses = 0;
  
  		/* now find the first non-space which then constitutes the end */
  		end = -1;
*************** get_previous_words(int point, char **pre
*** 3667,3725 ****
  				break;
  			}
  		}
  
  		/*
! 		 * If no end found we return an empty string, because there is no word
! 		 * before the point
  		 */
! 		if (end < 0)
! 		{
! 			point = end;
! 			s = pg_strdup("");
! 		}
! 		else
  		{
! 			/*
! 			 * Otherwise we now look for the start. The start is either the
! 			 * last character before any word-break character going backwards
! 			 * from the end, or it's simply character 0. We also handle open
! 			 * quotes and parentheses.
! 			 */
! 			bool		inquotes = false;
! 			int			parentheses = 0;
! 
! 			for (start = end; start > 0; start--)
  			{
! 				if (buf[start] == '"')
! 					inquotes = !inquotes;
! 				if (!inquotes)
  				{
! 					if (buf[start] == ')')
! 						parentheses++;
! 					else if (buf[start] == '(')
! 					{
! 						if (--parentheses <= 0)
! 							break;
! 					}
! 					else if (parentheses == 0 &&
! 							 strchr(WORD_BREAKS, buf[start - 1]))
  						break;
  				}
  			}
- 
- 			point = start - 1;
- 
- 			/* make a copy of chars from start to end inclusive */
- 			s = pg_malloc(end - start + 2);
- 			strlcpy(s, &buf[start], end - start + 2);
- 
- 			words_found++;
  		}
  
! 		*previous_words++ = s;
  	}
  
! 	return words_found;
  }
  
  /*
--- 3731,3776 ----
  				break;
  			}
  		}
+ 		/* if no end found, we're done */
+ 		if (end < 0)
+ 			break;
  
  		/*
! 		 * Otherwise we now look for the start.  The start is either the last
! 		 * character before any word-break character going backwards from the
! 		 * end, or it's simply character 0.  We also handle open quotes and
! 		 * parentheses.
  		 */
! 		for (start = end; start > 0; start--)
  		{
! 			if (buf[start] == '"')
! 				inquotes = !inquotes;
! 			if (!inquotes)
  			{
! 				if (buf[start] == ')')
! 					parentheses++;
! 				else if (buf[start] == '(')
  				{
! 					if (--parentheses <= 0)
  						break;
  				}
+ 				else if (parentheses == 0 &&
+ 						 strchr(WORD_BREAKS, buf[start - 1]))
+ 					break;
  			}
  		}
  
! 		/* Return the word located at start to end inclusive */
! 		previous_words[words_found] = &buf[start];
! 		buf[end + 1] = '\0';
! 		words_found++;
! 
! 		/* Continue searching */
! 		point = start - 1;
  	}
  
! 	*nwords = words_found;
! 	return previous_words;
  }
  
  /*
diff --git a/src/bin/psql/tab-complete.h b/src/bin/psql/tab-complete.h
index 9dcd7e7..0e9a430 100644
*** a/src/bin/psql/tab-complete.h
--- b/src/bin/psql/tab-complete.h
***************
*** 8,15 ****
  #ifndef TAB_COMPLETE_H
  #define TAB_COMPLETE_H
  
! #include "postgres_fe.h"
  
! void		initialize_readline(void);
  
! #endif
--- 8,17 ----
  #ifndef TAB_COMPLETE_H
  #define TAB_COMPLETE_H
  
! #include "pqexpbuffer.h"
  
! extern PQExpBuffer tab_completion_query_buf;
  
! extern void initialize_readline(void);
! 
! #endif   /* TAB_COMPLETE_H */
#72Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Tom Lane (#69)
Re: Making tab-complete.c easier to maintain

On Sun, Dec 20, 2015 at 10:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I've committed this now with a number of changes, many of them just
stylistic.

Thanks! And thanks also to Michael, Kyotaro, Alvaro and Jeff. +1 for
the suggested further improvements, which I will help out with where I
can.

--
Thomas Munro
http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#70)
2 attachment(s)
Re: Making tab-complete.c easier to maintain

On Sun, Dec 20, 2015 at 8:08 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sun, Dec 20, 2015 at 6:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

1. I think it would be a good idea to convert the matching rules for
backslash commands too. To do that, we'd need to provide a case-sensitive
equivalent to word_match and the matching macros. I think we'd also have
to extend word_match to allow a trailing wildcard character, maybe "*".

I am not really sure I follow much the use of the wildcard, do you
mean to be able to work with the [S] extensions of the backslash
commands which are not completed now? I am attaching a patch that adds
support for a case-sensitive comparison facility without this wildcard
system, simplifying the backslash commands.

2. I believe that a very large fraction of the TailMatches() rules really
ought to be Matches(), ie, they should not consider matches that don't
start at the start of the line. And there's another bunch that could
be Matches() if the author hadn't been unaccountably lazy about checking
all words of the expected command. If we converted as much as we could
that way, it would make psql_completion faster because many inapplicable
rules could be discarded after a single integer comparison on
previous_words_count, and it would greatly reduce the risk of inapplicable
matches. We can't do that for rules meant to apply to DML statements,
since they can be buried in WITH, EXPLAIN, etc ... but an awful lot of
the DDL rules could be changed.

Yep, clearly. We may gain a bit of performance by matching directly
with an equal number of words using Matches instead of a lower bound
with TailMatches. I have looked at this thing and hacked a patch as
attached.

3. The HeadMatches macros are pretty iffy because they can only look back
nine words. I'm tempted to redesign get_previous_words so it just
tokenizes the whole line rather than having an arbitrary limitation.
(For that matter, it's long overdue for it to be able to deal with
multiline input...)

I might go look at #3, but I can't currently summon the energy to tackle
#1 or #2 --- any volunteers?

#3 has been already done in the mean time...

I could have a look at both of them and submit patch for next CF, both
things do not seem that much complicated.

Those things are as well added to the next CF.
--
Michael

Attachments:

0001-Improve-performance-of-psql-tab-completion.patchtext/x-patch; charset=US-ASCII; name=0001-Improve-performance-of-psql-tab-completion.patchDownload
From 351894c975e72d62b6c49e8ea203fc194ccc59ee Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 29 Dec 2015 22:13:27 +0900
Subject: [PATCH 1/2] Improve performance of psql tab completion

TailMatches are based on a lower-bound check and Matches uses a direct
match for the number of words. It happens that the former is used in many
places where the latter could be used. Doing the switch improve the
performance of tab completion by having to match only a number of words
for many commands.
---
 src/bin/psql/tab-complete.c | 567 +++++++++++++++++++++++---------------------
 1 file changed, 291 insertions(+), 276 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4c93ae9..c920353 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1137,6 +1137,21 @@ psql_completion(const char *text, int start, int end)
 #define Matches4(p1, p2, p3, p4) \
 	(previous_words_count == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
@@ -1266,10 +1281,10 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (TailMatches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (TailMatches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1278,49 +1293,49 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (TailMatches3("ALTER", "SCHEMA", MatchAny))
+	else if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (TailMatches3("ALTER", "COLLATION", MatchAny))
+	else if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (TailMatches3("ALTER", "CONVERSION", MatchAny))
+	else if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (TailMatches3("ALTER", "DATABASE", MatchAny))
+	else if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (TailMatches3("ALTER", "EVENT", "TRIGGER"))
+	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (TailMatches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (TailMatches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (TailMatches3("ALTER", "EXTENSION", MatchAny))
+	else if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (TailMatches2("ALTER", "FOREIGN"))
+	else if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (TailMatches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST4("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (TailMatches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1331,41 +1346,41 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (TailMatches2("ALTER", "INDEX"))
+	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (TailMatches3("ALTER", "INDEX", MatchAny))
+	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "SET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (TailMatches3("ALTER", "LANGUAGE", MatchAny))
+	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (TailMatches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (TailMatches3("ALTER", "MATERIALIZED", "VIEW"))
+	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (TailMatches3("ALTER", "USER|ROLE", MatchAny) &&
+	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
@@ -1380,7 +1395,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1395,43 +1410,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (TailMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (TailMatches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (TailMatches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
-			 TailMatches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
+	else if (Matches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+			 Matches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (TailMatches3("ALTER", "DOMAIN", MatchAny))
+	else if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (TailMatches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (TailMatches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	else if (Matches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (TailMatches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1440,71 +1455,71 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (TailMatches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (TailMatches3("ALTER", "SERVER", MatchAny))
+	else if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("VERSION", "OPTIONS", "OWNER TO");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (TailMatches2("ALTER", "SYSTEM"))
+	else if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (TailMatches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
+	else if (Matches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (TailMatches3("ALTER", "VIEW", MatchAny))
+	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (TailMatches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (TailMatches2("ALTER", "POLICY"))
+	else if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (TailMatches3("ALTER", "POLICY", MatchAny))
+	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (TailMatches4("ALTER", "POLICY", MatchAny, "ON"))
+	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (TailMatches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING", "WITH CHECK");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	else if (Matches6("POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (TailMatches3("ALTER", "RULE", MatchAny))
+	else if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (TailMatches4("ALTER", "RULE", MatchAny, "ON"))
+	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (TailMatches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (TailMatches3("ALTER", "TRIGGER", MatchAny))
+	else if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1513,17 +1528,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (TailMatches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (TailMatches3("ALTER", "TABLE", MatchAny))
+	else if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1533,134 +1548,134 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (TailMatches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	else if (Matches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (TailMatches4("DISABLE", "ROW", "LEVEL", "SECURITY"))
+	else if (Matches4("DISABLE", "ROW", "LEVEL", "SECURITY"))
 		COMPLETE_WITH_CONST("CASCADE");
 
 	/* ALTER TABLE xxx ALTER */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (TailMatches4("TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (TailMatches5("TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (TailMatches3("TABLE", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (TailMatches3("ALTER", "COLUMN", MatchAny) ||
-			 TailMatches4("TABLE", MatchAny, "ALTER", MatchAny))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "SET") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "(") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "("))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "STORAGE"))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "DROP") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (TailMatches3("TABLE", MatchAny, "CLUSTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("TABLE", MatchAny, "CLUSTER", "ON"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (TailMatches3("TABLE", MatchAny, "SET"))
+	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
+	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
-	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "TABLESPACE"))
+	/* If we have ALTER TABLE <sth> SET TABLESPACE provide a list of tablespaces */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "WITHOUT"))
+	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (TailMatches3("TABLE", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (TailMatches4("TABLE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1697,59 +1712,59 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (TailMatches5(MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (TailMatches5("TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (TailMatches4("TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (TailMatches3("TABLE", MatchAny, "REPLICA"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (TailMatches3("ALTER", "TABLESPACE", MatchAny))
+	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (TailMatches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (TailMatches3("ALTER", "TEXT", "SEARCH"))
+	else if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (TailMatches3("ALTER", "TYPE", MatchAny))
+	else if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "ADD"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (TailMatches5("TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
-	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
-	 * attributes
+	 * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
+	 * of attributes
 	 */
 	else if (TailMatches4("TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
@@ -1757,26 +1772,26 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches3("ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (TailMatches3("ALTER", "GROUP", MatchAny))
+	else if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (TailMatches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (TailMatches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (TailMatches1("BEGIN|END|ABORT"))
+	else if (Matches1("BEGIN|END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (TailMatches1("COMMIT"))
+	else if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (TailMatches1("RELEASE"))
+	else if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (TailMatches1("ROLLBACK"))
+	else if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 
@@ -1818,9 +1833,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (TailMatches1("COMMENT"))
+	else if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches2("COMMENT", "ON"))
+	else if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -1832,26 +1847,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (TailMatches3("COMMENT", "ON", "FOREIGN"))
+	else if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (TailMatches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches3("COMMENT", "ON", "CONSTRAINT"))
+	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (TailMatches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (TailMatches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (TailMatches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+			 Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
+			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1860,7 +1875,7 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (TailMatches1("COPY|\\copy") || TailMatches2("COPY", "BINARY"))
+	else if (Matches1("COPY|\\copy") || Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
 	else if (TailMatches2("COPY|\\copy|BINARY", MatchAny))
@@ -1883,40 +1898,40 @@ psql_completion(const char *text, int start, int end)
 							"FORCE NOT NULL");
 
 	/* CREATE DATABASE */
-	else if (TailMatches3("CREATE", "DATABASE", MatchAny))
+	else if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (TailMatches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (TailMatches2("CREATE", "EXTENSION"))
+	else if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (TailMatches3("CREATE", "EXTENSION", MatchAny))
+	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (TailMatches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (TailMatches2("CREATE", "FOREIGN"))
+	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (TailMatches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST2("HANDLER", "VALIDATOR");
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	else if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
 	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
@@ -1960,39 +1975,39 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (TailMatches3("CREATE", "POLICY", MatchAny))
+	else if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (TailMatches4("CREATE", "POLICY", MatchAny, "ON"))
+	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (TailMatches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (TailMatches3("CREATE", "RULE", MatchAny))
+	else if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (TailMatches4("CREATE", "RULE", MatchAny, "AS"))
+	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|INSERT|DELETE */
 	else if (TailMatches4("RULE", MatchAny, "AS", "ON"))
@@ -2005,60 +2020,60 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	else if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
+			 Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (TailMatches3("CREATE", "SERVER", MatchAny))
+	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (TailMatches3("CREATE", "TABLESPACE", MatchAny))
+	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (TailMatches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (TailMatches3("CREATE", "TEXT", "SEARCH"))
+	else if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches4("TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (TailMatches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+		 Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches5("TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	else if (Matches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches4("INSTEAD", "OF", MatchAny, "ON"))
@@ -2068,7 +2083,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (TailMatches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2083,7 +2098,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (TailMatches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2101,47 +2116,47 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	else if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (TailMatches2("CREATE", "MATERIALIZED"))
+	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (TailMatches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (TailMatches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (TailMatches2("CREATE", "EVENT"))
+	else if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (TailMatches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (TailMatches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (TailMatches2("DECLARE", MatchAny))
+	else if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
 
 /* CURSOR */
-	else if (TailMatches1("CURSOR"))
+	else if (Matches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE */
@@ -2150,15 +2165,15 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches1("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	else if (Matches2("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	else if (Matches3("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (TailMatches1("DISCARD"))
+	else if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
@@ -2166,84 +2181,84 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (TailMatches1("DO"))
+	else if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP (when not the previous word) */
 	/* DROP object with CASCADE / RESTRICT */
-	else if (TailMatches3("DROP",
+	else if (Matches3("DROP",
 						  "COLLATION|CONVERSION|DOMAIN|EXTENSION|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 						  MatchAny) ||
-		   (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
+		   (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
 			ends_with(prev_wd, ')')) ||
-			 TailMatches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
-			 TailMatches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
-			 TailMatches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
-			 TailMatches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+			 Matches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
+			 Matches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+			 Matches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
+			 Matches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (TailMatches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (TailMatches2("DROP", "FOREIGN"))
+	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (TailMatches2("DROP", "MATERIALIZED"))
+	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("DROP", "MATERIALIZED", "VIEW"))
+	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (TailMatches2("DROP", "OWNED"))
+	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("DROP", "OWNED", "BY"))
+	else if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (TailMatches3("DROP", "TEXT", "SEARCH"))
+	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (TailMatches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (TailMatches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (TailMatches2("DROP", "EVENT"))
+	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (TailMatches3("DROP", "EVENT", "TRIGGER"))
+	else if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (TailMatches2("DROP", "POLICY"))
+	else if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (TailMatches3("DROP", "POLICY", MatchAny))
+	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (TailMatches4("DROP", "POLICY", MatchAny, "ON"))
+	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (TailMatches3("DROP", "RULE", MatchAny))
+	else if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "RULE", MatchAny, "ON"))
+	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (TailMatches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
@@ -2255,22 +2270,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (TailMatches1("EXPLAIN"))
+	else if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (TailMatches2("EXPLAIN", "ANALYZE"))
+	else if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (TailMatches2("EXPLAIN", "VERBOSE") ||
-			 TailMatches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	else if (Matches2("EXPLAIN", "VERBOSE") ||
+			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (TailMatches1("FETCH|MOVE"))
+	else if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (TailMatches2("FETCH|MOVE", MatchAny))
+	else if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2278,7 +2293,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (TailMatches3("FETCH|MOVE", MatchAny, MatchAny))
+	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
@@ -2298,7 +2313,7 @@ psql_completion(const char *text, int start, int end)
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	else if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2318,7 +2333,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	else if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
@@ -2339,7 +2354,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	else if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2357,11 +2372,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2370,7 +2385,7 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -2411,27 +2426,27 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	else if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	else if (Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2442,34 +2457,34 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (TailMatches1("IMPORT"))
+	else if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (TailMatches2("IMPORT", "FOREIGN"))
+	else if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	else if (Matches1("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	else if (Matches2("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete "INSERT INTO <table> (" with attribute names */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, "("))
+	else if (Matches4("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	else if (Matches3("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	else if (Matches4("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
@@ -2479,22 +2494,22 @@ psql_completion(const char *text, int start, int end)
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (TailMatches1("LOCK"))
+	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (TailMatches2("LOCK", "TABLE"))
+	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (TailMatches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 TailMatches3("LOCK", "TABLE", MatchAny))
+	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (TailMatches3("LOCK", MatchAny, "IN") ||
-			 TailMatches4("LOCK", "TABLE", MatchAny, "IN"))
+	else if (Matches3("LOCK", MatchAny, "IN") ||
+			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -2502,7 +2517,7 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY */
-	else if (TailMatches1("NOTIFY"))
+	else if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2520,7 +2535,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (TailMatches3("PREPARE", MatchAny, "AS"))
+	else if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 
 /*
@@ -2529,42 +2544,42 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (TailMatches1("REASSIGN"))
+	else if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (TailMatches2("REASSIGN", "OWNED"))
+	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("REASSIGN", "OWNED", "BY"))
+	else if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatches4("REASSIGN", "OWNED", "BY", MatchAny))
+	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (TailMatches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (TailMatches1("REFRESH"))
+	else if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (TailMatches2("REFRESH", "MATERIALIZED"))
+	else if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("REFRESH", "MATERIALIZED", "VIEW"))
+	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
 	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (TailMatches1("REINDEX"))
+	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (TailMatches2("REINDEX", MatchAny))
+	else if (Matches2("REINDEX", MatchAny))
 	{
 		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
@@ -2602,13 +2617,13 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !Matches3("UPDATE", MatchAny, "SET"))
+	else if (Matches1("SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (TailMatches1("SHOW"))
+	else if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (TailMatches2("SET|BEGIN|START", "TRANSACTION") ||
-			 TailMatches2("BEGIN", "WORK") ||
+	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
+			 Matches2("BEGIN", "WORK") ||
 			 TailMatches4("SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
 	else if (TailMatches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
@@ -2626,19 +2641,19 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (TailMatches3("SET", "CONSTRAINTS", MatchAny))
+	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (TailMatches2("SET", "ROLE"))
+	else if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (TailMatches2("SET", "SESSION"))
+	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (TailMatches3("SET", "SESSION", "AUTHORIZATION"))
+	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (TailMatches2("RESET", "SESSION"))
+	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
 	else if (Matches2("SET", MatchAny))
@@ -2701,11 +2716,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (TailMatches1("TRUNCATE"))
+	else if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (TailMatches1("UNLISTEN"))
+	else if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
@@ -2729,16 +2744,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (TailMatches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (TailMatches4("CREATE", "USER", "MAPPING", "FOR"))
+	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (TailMatches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
-- 
2.6.4

0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchtext/x-patch; charset=US-ASCII; name=0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchDownload
From 3648a0ccf0aee97b328053fef6fa7c336d7ee56b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 29 Dec 2015 23:07:27 +0900
Subject: [PATCH 2/2] Adopt more compact tab completion for backslash commands
 in psql

This upgrades a bit the existing psql facility so as case-sensitive
comparisons can be done, which is a requirement contrary to other query
types that do not need to mind about that.
---
 src/bin/psql/tab-complete.c | 182 +++++++++++++++++++++++++-------------------
 1 file changed, 104 insertions(+), 78 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index c920353..ffa1942 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -963,8 +963,15 @@ initialize_readline(void)
 #define MatchAny  NULL
 #define MatchAnyExcept(pattern)  ("!" pattern)
 
+/*
+ * word_matches_internal
+ * Internal worker routine to check if a word matches a given pattern. Caller
+ * can optionally decide to make the check case-sensitive or not.
+ */
 static bool
-word_matches(const char *pattern, const char *word)
+word_matches_internal(const char *pattern,
+					  const char *word,
+					  bool case_sensitive)
 {
 	size_t		wordlen;
 
@@ -974,7 +981,7 @@ word_matches(const char *pattern, const char *word)
 
 	/* Handle negated patterns from the MatchAnyExcept macro. */
 	if (*pattern == '!')
-		return !word_matches(pattern + 1, word);
+		return !word_matches_internal(pattern + 1, word, case_sensitive);
 
 	/* Else consider each alternative in the pattern. */
 	wordlen = strlen(word);
@@ -987,9 +994,13 @@ word_matches(const char *pattern, const char *word)
 		while (*c != '\0' && *c != '|')
 			c++;
 		/* Match? */
-		if (wordlen == (c - pattern) &&
-			pg_strncasecmp(word, pattern, wordlen) == 0)
-			return true;
+		if (wordlen == (c - pattern))
+		{
+			/* Do the pattern comparison, depending on the sensitiveness */
+			if ((!case_sensitive && pg_strncasecmp(word, pattern, wordlen) == 0) ||
+				(case_sensitive && strncmp(word, pattern, wordlen) == 0))
+				return true;
+		}
 		/* Out of alternatives? */
 		if (*c == '\0')
 			break;
@@ -1001,6 +1012,26 @@ word_matches(const char *pattern, const char *word)
 }
 
 /*
+ * word_matches
+ * Utility routine to match a word with a pattern as case-insensitive.
+ */
+static bool
+word_matches(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, false);
+}
+
+/*
+ * word_case_matches
+ * Utility routine to match a word with a patterm as case-sensitive.
+ */
+static bool
+word_case_matches(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, true);
+}
+
+/*
  * Check if the final character of 's' is 'c'.
  */
 static bool
@@ -1172,6 +1203,18 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p2, previous_words[previous_words_count - 2]) && \
 	 word_matches(p3, previous_words[previous_words_count - 3]))
 
+	/*
+	 * Macros for matching N words before point with case-sensitive
+	 * comparison.
+	 */
+#define CaseMatches1(p1) \
+	(previous_words_count >= 1 && \
+	 word_case_matches(p1, prev_wd))
+#define CaseMatches2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_case_matches(p1, prev_wd) && \
+	 word_case_matches(p2, prev2_wd))
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -2815,95 +2858,91 @@ psql_completion(const char *text, int start, int end)
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (CaseMatches1("\\?"))
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (CaseMatches1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (previous_words_count >= 2 &&
-			 (strcmp(prev2_wd, "\\connect") == 0 ||
-			  strcmp(prev2_wd, "\\c") == 0))
+	else if (CaseMatches2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (CaseMatches1("\\da"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (CaseMatches1("\\db"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (CaseMatches1("\\dD"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (CaseMatches1("\\des"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (CaseMatches1("\\deu"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (CaseMatches1("\\dew"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (CaseMatches1("\\df"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (CaseMatches1("\\dFd"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (CaseMatches1("\\dFp"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (CaseMatches1("\\dFt"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (CaseMatches1("\\dF"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (CaseMatches1("\\di"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (CaseMatches1("\\dL"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (CaseMatches1("\\dn"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (CaseMatches1("\\dp") || CaseMatches1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (CaseMatches1("\\ds"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (CaseMatches1("\\dt"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (CaseMatches1("\\dT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (CaseMatches1("\\du") || CaseMatches1("\\dg"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (CaseMatches1("\\dv"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (CaseMatches1("\\dx"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (CaseMatches1("\\dm"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (CaseMatches1("\\dE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (CaseMatches1("\\dy"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (CaseMatches1("\\d"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (CaseMatches1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (CaseMatches1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (CaseMatches1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (CaseMatches1("\\h") || CaseMatches1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (CaseMatches1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (CaseMatches1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -2914,10 +2953,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\pset") == 0)
+	else if (CaseMatches2("\\pset", MatchAny))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		if (CaseMatches1("format"))
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -2925,16 +2963,14 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (CaseMatches1("linestyle"))
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (CaseMatches1("unicode_border_linestyle|unicode_column_linestyle|unicode_header_linestyle"))
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -2943,73 +2979,72 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (CaseMatches1("\\unset"))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (CaseMatches1("\\set"))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\set") == 0)
+	else if (CaseMatches2("\\set", MatchAny))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		if (CaseMatches1("AUTOCOMMIT"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (CaseMatches1("COMP_KEYWORD_CASE"))
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (CaseMatches1("ECHO"))
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (CaseMatches1("ECHO_HIDDEN"))
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (CaseMatches1("HISTCONTROL"))
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (CaseMatches1("ON_ERROR_ROLLBACK"))
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (CaseMatches1("ON_ERROR_STOP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (CaseMatches1("QUIET"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (CaseMatches1("SHOW_CONTEXT"))
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (CaseMatches1("SINGLELINE"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (CaseMatches1("SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (CaseMatches1("VERBOSITY"))
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -3017,20 +3052,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (CaseMatches1("\\sf|\\sf+"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (CaseMatches1("\\sv|\\sv+"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (CaseMatches1("\\cd|\\e|\\edit|\\g|\\i|\\include|\\ir|\\include_relative|\\o|\\out|\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
-- 
2.6.4

#74Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#73)
Re: Making tab-complete.c easier to maintain

Michael Paquier <michael.paquier@gmail.com> writes:

On Sun, Dec 20, 2015 at 6:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

1. I think it would be a good idea to convert the matching rules for
backslash commands too. To do that, we'd need to provide a case-sensitive
equivalent to word_match and the matching macros. I think we'd also have
to extend word_match to allow a trailing wildcard character, maybe "*".

I am not really sure I follow much the use of the wildcard, do you
mean to be able to work with the [S] extensions of the backslash
commands which are not completed now?

But they are completed:

regression=# \dfS str<TAB>
string_agg string_agg_transfn strip
string_agg_finalfn string_to_array strpos

This is because of the use of strncmp instead of plain strcmp
in most of the backslash matching rules, eg the above case is
covered by

else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)

I was envisioning that we'd want to convert this to something like

else if (TailMatchesCS1("\\df*"))

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Michael Paquier (#73)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 30, 2015 at 3:14 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sun, Dec 20, 2015 at 8:08 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sun, Dec 20, 2015 at 6:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

2. I believe that a very large fraction of the TailMatches() rules really
ought to be Matches(), ie, they should not consider matches that don't
start at the start of the line. And there's another bunch that could
be Matches() if the author hadn't been unaccountably lazy about checking
all words of the expected command. If we converted as much as we could
that way, it would make psql_completion faster because many inapplicable
rules could be discarded after a single integer comparison on
previous_words_count, and it would greatly reduce the risk of inapplicable
matches. We can't do that for rules meant to apply to DML statements,
since they can be buried in WITH, EXPLAIN, etc ... but an awful lot of
the DDL rules could be changed.

Yep, clearly. We may gain a bit of performance by matching directly
with an equal number of words using Matches instead of a lower bound
with TailMatches. I have looked at this thing and hacked a patch as
attached.

I see that you changed INSERT and DELETE (but not UPDATE) to use
MatchesN rather than TailMatchesN. Shouldn't these stay with
TailMatchesN for the reason Tom gave above?

--
Thomas Munro
http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#74)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 30, 2015 at 1:21 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

This is because of the use of strncmp instead of plain strcmp
in most of the backslash matching rules, eg the above case is
covered by

else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)

Ah, OK. The length of the name and not the pattern is used in
word_matches, but we had better use something based on the pattern
shape. And the current logic for backslash commands uses the length of
the pattern, and not the word for its checks.

I was envisioning that we'd want to convert this to something like

else if (TailMatchesCS1("\\df*"))

That's a better macro name...
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#75)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 30, 2015 at 6:26 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

On Wed, Dec 30, 2015 at 3:14 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sun, Dec 20, 2015 at 8:08 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sun, Dec 20, 2015 at 6:24 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

2. I believe that a very large fraction of the TailMatches() rules really
ought to be Matches(), ie, they should not consider matches that don't
start at the start of the line. And there's another bunch that could
be Matches() if the author hadn't been unaccountably lazy about checking
all words of the expected command. If we converted as much as we could
that way, it would make psql_completion faster because many inapplicable
rules could be discarded after a single integer comparison on
previous_words_count, and it would greatly reduce the risk of inapplicable
matches. We can't do that for rules meant to apply to DML statements,
since they can be buried in WITH, EXPLAIN, etc ... but an awful lot of
the DDL rules could be changed.

Yep, clearly. We may gain a bit of performance by matching directly
with an equal number of words using Matches instead of a lower bound
with TailMatches. I have looked at this thing and hacked a patch as
attached.

I see that you changed INSERT and DELETE (but not UPDATE) to use
MatchesN rather than TailMatchesN. Shouldn't these stay with
TailMatchesN for the reason Tom gave above?

Er, yeah. They had better be TailMatches, or even COPY DML stuff will be broken.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#77)
2 attachment(s)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 30, 2015 at 9:14 AM, Michael Paquier wrote:

On Wed, Dec 30, 2015 at 6:26 AM, Thomas Munro wrote:

I see that you changed INSERT and DELETE (but not UPDATE) to use
MatchesN rather than TailMatchesN. Shouldn't these stay with
TailMatchesN for the reason Tom gave above?

Er, yeah. They had better be TailMatches, or even COPY DML stuff will be broken.

OK, here are new patches.
- 0001 switches a bunch of TailMatches to Matches. Do we want to care
about the case where a schema is created following by a bunch of
objects? I mean stuff like "CREATE SCHEMA hoge CREATE TABLE ..." where
the current completion would work fine. The performance gains seem
worth it compared to the number of people actually using it, the point
has just not been raised yet.
- 0002 that implements the new tab completion for backslash commands,
with the wildcard "*" as suggested by Tom.

I fixed in 0001 the stuff with DML queries, and also found one bug for
another query while re-reading the code.
Regards,
--
Michael

Attachments:

0001-Improve-performance-of-psql-tab-completion.patchtext/x-patch; charset=US-ASCII; name=0001-Improve-performance-of-psql-tab-completion.patchDownload
From 3a2e63b984548d2f4826dabd61e0efcae3aabe22 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 30 Dec 2015 20:32:18 +0900
Subject: [PATCH 1/2] Improve performance of psql tab completion

TailMatches are based on a lower-bound check and Matches uses a direct
match for the number of words. It happens that the former is used in many
places where the latter could be used. Doing the switch improve the
performance of tab completion by having to match only a number of words
for many commands.
---
 src/bin/psql/tab-complete.c | 547 +++++++++++++++++++++++---------------------
 1 file changed, 281 insertions(+), 266 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4c93ae9..b36bd73 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1137,6 +1137,21 @@ psql_completion(const char *text, int start, int end)
 #define Matches4(p1, p2, p3, p4) \
 	(previous_words_count == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
@@ -1266,10 +1281,10 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (TailMatches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (TailMatches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1278,49 +1293,49 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (TailMatches3("ALTER", "SCHEMA", MatchAny))
+	else if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (TailMatches3("ALTER", "COLLATION", MatchAny))
+	else if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (TailMatches3("ALTER", "CONVERSION", MatchAny))
+	else if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (TailMatches3("ALTER", "DATABASE", MatchAny))
+	else if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (TailMatches3("ALTER", "EVENT", "TRIGGER"))
+	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (TailMatches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (TailMatches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (TailMatches3("ALTER", "EXTENSION", MatchAny))
+	else if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (TailMatches2("ALTER", "FOREIGN"))
+	else if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (TailMatches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST4("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (TailMatches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1331,41 +1346,41 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (TailMatches2("ALTER", "INDEX"))
+	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (TailMatches3("ALTER", "INDEX", MatchAny))
+	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "SET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (TailMatches3("ALTER", "LANGUAGE", MatchAny))
+	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (TailMatches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (TailMatches3("ALTER", "MATERIALIZED", "VIEW"))
+	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (TailMatches3("ALTER", "USER|ROLE", MatchAny) &&
+	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
@@ -1380,7 +1395,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1395,43 +1410,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (TailMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (TailMatches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (TailMatches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
-			 TailMatches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
+	else if (Matches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+			 Matches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (TailMatches3("ALTER", "DOMAIN", MatchAny))
+	else if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (TailMatches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (TailMatches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	else if (Matches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (TailMatches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1440,71 +1455,71 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (TailMatches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (TailMatches3("ALTER", "SERVER", MatchAny))
+	else if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("VERSION", "OPTIONS", "OWNER TO");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (TailMatches2("ALTER", "SYSTEM"))
+	else if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (TailMatches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
+	else if (Matches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (TailMatches3("ALTER", "VIEW", MatchAny))
+	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (TailMatches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (TailMatches2("ALTER", "POLICY"))
+	else if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (TailMatches3("ALTER", "POLICY", MatchAny))
+	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (TailMatches4("ALTER", "POLICY", MatchAny, "ON"))
+	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (TailMatches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING", "WITH CHECK");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (TailMatches3("ALTER", "RULE", MatchAny))
+	else if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (TailMatches4("ALTER", "RULE", MatchAny, "ON"))
+	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (TailMatches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (TailMatches3("ALTER", "TRIGGER", MatchAny))
+	else if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1513,17 +1528,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (TailMatches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (TailMatches3("ALTER", "TABLE", MatchAny))
+	else if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1533,46 +1548,46 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (TailMatches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	else if (Matches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
@@ -1581,86 +1596,86 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("CASCADE");
 
 	/* ALTER TABLE xxx ALTER */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (TailMatches4("TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (TailMatches5("TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (TailMatches3("TABLE", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (TailMatches3("ALTER", "COLUMN", MatchAny) ||
-			 TailMatches4("TABLE", MatchAny, "ALTER", MatchAny))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "SET") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "(") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "("))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "STORAGE"))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "DROP") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (TailMatches3("TABLE", MatchAny, "CLUSTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("TABLE", MatchAny, "CLUSTER", "ON"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (TailMatches3("TABLE", MatchAny, "SET"))
+	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
+	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
-	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "TABLESPACE"))
+	/* If we have ALTER TABLE <sth> SET TABLESPACE provide a list of tablespaces */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "WITHOUT"))
+	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (TailMatches3("TABLE", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (TailMatches4("TABLE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1697,54 +1712,54 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (TailMatches5(MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (TailMatches5("TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (TailMatches4("TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (TailMatches3("TABLE", MatchAny, "REPLICA"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (TailMatches3("ALTER", "TABLESPACE", MatchAny))
+	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (TailMatches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (TailMatches3("ALTER", "TEXT", "SEARCH"))
+	else if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (TailMatches3("ALTER", "TYPE", MatchAny))
+	else if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "ADD"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (TailMatches5("TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
@@ -1757,26 +1772,26 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches3("ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (TailMatches3("ALTER", "GROUP", MatchAny))
+	else if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (TailMatches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (TailMatches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (TailMatches1("BEGIN|END|ABORT"))
+	else if (Matches1("BEGIN|END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (TailMatches1("COMMIT"))
+	else if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (TailMatches1("RELEASE"))
+	else if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (TailMatches1("ROLLBACK"))
+	else if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 
@@ -1818,9 +1833,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (TailMatches1("COMMENT"))
+	else if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches2("COMMENT", "ON"))
+	else if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -1832,26 +1847,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (TailMatches3("COMMENT", "ON", "FOREIGN"))
+	else if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (TailMatches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches3("COMMENT", "ON", "CONSTRAINT"))
+	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (TailMatches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (TailMatches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (TailMatches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+			 Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
+			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1860,7 +1875,7 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (TailMatches1("COPY|\\copy") || TailMatches2("COPY", "BINARY"))
+	else if (Matches1("COPY|\\copy") || Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
 	else if (TailMatches2("COPY|\\copy|BINARY", MatchAny))
@@ -1883,40 +1898,40 @@ psql_completion(const char *text, int start, int end)
 							"FORCE NOT NULL");
 
 	/* CREATE DATABASE */
-	else if (TailMatches3("CREATE", "DATABASE", MatchAny))
+	else if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (TailMatches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (TailMatches2("CREATE", "EXTENSION"))
+	else if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (TailMatches3("CREATE", "EXTENSION", MatchAny))
+	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (TailMatches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (TailMatches2("CREATE", "FOREIGN"))
+	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (TailMatches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST2("HANDLER", "VALIDATOR");
 
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	else if (Matches2("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
 	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
@@ -1960,39 +1975,39 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (TailMatches3("CREATE", "POLICY", MatchAny))
+	else if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (TailMatches4("CREATE", "POLICY", MatchAny, "ON"))
+	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (TailMatches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (TailMatches3("CREATE", "RULE", MatchAny))
+	else if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (TailMatches4("CREATE", "RULE", MatchAny, "AS"))
+	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|INSERT|DELETE */
 	else if (TailMatches4("RULE", MatchAny, "AS", "ON"))
@@ -2005,60 +2020,60 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	else if (Matches3("CREATE", "SEQUENCE", MatchAny) ||
+			 Matches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
 /* CREATE TEMP/TEMPORARY SEQUENCE <name> NO */
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
+	else if (Matches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
 		TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (TailMatches3("CREATE", "SERVER", MatchAny))
+	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (Matches2("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (Matches2("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (TailMatches3("CREATE", "TABLESPACE", MatchAny))
+	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (TailMatches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (TailMatches3("CREATE", "TEXT", "SEARCH"))
+	else if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches4("TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (TailMatches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (Matches3("CREATE", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
-	else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
+	else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
 		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
-	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+	else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+		 Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches5("TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	else if (Matches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches4("INSTEAD", "OF", MatchAny, "ON"))
@@ -2068,7 +2083,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (TailMatches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2083,7 +2098,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (TailMatches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2101,47 +2116,47 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (Matches3("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	else if (Matches4("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (TailMatches2("CREATE", "MATERIALIZED"))
+	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (TailMatches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (TailMatches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (TailMatches2("CREATE", "EVENT"))
+	else if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (TailMatches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (TailMatches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (TailMatches2("DECLARE", MatchAny))
+	else if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
 
 /* CURSOR */
-	else if (TailMatches1("CURSOR"))
+	else if (Matches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE */
@@ -2158,7 +2173,7 @@ psql_completion(const char *text, int start, int end)
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (TailMatches1("DISCARD"))
+	else if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
@@ -2166,84 +2181,84 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (TailMatches1("DO"))
+	else if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP (when not the previous word) */
 	/* DROP object with CASCADE / RESTRICT */
-	else if (TailMatches3("DROP",
+	else if (Matches3("DROP",
 						  "COLLATION|CONVERSION|DOMAIN|EXTENSION|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 						  MatchAny) ||
-		   (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
+		   (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
 			ends_with(prev_wd, ')')) ||
-			 TailMatches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
-			 TailMatches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
-			 TailMatches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
-			 TailMatches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+			 Matches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
+			 Matches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+			 Matches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
+			 Matches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (TailMatches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (TailMatches2("DROP", "FOREIGN"))
+	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (TailMatches2("DROP", "MATERIALIZED"))
+	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("DROP", "MATERIALIZED", "VIEW"))
+	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (TailMatches2("DROP", "OWNED"))
+	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("DROP", "OWNED", "BY"))
+	else if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (TailMatches3("DROP", "TEXT", "SEARCH"))
+	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (TailMatches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (TailMatches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (TailMatches2("DROP", "EVENT"))
+	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (TailMatches3("DROP", "EVENT", "TRIGGER"))
+	else if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (TailMatches2("DROP", "POLICY"))
+	else if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (TailMatches3("DROP", "POLICY", MatchAny))
+	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (TailMatches4("DROP", "POLICY", MatchAny, "ON"))
+	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (TailMatches3("DROP", "RULE", MatchAny))
+	else if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "RULE", MatchAny, "ON"))
+	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (TailMatches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
@@ -2255,22 +2270,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (TailMatches1("EXPLAIN"))
+	else if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (TailMatches2("EXPLAIN", "ANALYZE"))
+	else if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (TailMatches2("EXPLAIN", "VERBOSE") ||
-			 TailMatches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	else if (Matches2("EXPLAIN", "VERBOSE") ||
+			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (TailMatches1("FETCH|MOVE"))
+	else if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (TailMatches2("FETCH|MOVE", MatchAny))
+	else if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2278,7 +2293,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (TailMatches3("FETCH|MOVE", MatchAny, MatchAny))
+	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
@@ -2298,7 +2313,7 @@ psql_completion(const char *text, int start, int end)
 
 /* GRANT && REVOKE */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	else if (Matches1("GRANT|REVOKE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'SELECT'"
 							" UNION SELECT 'INSERT'"
@@ -2318,7 +2333,7 @@ psql_completion(const char *text, int start, int end)
 	 * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
 	 * TO/FROM
 	 */
-	else if (TailMatches2("GRANT|REVOKE", MatchAny))
+	else if (Matches2("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
@@ -2339,7 +2354,7 @@ psql_completion(const char *text, int start, int end)
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
 	 */
-	else if (TailMatches3("GRANT|REVOKE", MatchAny, "ON"))
+	else if (Matches3("GRANT|REVOKE", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 								   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
 								   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
@@ -2357,11 +2372,11 @@ psql_completion(const char *text, int start, int end)
 								   " UNION SELECT 'TABLESPACE'"
 								   " UNION SELECT 'TYPE'");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST3("FUNCTIONS IN SCHEMA", "SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2370,7 +2385,7 @@ psql_completion(const char *text, int start, int end)
 	 *
 	 * Complete "GRANT/REVOKE * ON *" with "TO/FROM".
 	 */
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
+	else if (Matches4("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
 		if (TailMatches1("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -2411,27 +2426,27 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
-	else if (TailMatches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
+	else if (Matches8("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
-	else if (TailMatches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches7("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
-	else if (TailMatches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
+	else if (Matches6("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (Matches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2442,9 +2457,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (TailMatches1("IMPORT"))
+	else if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (TailMatches2("IMPORT", "FOREIGN"))
+	else if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
@@ -2479,22 +2494,22 @@ psql_completion(const char *text, int start, int end)
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (TailMatches1("LOCK"))
+	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (TailMatches2("LOCK", "TABLE"))
+	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (TailMatches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 TailMatches3("LOCK", "TABLE", MatchAny))
+	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (TailMatches3("LOCK", MatchAny, "IN") ||
-			 TailMatches4("LOCK", "TABLE", MatchAny, "IN"))
+	else if (Matches3("LOCK", MatchAny, "IN") ||
+			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -2502,7 +2517,7 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY */
-	else if (TailMatches1("NOTIFY"))
+	else if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2520,7 +2535,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (TailMatches3("PREPARE", MatchAny, "AS"))
+	else if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 
 /*
@@ -2529,42 +2544,42 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (TailMatches1("REASSIGN"))
+	else if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (TailMatches2("REASSIGN", "OWNED"))
+	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("REASSIGN", "OWNED", "BY"))
+	else if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatches4("REASSIGN", "OWNED", "BY", MatchAny))
+	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (TailMatches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (TailMatches1("REFRESH"))
+	else if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (TailMatches2("REFRESH", "MATERIALIZED"))
+	else if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("REFRESH", "MATERIALIZED", "VIEW"))
+	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
 	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (TailMatches1("REINDEX"))
+	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (TailMatches2("REINDEX", MatchAny))
+	else if (Matches2("REINDEX", MatchAny))
 	{
 		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
@@ -2602,13 +2617,13 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !Matches3("UPDATE", MatchAny, "SET"))
+	else if (Matches1("SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (TailMatches1("SHOW"))
+	else if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (TailMatches2("SET|BEGIN|START", "TRANSACTION") ||
-			 TailMatches2("BEGIN", "WORK") ||
+	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
+			 Matches2("BEGIN", "WORK") ||
 			 TailMatches4("SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
 	else if (TailMatches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
@@ -2626,19 +2641,19 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (TailMatches3("SET", "CONSTRAINTS", MatchAny))
+	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (TailMatches2("SET", "ROLE"))
+	else if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (TailMatches2("SET", "SESSION"))
+	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (TailMatches3("SET", "SESSION", "AUTHORIZATION"))
+	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (TailMatches2("RESET", "SESSION"))
+	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
 	else if (Matches2("SET", MatchAny))
@@ -2701,11 +2716,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (TailMatches1("TRUNCATE"))
+	else if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (TailMatches1("UNLISTEN"))
+	else if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
@@ -2729,16 +2744,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (TailMatches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (TailMatches4("CREATE", "USER", "MAPPING", "FOR"))
+	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (TailMatches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
-- 
2.6.4

0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchtext/x-patch; charset=US-ASCII; name=0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchDownload
From 517b4cd5aae515194b296ae0eafe0a821bc90123 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 30 Dec 2015 21:13:01 +0900
Subject: [PATCH 2/2] Adopt more compact tab completion for backslash commands
 in psql

This upgrades a bit the existing psql facility so as case-sensitive
comparisons can be done, which is a requirement contrary to other query
types that do not need to mind about that.
---
 src/bin/psql/tab-complete.c | 201 ++++++++++++++++++++++++++------------------
 1 file changed, 120 insertions(+), 81 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b36bd73..9997262 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -952,10 +952,12 @@ initialize_readline(void)
 
 /*
  * Check if 'word' matches any of the '|'-separated strings in 'pattern',
- * using case-insensitive comparisons.
+ * using case-insensitive or case-sensitive comparisons.
  * If pattern is NULL, it's a wild card that matches any word.
  * If pattern begins with "!", the result is negated, ie we check that 'word'
  * does *not* match any alternative appearing in the rest of 'pattern'.
+ * If pattern finishes with a trailing wild card "*", it can match with any
+ * words beginning by the same characters up to this point.
  *
  * For readability, callers should use the macros MatchAny and MatchAnyExcept
  * to invoke the two special cases for 'pattern'.
@@ -963,10 +965,18 @@ initialize_readline(void)
 #define MatchAny  NULL
 #define MatchAnyExcept(pattern)  ("!" pattern)
 
+/*
+ * word_matches_internal
+ *
+ * Internal worker routine to check if a word matches a given pattern. Caller
+ * can optionally decide to make the check case-sensitive or not.
+ */
 static bool
-word_matches(const char *pattern, const char *word)
+word_matches_internal(const char *pattern,
+					  const char *word,
+					  bool case_sensitive)
 {
-	size_t		wordlen;
+	size_t		wordlen, patternlen;
 
 	/* NULL pattern matches anything. */
 	if (pattern == NULL)
@@ -974,22 +984,36 @@ word_matches(const char *pattern, const char *word)
 
 	/* Handle negated patterns from the MatchAnyExcept macro. */
 	if (*pattern == '!')
-		return !word_matches(pattern + 1, word);
+		return !word_matches_internal(pattern + 1, word, case_sensitive);
 
 	/* Else consider each alternative in the pattern. */
 	wordlen = strlen(word);
+	patternlen = strlen(pattern);
+
+	/*
+	 * Check if a trailing wild card is used in the pattern and adjust
+	 * the length to be evaluated to be just the pattern without its
+	 * wild card.
+	 */
+	if (pattern[patternlen - 1] == '*')
+		wordlen = patternlen - 1;
+
 	for (;;)
 	{
 		const char *c;
 
 		/* Find end of current alternative. */
 		c = pattern;
-		while (*c != '\0' && *c != '|')
+		while (*c != '\0' && *c != '|' && *c != '*')
 			c++;
 		/* Match? */
-		if (wordlen == (c - pattern) &&
-			pg_strncasecmp(word, pattern, wordlen) == 0)
-			return true;
+		if (wordlen == (c - pattern))
+		{
+			/* Do the pattern comparison, depending on the sensitiveness */
+			if ((!case_sensitive && pg_strncasecmp(word, pattern, wordlen) == 0) ||
+				(case_sensitive && strncmp(word, pattern, wordlen) == 0))
+				return true;
+		}
 		/* Out of alternatives? */
 		if (*c == '\0')
 			break;
@@ -1001,6 +1025,26 @@ word_matches(const char *pattern, const char *word)
 }
 
 /*
+ * word_matches
+ * Utility routine to match a word with a pattern as case-insensitive.
+ */
+static bool
+word_matches(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, false);
+}
+
+/*
+ * word_matches_cs
+ * Utility routine to match a word with a patterm as case-sensitive.
+ */
+static bool
+word_matches_cs(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, true);
+}
+
+/*
  * Check if the final character of 's' is 'c'.
  */
 static bool
@@ -1172,6 +1216,18 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p2, previous_words[previous_words_count - 2]) && \
 	 word_matches(p3, previous_words[previous_words_count - 3]))
 
+	/*
+	 * Macros for matching N words before point with case-sensitive
+	 * comparison.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -2815,95 +2871,91 @@ psql_completion(const char *text, int start, int end)
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (TailMatchesCS1("\\?"))
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (previous_words_count >= 2 &&
-			 (strcmp(prev2_wd, "\\connect") == 0 ||
-			  strcmp(prev2_wd, "\\c") == 0))
+	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -2914,10 +2966,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\pset") == 0)
+	else if (TailMatchesCS2("\\pset", MatchAny))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		if (TailMatchesCS1("format"))
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -2925,16 +2976,14 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (TailMatchesCS1("linestyle"))
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (TailMatchesCS1("unicode_border_linestyle|unicode_column_linestyle|unicode_header_linestyle"))
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -2943,73 +2992,72 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (TailMatchesCS1("\\unset"))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (TailMatchesCS1("\\set"))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\set") == 0)
+	else if (TailMatchesCS2("\\set", MatchAny))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		if (TailMatchesCS1("AUTOCOMMIT"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (TailMatchesCS1("ECHO"))
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (TailMatchesCS1("ECHO_HIDDEN"))
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (TailMatchesCS1("HISTCONTROL"))
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (TailMatchesCS1("ON_ERROR_STOP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (TailMatchesCS1("QUIET"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (TailMatchesCS1("SHOW_CONTEXT"))
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (TailMatchesCS1("SINGLELINE"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (TailMatchesCS1("SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (TailMatchesCS1("VERBOSITY"))
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -3017,20 +3065,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|\\ir|\\include_relative|\\o|\\out|\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
-- 
2.6.4

#79Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#78)
Re: Making tab-complete.c easier to maintain

Michael Paquier wrote:

OK, here are new patches.
- 0001 switches a bunch of TailMatches to Matches. Do we want to care
about the case where a schema is created following by a bunch of
objects? I mean stuff like "CREATE SCHEMA hoge CREATE TABLE ..." where
the current completion would work fine. The performance gains seem
worth it compared to the number of people actually using it, the point
has just not been raised yet.

I'd rather have the completion work for that case than get a few
microseconds speedup. As far as I recall, it's only four commands that
must retain the old coding.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#79)
Re: Making tab-complete.c easier to maintain

On Wed, Dec 30, 2015 at 11:21 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

OK, here are new patches.
- 0001 switches a bunch of TailMatches to Matches. Do we want to care
about the case where a schema is created following by a bunch of
objects? I mean stuff like "CREATE SCHEMA hoge CREATE TABLE ..." where
the current completion would work fine. The performance gains seem
worth it compared to the number of people actually using it, the point
has just not been raised yet.

I'd rather have the completion work for that case than get a few
microseconds speedup. As far as I recall, it's only four commands that
must retain the old coding.

Fine for me this way.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#80)
2 attachment(s)
Re: Making tab-complete.c easier to maintain

On Thu, Dec 31, 2015 at 9:13 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Wed, Dec 30, 2015 at 11:21 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

OK, here are new patches.
- 0001 switches a bunch of TailMatches to Matches. Do we want to care
about the case where a schema is created following by a bunch of
objects? I mean stuff like "CREATE SCHEMA hoge CREATE TABLE ..." where
the current completion would work fine. The performance gains seem
worth it compared to the number of people actually using it, the point
has just not been raised yet.

I'd rather have the completion work for that case than get a few
microseconds speedup. As far as I recall, it's only four commands that
must retain the old coding.

Fine for me this way.

So, here are the commands that still remain with TailMatches to cover
this case, per gram.y:
- CREATE TABLE
- CREATE INDEX
- CREATE VIEW
- GRANT
- CREATE TRIGGER
- CREATE SEQUENCE
New patches are attached.
Regards,
--
Michael

Attachments:

0001-Improve-performance-of-psql-tab-completion.patchtext/x-patch; charset=US-ASCII; name=0001-Improve-performance-of-psql-tab-completion.patchDownload
From 99d664506b712defaaae378bdc0763201d3d4721 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 1 Jan 2016 21:07:01 +0900
Subject: [PATCH 1/2] Improve performance of psql tab completion

TailMatches are based on a lower-bound check and Matches uses a direct
match for the number of words. It happens that the former is used in many
places where the latter could be used. Doing the switch improve the
performance of tab completion by having to match only a number of words
for many commands.
---
 src/bin/psql/tab-complete.c | 499 +++++++++++++++++++++++---------------------
 1 file changed, 257 insertions(+), 242 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4c93ae9..2472a99 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1137,6 +1137,21 @@ psql_completion(const char *text, int start, int end)
 #define Matches4(p1, p2, p3, p4) \
 	(previous_words_count == 4 && \
 	 TailMatches4(p1, p2, p3, p4))
+#define Matches5(p1, p2, p3, p4, p5) \
+	(previous_words_count == 5 && \
+	 TailMatches5(p1, p2, p3, p4, p5))
+#define Matches6(p1, p2, p3, p4, p5, p6) \
+	(previous_words_count == 6 && \
+	 TailMatches6(p1, p2, p3, p4, p5, p6))
+#define Matches7(p1, p2, p3, p4, p5, p6, p7) \
+	(previous_words_count == 7 && \
+	 TailMatches7(p1, p2, p3, p4, p5, p6, p7))
+#define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \
+	(previous_words_count == 8 && \
+	 TailMatches8(p1, p2, p3, p4, p5, p6, p7, p8))
+#define Matches9(p1, p2, p3, p4, p5, p6, p7, p8, p9) \
+	(previous_words_count == 9 && \
+	 TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9))
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
@@ -1266,10 +1281,10 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (TailMatches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("ALTER", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (TailMatches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "AGGREGATE|FUNCTION", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1278,49 +1293,49 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (TailMatches3("ALTER", "SCHEMA", MatchAny))
+	else if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (TailMatches3("ALTER", "COLLATION", MatchAny))
+	else if (Matches3("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (TailMatches3("ALTER", "CONVERSION", MatchAny))
+	else if (Matches3("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (TailMatches3("ALTER", "DATABASE", MatchAny))
+	else if (Matches3("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER EVENT TRIGGER */
-	else if (TailMatches3("ALTER", "EVENT", "TRIGGER"))
+	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (TailMatches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (TailMatches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (TailMatches3("ALTER", "EXTENSION", MatchAny))
+	else if (Matches3("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER FOREIGN */
-	else if (TailMatches2("ALTER", "FOREIGN"))
+	else if (Matches2("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (TailMatches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST4("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (TailMatches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1331,41 +1346,41 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (TailMatches2("ALTER", "INDEX"))
+	else if (Matches2("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (TailMatches3("ALTER", "INDEX", MatchAny))
+	else if (Matches3("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET");
 	/* ALTER INDEX <name> SET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "SET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (TailMatches4("ALTER", "INDEX", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST3("fillfactor", "fastupdate",
 							"gin_pending_list_limit");
-	else if (TailMatches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST3("fillfactor =", "fastupdate =",
 							"gin_pending_list_limit =");
 
 	/* ALTER LANGUAGE <name> */
-	else if (TailMatches3("ALTER", "LANGUAGE", MatchAny))
+	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (TailMatches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (TailMatches3("ALTER", "MATERIALIZED", "VIEW"))
+	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (TailMatches3("ALTER", "USER|ROLE", MatchAny) &&
+	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
@@ -1380,7 +1395,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1395,43 +1410,43 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
-	else if (TailMatches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (TailMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST3("FOR ROLE", "FOR USER", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (TailMatches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_LIST2("ROLE", "USER");
 	/* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
-	else if (TailMatches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
-			 TailMatches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
+	else if (Matches5("DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER", MatchAny) ||
+			 Matches5("DEFAULT", "PRIVILEGES", "IN", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (TailMatches3("ALTER", "DOMAIN", MatchAny))
+	else if (Matches3("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (TailMatches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (TailMatches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	else if (Matches5("DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (TailMatches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (TailMatches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1440,71 +1455,71 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (TailMatches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (TailMatches3("ALTER", "SERVER", MatchAny))
+	else if (Matches3("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("VERSION", "OPTIONS", "OWNER TO");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (TailMatches2("ALTER", "SYSTEM"))
+	else if (Matches2("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
 	/* ALTER SYSTEM SET|RESET <name> */
-	else if (TailMatches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
+	else if (Matches4("ALTER", "SYSTEM", "SET|RESET", MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	/* ALTER VIEW <name> */
-	else if (TailMatches3("ALTER", "VIEW", MatchAny))
+	else if (Matches3("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (TailMatches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (TailMatches2("ALTER", "POLICY"))
+	else if (Matches2("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (TailMatches3("ALTER", "POLICY", MatchAny))
+	else if (Matches3("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (TailMatches4("ALTER", "POLICY", MatchAny, "ON"))
+	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
-	else if (TailMatches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING", "WITH CHECK");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (TailMatches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (TailMatches3("ALTER", "RULE", MatchAny))
+	else if (Matches3("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (TailMatches4("ALTER", "RULE", MatchAny, "ON"))
+	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
-	else if (TailMatches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (TailMatches3("ALTER", "TRIGGER", MatchAny))
+	else if (Matches3("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1513,17 +1528,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (TailMatches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (TailMatches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (TailMatches3("ALTER", "TABLE", MatchAny))
+	else if (Matches3("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1533,46 +1548,46 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (TailMatches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	else if (Matches4("TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (TailMatches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
@@ -1581,86 +1596,86 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("CASCADE");
 
 	/* ALTER TABLE xxx ALTER */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (TailMatches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
 
 	/*
 	 * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
 	 * columns
 	 */
-	else if (TailMatches4("TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (TailMatches5("TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (TailMatches3("TABLE", MatchAny, "DROP"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/*
 	 * If we have ALTER TABLE <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT,
 	 * provide list of constraints
 	 */
-	else if (TailMatches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
-	else if (TailMatches3("ALTER", "COLUMN", MatchAny) ||
-			 TailMatches4("TABLE", MatchAny, "ALTER", MatchAny))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "SET") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "(") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "("))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (TailMatches5("ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-			 TailMatches4("ALTER", MatchAny, "SET", "STORAGE"))
+	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (TailMatches4("ALTER", "COLUMN", MatchAny, "DROP") ||
-			 TailMatches5("TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+			 Matches8("ALTER", "TABLE", MatchAny, "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL");
-	else if (TailMatches3("TABLE", MatchAny, "CLUSTER"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("TABLE", MatchAny, "CLUSTER", "ON"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	/* If we have TABLE <sth> SET, provide list of attributes and '(' */
-	else if (TailMatches3("TABLE", MatchAny, "SET"))
+	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
+	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
-	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "TABLESPACE"))
+	/* If we have ALTER TABLE <sth> SET TABLESPACE provide a list of tablespaces */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	/* If we have TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (TailMatches4("TABLE", MatchAny, "SET", "WITHOUT"))
+	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (TailMatches3("TABLE", MatchAny, "RESET"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (TailMatches4("TABLE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -1697,54 +1712,54 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (TailMatches5(MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (TailMatches5("TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (TailMatches4("TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (TailMatches3("TABLE", MatchAny, "REPLICA"))
+	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (TailMatches3("ALTER", "TABLESPACE", MatchAny))
+	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (TailMatches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (TailMatches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (TailMatches3("ALTER", "TEXT", "SEARCH"))
+	else if (Matches3("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (TailMatches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
-	else if (TailMatches3("ALTER", "TYPE", MatchAny))
+	else if (Matches3("ALTER", "TYPE", MatchAny))
 		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "ADD"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (TailMatches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "TO");
 	/* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
-	else if (TailMatches5("TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
+	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
@@ -1757,26 +1772,26 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches3("ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (TailMatches3("ALTER", "GROUP", MatchAny))
+	else if (Matches3("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (TailMatches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (TailMatches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
-	else if (TailMatches1("BEGIN|END|ABORT"))
+	else if (Matches1("BEGIN|END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (TailMatches1("COMMIT"))
+	else if (Matches1("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (TailMatches1("RELEASE"))
+	else if (Matches1("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK*/
-	else if (TailMatches1("ROLLBACK"))
+	else if (Matches1("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CLUSTER */
 
@@ -1818,9 +1833,9 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* COMMENT */
-	else if (TailMatches1("COMMENT"))
+	else if (Matches1("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches2("COMMENT", "ON"))
+	else if (Matches2("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EVENT TRIGGER", "EXTENSION",
@@ -1832,26 +1847,26 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (TailMatches3("COMMENT", "ON", "FOREIGN"))
+	else if (Matches3("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (TailMatches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches3("COMMENT", "ON", "CONSTRAINT"))
+	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (TailMatches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (TailMatches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (TailMatches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 TailMatches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+			 Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
+			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1860,7 +1875,7 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY [BINARY] (which you'd have to type yourself), offer
 	 * list of tables (Also cover the analogous backslash command)
 	 */
-	else if (TailMatches1("COPY|\\copy") || TailMatches2("COPY", "BINARY"))
+	else if (Matches1("COPY|\\copy") || Matches2("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
 	else if (TailMatches2("COPY|\\copy|BINARY", MatchAny))
@@ -1883,35 +1898,35 @@ psql_completion(const char *text, int start, int end)
 							"FORCE NOT NULL");
 
 	/* CREATE DATABASE */
-	else if (TailMatches3("CREATE", "DATABASE", MatchAny))
+	else if (Matches3("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (TailMatches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
-	else if (TailMatches2("CREATE", "EXTENSION"))
+	else if (Matches2("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (TailMatches3("CREATE", "EXTENSION", MatchAny))
+	else if (Matches3("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (TailMatches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (TailMatches2("CREATE", "FOREIGN"))
+	else if (Matches2("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (TailMatches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST2("HANDLER", "VALIDATOR");
 
 	/* CREATE INDEX */
@@ -1960,39 +1975,39 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (TailMatches3("CREATE", "POLICY", MatchAny))
+	else if (Matches3("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (TailMatches4("CREATE", "POLICY", MatchAny, "ON"))
+	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
-	else if (TailMatches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
-	else if (TailMatches6("POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING", "WITH CHECK");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (TailMatches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
-	else if (TailMatches3("CREATE", "RULE", MatchAny))
+	else if (Matches3("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (TailMatches4("CREATE", "RULE", MatchAny, "AS"))
+	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "RULE * AS ON" with SELECT|UPDATE|INSERT|DELETE */
 	else if (TailMatches4("RULE", MatchAny, "AS", "ON"))
@@ -2015,7 +2030,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (TailMatches3("CREATE", "SERVER", MatchAny))
+	else if (Matches3("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE TABLE */
@@ -2027,16 +2042,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 
 /* CREATE TABLESPACE */
-	else if (TailMatches3("CREATE", "TABLESPACE", MatchAny))
+	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (TailMatches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (TailMatches3("CREATE", "TEXT", "SEARCH"))
+	else if (Matches3("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (TailMatches4("TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (TailMatches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE TRIGGER */
@@ -2051,14 +2066,14 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
 	else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
-	  TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
+		 Matches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
 	 * tables
 	 */
-	else if (TailMatches5("TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	else if (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
 	else if (TailMatches4("INSTEAD", "OF", MatchAny, "ON"))
@@ -2068,7 +2083,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (TailMatches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
 			 !TailMatches2("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
@@ -2083,7 +2098,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (TailMatches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2101,10 +2116,10 @@ psql_completion(const char *text, int start, int end)
 	 * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
 	 * PASSWORD
 	 */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "ENCRYPTED|UNENCRYPTED"))
 		COMPLETE_WITH_CONST("PASSWORD");
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (TailMatches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW */
@@ -2116,32 +2131,32 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (TailMatches2("CREATE", "MATERIALIZED"))
+	else if (Matches2("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (TailMatches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (TailMatches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (TailMatches2("CREATE", "EVENT"))
+	else if (Matches2("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (TailMatches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (TailMatches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DECLARE */
-	else if (TailMatches2("DECLARE", MatchAny))
+	else if (Matches2("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
 
 /* CURSOR */
-	else if (TailMatches1("CURSOR"))
+	else if (Matches1("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE */
@@ -2158,7 +2173,7 @@ psql_completion(const char *text, int start, int end)
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (TailMatches1("DISCARD"))
+	else if (Matches1("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
@@ -2166,84 +2181,84 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete DO with LANGUAGE.
 	 */
-	else if (TailMatches1("DO"))
+	else if (Matches1("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP (when not the previous word) */
 	/* DROP object with CASCADE / RESTRICT */
-	else if (TailMatches3("DROP",
+	else if (Matches3("DROP",
 						  "COLLATION|CONVERSION|DOMAIN|EXTENSION|INDEX|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 						  MatchAny) ||
-		   (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
+		   (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
 			ends_with(prev_wd, ')')) ||
-			 TailMatches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
-			 TailMatches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
-			 TailMatches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
-			 TailMatches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
+			 Matches4("DROP", "EVENT", "TRIGGER", MatchAny) ||
+			 Matches5("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+			 Matches4("DROP", "FOREIGN", "TABLE", MatchAny) ||
+			 Matches5("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (TailMatches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
+	else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (TailMatches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
+	else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (TailMatches2("DROP", "FOREIGN"))
+	else if (Matches2("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (TailMatches2("DROP", "MATERIALIZED"))
+	else if (Matches2("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("DROP", "MATERIALIZED", "VIEW"))
+	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (TailMatches2("DROP", "OWNED"))
+	else if (Matches2("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("DROP", "OWNED", "BY"))
+	else if (Matches3("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
-	else if (TailMatches3("DROP", "TEXT", "SEARCH"))
+	else if (Matches3("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (TailMatches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches3("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "TRIGGER", MatchAny, "ON"))
+	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (TailMatches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP EVENT TRIGGER */
-	else if (TailMatches2("DROP", "EVENT"))
+	else if (Matches2("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (TailMatches3("DROP", "EVENT", "TRIGGER"))
+	else if (Matches3("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (TailMatches2("DROP", "POLICY"))
+	else if (Matches2("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (TailMatches3("DROP", "POLICY", MatchAny))
+	else if (Matches3("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (TailMatches4("DROP", "POLICY", MatchAny, "ON"))
+	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (TailMatches3("DROP", "RULE", MatchAny))
+	else if (Matches3("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (TailMatches4("DROP", "RULE", MatchAny, "ON"))
+	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (TailMatches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
@@ -2255,22 +2270,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (TailMatches1("EXPLAIN"))
+	else if (Matches1("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (TailMatches2("EXPLAIN", "ANALYZE"))
+	else if (Matches2("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (TailMatches2("EXPLAIN", "VERBOSE") ||
-			 TailMatches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	else if (Matches2("EXPLAIN", "VERBOSE") ||
+			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (TailMatches1("FETCH|MOVE"))
+	else if (Matches1("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (TailMatches2("FETCH|MOVE", MatchAny))
+	else if (Matches2("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2278,7 +2293,7 @@ psql_completion(const char *text, int start, int end)
 	 * but we may as well tab-complete both: perhaps some users prefer one
 	 * variant or the other.
 	 */
-	else if (TailMatches3("FETCH|MOVE", MatchAny, MatchAny))
+	else if (Matches3("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
@@ -2442,9 +2457,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (TailMatches1("IMPORT"))
+	else if (Matches1("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (TailMatches2("IMPORT", "FOREIGN"))
+	else if (Matches2("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT */
@@ -2479,22 +2494,22 @@ psql_completion(const char *text, int start, int end)
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (TailMatches1("LOCK"))
+	else if (Matches1("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (TailMatches2("LOCK", "TABLE"))
+	else if (Matches2("LOCK", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] <table> with "IN" */
-	else if (TailMatches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 TailMatches3("LOCK", "TABLE", MatchAny))
+	else if (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
+			 Matches3("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (TailMatches3("LOCK", MatchAny, "IN") ||
-			 TailMatches4("LOCK", "TABLE", MatchAny, "IN"))
+	else if (Matches3("LOCK", MatchAny, "IN") ||
+			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -2502,7 +2517,7 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 /* NOTIFY */
-	else if (TailMatches1("NOTIFY"))
+	else if (Matches1("NOTIFY"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
 
 /* OPTIONS */
@@ -2520,7 +2535,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (TailMatches3("PREPARE", MatchAny, "AS"))
+	else if (Matches3("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 
 /*
@@ -2529,42 +2544,42 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (TailMatches1("REASSIGN"))
+	else if (Matches1("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (TailMatches2("REASSIGN", "OWNED"))
+	else if (Matches2("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches3("REASSIGN", "OWNED", "BY"))
+	else if (Matches3("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatches4("REASSIGN", "OWNED", "BY", MatchAny))
+	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (TailMatches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (TailMatches1("REFRESH"))
+	else if (Matches1("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (TailMatches2("REFRESH", "MATERIALIZED"))
+	else if (Matches2("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (TailMatches3("REFRESH", "MATERIALIZED", "VIEW"))
+	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH DATA");
-	else if (TailMatches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
 	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (TailMatches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (TailMatches1("REINDEX"))
+	else if (Matches1("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (TailMatches2("REINDEX", MatchAny))
+	else if (Matches2("REINDEX", MatchAny))
 	{
 		if (TailMatches1("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
@@ -2602,13 +2617,13 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !Matches3("UPDATE", MatchAny, "SET"))
+	else if (Matches1("SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (TailMatches1("SHOW"))
+	else if (Matches1("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (TailMatches2("SET|BEGIN|START", "TRANSACTION") ||
-			 TailMatches2("BEGIN", "WORK") ||
+	else if (Matches2("SET|BEGIN|START", "TRANSACTION") ||
+			 Matches2("BEGIN", "WORK") ||
 			 TailMatches4("SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST2("ISOLATION LEVEL", "READ");
 	else if (TailMatches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
@@ -2626,19 +2641,19 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches2("SET", "CONSTRAINTS"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
-	else if (TailMatches3("SET", "CONSTRAINTS", MatchAny))
+	else if (Matches3("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (TailMatches2("SET", "ROLE"))
+	else if (Matches2("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (TailMatches2("SET", "SESSION"))
+	else if (Matches2("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (TailMatches3("SET", "SESSION", "AUTHORIZATION"))
+	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (TailMatches2("RESET", "SESSION"))
+	else if (Matches2("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
 	else if (Matches2("SET", MatchAny))
@@ -2701,11 +2716,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (TailMatches1("TRUNCATE"))
+	else if (Matches1("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (TailMatches1("UNLISTEN"))
+	else if (Matches1("UNLISTEN"))
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
@@ -2729,16 +2744,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (TailMatches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (TailMatches4("CREATE", "USER", "MAPPING", "FOR"))
+	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (TailMatches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
 
 /*
-- 
2.6.4

0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchtext/x-patch; charset=US-ASCII; name=0002-Adopt-more-compact-tab-completion-for-backslash-comm.patchDownload
From 21aca0b42b8ff07cd4cc4d232a29296476ddb382 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 30 Dec 2015 21:13:01 +0900
Subject: [PATCH 2/2] Adopt more compact tab completion for backslash commands
 in psql

This upgrades a bit the existing psql facility so as case-sensitive
comparisons can be done, which is a requirement contrary to other query
types that do not need to mind about that.
---
 src/bin/psql/tab-complete.c | 201 ++++++++++++++++++++++++++------------------
 1 file changed, 120 insertions(+), 81 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2472a99..e350d6f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -952,10 +952,12 @@ initialize_readline(void)
 
 /*
  * Check if 'word' matches any of the '|'-separated strings in 'pattern',
- * using case-insensitive comparisons.
+ * using case-insensitive or case-sensitive comparisons.
  * If pattern is NULL, it's a wild card that matches any word.
  * If pattern begins with "!", the result is negated, ie we check that 'word'
  * does *not* match any alternative appearing in the rest of 'pattern'.
+ * If pattern finishes with a trailing wild card "*", it can match with any
+ * words beginning by the same characters up to this point.
  *
  * For readability, callers should use the macros MatchAny and MatchAnyExcept
  * to invoke the two special cases for 'pattern'.
@@ -963,10 +965,18 @@ initialize_readline(void)
 #define MatchAny  NULL
 #define MatchAnyExcept(pattern)  ("!" pattern)
 
+/*
+ * word_matches_internal
+ *
+ * Internal worker routine to check if a word matches a given pattern. Caller
+ * can optionally decide to make the check case-sensitive or not.
+ */
 static bool
-word_matches(const char *pattern, const char *word)
+word_matches_internal(const char *pattern,
+					  const char *word,
+					  bool case_sensitive)
 {
-	size_t		wordlen;
+	size_t		wordlen, patternlen;
 
 	/* NULL pattern matches anything. */
 	if (pattern == NULL)
@@ -974,22 +984,36 @@ word_matches(const char *pattern, const char *word)
 
 	/* Handle negated patterns from the MatchAnyExcept macro. */
 	if (*pattern == '!')
-		return !word_matches(pattern + 1, word);
+		return !word_matches_internal(pattern + 1, word, case_sensitive);
 
 	/* Else consider each alternative in the pattern. */
 	wordlen = strlen(word);
+	patternlen = strlen(pattern);
+
+	/*
+	 * Check if a trailing wild card is used in the pattern and adjust
+	 * the length to be evaluated to be just the pattern without its
+	 * wild card.
+	 */
+	if (pattern[patternlen - 1] == '*')
+		wordlen = patternlen - 1;
+
 	for (;;)
 	{
 		const char *c;
 
 		/* Find end of current alternative. */
 		c = pattern;
-		while (*c != '\0' && *c != '|')
+		while (*c != '\0' && *c != '|' && *c != '*')
 			c++;
 		/* Match? */
-		if (wordlen == (c - pattern) &&
-			pg_strncasecmp(word, pattern, wordlen) == 0)
-			return true;
+		if (wordlen == (c - pattern))
+		{
+			/* Do the pattern comparison, depending on the sensitiveness */
+			if ((!case_sensitive && pg_strncasecmp(word, pattern, wordlen) == 0) ||
+				(case_sensitive && strncmp(word, pattern, wordlen) == 0))
+				return true;
+		}
 		/* Out of alternatives? */
 		if (*c == '\0')
 			break;
@@ -1001,6 +1025,26 @@ word_matches(const char *pattern, const char *word)
 }
 
 /*
+ * word_matches
+ * Utility routine to match a word with a pattern as case-insensitive.
+ */
+static bool
+word_matches(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, false);
+}
+
+/*
+ * word_matches_cs
+ * Utility routine to match a word with a patterm as case-sensitive.
+ */
+static bool
+word_matches_cs(const char *pattern, const char *word)
+{
+	return word_matches_internal(pattern, word, true);
+}
+
+/*
  * Check if the final character of 's' is 'c'.
  */
 static bool
@@ -1172,6 +1216,18 @@ psql_completion(const char *text, int start, int end)
 	 word_matches(p2, previous_words[previous_words_count - 2]) && \
 	 word_matches(p3, previous_words[previous_words_count - 3]))
 
+	/*
+	 * Macros for matching N words before point with case-sensitive
+	 * comparison.
+	 */
+#define TailMatchesCS1(p1) \
+	(previous_words_count >= 1 && \
+	 word_matches_cs(p1, prev_wd))
+#define TailMatchesCS2(p2, p1) \
+	(previous_words_count >= 2 && \
+	 word_matches_cs(p1, prev_wd) && \
+	 word_matches_cs(p2, prev2_wd))
+
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
@@ -2815,95 +2871,91 @@ psql_completion(const char *text, int start, int end)
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (strcmp(prev_wd, "\\?") == 0)
+	else if (TailMatchesCS1("\\?"))
 	{
 		static const char *const my_list[] =
 		{"commands", "options", "variables", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
+	else if (TailMatchesCS1("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (previous_words_count >= 2 &&
-			 (strcmp(prev2_wd, "\\connect") == 0 ||
-			  strcmp(prev2_wd, "\\c") == 0))
+	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
+	else if (TailMatchesCS1("\\da*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
+	else if (TailMatchesCS1("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
+	else if (TailMatchesCS1("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+	else if (TailMatchesCS1("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+	else if (TailMatchesCS1("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+	else if (TailMatchesCS1("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
-	else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
+	else if (TailMatchesCS1("\\df*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+	else if (TailMatchesCS1("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+	else if (TailMatchesCS1("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+	else if (TailMatchesCS1("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF */
-	else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+	else if (TailMatchesCS1("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
+	else if (TailMatchesCS1("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+	else if (TailMatchesCS1("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
+	else if (TailMatchesCS1("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
-			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
+	else if (TailMatchesCS1("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
+	else if (TailMatchesCS1("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
+	else if (TailMatchesCS1("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
-			 || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
+	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
+	else if (TailMatchesCS1("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+	else if (TailMatchesCS1("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+	else if (TailMatchesCS1("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (strncmp(prev_wd, "\\dE", strlen("\\dE")) == 0)
+	else if (TailMatchesCS1("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (strncmp(prev_wd, "\\dy", strlen("\\dy")) == 0)
+	else if (TailMatchesCS1("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d list */
-	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+	else if (TailMatchesCS1("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (strcmp(prev_wd, "\\ef") == 0)
+	else if (TailMatchesCS1("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\ev") == 0)
+	else if (TailMatchesCS1("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (strcmp(prev_wd, "\\encoding") == 0)
+	else if (TailMatchesCS1("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
+	else if (TailMatchesCS1("\\h") || TailMatchesCS1("\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (strcmp(prev_wd, "\\password") == 0)
+	else if (TailMatchesCS1("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (strcmp(prev_wd, "\\pset") == 0)
+	else if (TailMatchesCS1("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -2914,10 +2966,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\pset") == 0)
+	else if (TailMatchesCS2("\\pset", MatchAny))
 	{
-		if (strcmp(prev_wd, "format") == 0)
+		if (TailMatchesCS1("format"))
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -2925,16 +2976,14 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "linestyle") == 0)
+		else if (TailMatchesCS1("linestyle"))
 		{
 			static const char *const my_list[] =
 			{"ascii", "old-ascii", "unicode", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "unicode_border_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_column_linestyle") == 0 ||
-				 strcmp(prev_wd, "unicode_header_linestyle") == 0)
+		else if (TailMatchesCS1("unicode_border_linestyle|unicode_column_linestyle|unicode_header_linestyle"))
 		{
 			static const char *const my_list[] =
 			{"single", "double", NULL};
@@ -2943,73 +2992,72 @@ psql_completion(const char *text, int start, int end)
 
 		}
 	}
-	else if (strcmp(prev_wd, "\\unset") == 0)
+	else if (TailMatchesCS1("\\unset"))
 	{
 		matches = complete_from_variables(text, "", "", true);
 	}
-	else if (strcmp(prev_wd, "\\set") == 0)
+	else if (TailMatchesCS1("\\set"))
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (previous_words_count >= 2 &&
-			 strcmp(prev2_wd, "\\set") == 0)
+	else if (TailMatchesCS2("\\set", MatchAny))
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
 
-		if (strcmp(prev_wd, "AUTOCOMMIT") == 0)
+		if (TailMatchesCS1("AUTOCOMMIT"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "COMP_KEYWORD_CASE") == 0)
+		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
 		{
 			static const char *const my_list[] =
 			{"lower", "upper", "preserve-lower", "preserve-upper", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO") == 0)
+		else if (TailMatchesCS1("ECHO"))
 		{
 			static const char *const my_list[] =
 			{"errors", "queries", "all", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ECHO_HIDDEN") == 0)
+		else if (TailMatchesCS1("ECHO_HIDDEN"))
 		{
 			static const char *const my_list[] =
 			{"noexec", "off", "on", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "HISTCONTROL") == 0)
+		else if (TailMatchesCS1("HISTCONTROL"))
 		{
 			static const char *const my_list[] =
 			{"ignorespace", "ignoredups", "ignoreboth", "none", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_ROLLBACK") == 0)
+		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
 		{
 			static const char *const my_list[] =
 			{"on", "off", "interactive", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "ON_ERROR_STOP") == 0)
+		else if (TailMatchesCS1("ON_ERROR_STOP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "QUIET") == 0)
+		else if (TailMatchesCS1("QUIET"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SHOW_CONTEXT") == 0)
+		else if (TailMatchesCS1("SHOW_CONTEXT"))
 		{
 			static const char *const my_list[] =
 			{"never", "errors", "always", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (strcmp(prev_wd, "SINGLELINE") == 0)
+		else if (TailMatchesCS1("SINGLELINE"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "SINGLESTEP") == 0)
+		else if (TailMatchesCS1("SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS(boolean_value_list);
-		else if (strcmp(prev_wd, "VERBOSITY") == 0)
+		else if (TailMatchesCS1("VERBOSITY"))
 		{
 			static const char *const my_list[] =
 			{"default", "verbose", "terse", NULL};
@@ -3017,20 +3065,11 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 	}
-	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+	else if (TailMatchesCS1("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+	else if (TailMatchesCS1("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (strcmp(prev_wd, "\\cd") == 0 ||
-			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
-			 strcmp(prev_wd, "\\g") == 0 ||
-		  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-			 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
-			 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
-			 strcmp(prev_wd, "\\s") == 0 ||
-			 strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0 ||
-			 strcmp(prev_wd, "\\lo_import") == 0
-		)
+	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|\\ir|\\include_relative|\\o|\\out|\\s|\\w|\\write|\\lo_import"))
 	{
 		completion_charp = "\\";
 		matches = completion_matches(text, complete_from_files);
-- 
2.6.4

#82Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#81)
Re: Making tab-complete.c easier to maintain

Michael Paquier <michael.paquier@gmail.com> writes:

So, here are the commands that still remain with TailMatches to cover
this case, per gram.y:
- CREATE TABLE
- CREATE INDEX
- CREATE VIEW
- GRANT
- CREATE TRIGGER
- CREATE SEQUENCE
New patches are attached.

I've reviewed and committed the first of these patches. I found a few
mistakes, mostly where you'd converted TailMatches to Matches without
adding the leading words that the original author had left out of the
pattern. But man, this is mind-numbingly tedious work :-(. I probably
made a few more mistakes myself. Still, the code is enormously more
readable now than when we started, and almost certainly more reliable.

I'm too burned out to look at the second patch tonight, but hopefully
will get to it tomorrow.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#82)
Re: Making tab-complete.c easier to maintain

On Tue, Jan 5, 2016 at 10:13 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

So, here are the commands that still remain with TailMatches to cover
this case, per gram.y:
- CREATE TABLE
- CREATE INDEX
- CREATE VIEW
- GRANT
- CREATE TRIGGER
- CREATE SEQUENCE
New patches are attached.

I've reviewed and committed the first of these patches. I found a few
mistakes, mostly where you'd converted TailMatches to Matches without
adding the leading words that the original author had left out of the
pattern. But man, this is mind-numbingly tedious work :-(. I probably
made a few more mistakes myself. Still, the code is enormously more
readable now than when we started, and almost certainly more reliable.

Thanks. My best advice is to avoid doing such after 10PM, that's a
good way to finish with a headache. I did it.

I'm too burned out to look at the second patch tonight, but hopefully
will get to it tomorrow.

I see that you are on fire these days, still bodies need some rest.
This second one still applies cleanly, but it is far less urgent, the
other psql-tab patches do not depend on it.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#82)
Re: Making tab-complete.c easier to maintain

I wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

So, here are the commands that still remain with TailMatches to cover
this case, per gram.y:
- CREATE TABLE
- CREATE INDEX
- CREATE VIEW
- GRANT
- CREATE TRIGGER
- CREATE SEQUENCE
New patches are attached.

I've reviewed and committed the first of these patches.

I've pushed the second patch now. I made a few adjustments --- notably,
I didn't like the way you'd implemented '*' wildcards, because they
wouldn't have behaved very sanely in combination with '|'. The case
doesn't come up in the current set of patterns, but we'll likely want it
sometime.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#84)
Re: Making tab-complete.c easier to maintain

On Wed, Jan 6, 2016 at 2:03 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

So, here are the commands that still remain with TailMatches to cover
this case, per gram.y:
- CREATE TABLE
- CREATE INDEX
- CREATE VIEW
- GRANT
- CREATE TRIGGER
- CREATE SEQUENCE
New patches are attached.

I've reviewed and committed the first of these patches.

I've pushed the second patch now. I made a few adjustments --- notably,
I didn't like the way you'd implemented '*' wildcards, because they
wouldn't have behaved very sanely in combination with '|'. The case
doesn't come up in the current set of patterns, but we'll likely want it
sometime.

Thanks! Let's consider this project as done then. That was a long way to it...
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Andreas Karlsson
andreas@proxel.se
In reply to: Michael Paquier (#85)
Re: Making tab-complete.c easier to maintain

On 01/06/2016 01:13 AM, Michael Paquier wrote:

On Wed, Jan 6, 2016 at 2:03 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I've pushed the second patch now. I made a few adjustments --- notably,
I didn't like the way you'd implemented '*' wildcards, because they
wouldn't have behaved very sanely in combination with '|'. The case
doesn't come up in the current set of patterns, but we'll likely want it
sometime.

Thanks! Let's consider this project as done then. That was a long way to it...

Thanks to everyone involved in cleaning this up, it is much easier to
add tab completions now.

Andreas

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers