agora inbox for [email protected]  
help / color / mirror / Atom feed
Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
40+ messages / 11 participants
[nested] [flat]

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-14 13:12  Arthur Zakirov <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Arthur Zakirov @ 2018-09-14 13:12 UTC (permalink / raw)
  To: Dagfinn Ilmari Mannsåker <[email protected]>; +Cc: pgsql-hackers

On Thu, Aug 30, 2018 at 05:54:23PM +0100, Dagfinn Ilmari Mannsåker wrote:
> [email protected] (Dagfinn Ilmari Mannsåker) writes:
> > Hi hackers,
> >
> > I just noticed that psql's tab completion for ALTER TABLE … SET
> > TABLESPACE was treating it as any other configuration parameter and
> > completing with FROM DEFAULT or TO after it, instead of a list of
> > tablespaces.
> 
> And just after hitting send, I noticed I'd typed ALTER TABLE instead of
> ALTER DATABASE, including in the commit message :(
> 
> Fixed patch attached.

The patch seems reasonable. It fixes the lack of tab completion for
ALTER DATABASE ... SET TABLESPACE ... .

There is no need to patch the documentation and regression tests.

Marked as Ready for Commiter.

-- 
Arthur Zakirov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 21:22  Tom Lane <[email protected]>
  parent: Arthur Zakirov <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-20 21:22 UTC (permalink / raw)
  To: Arthur Zakirov <[email protected]>; +Cc: Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Arthur Zakirov <[email protected]> writes:
> On Thu, Aug 30, 2018 at 05:54:23PM +0100, Dagfinn Ilmari Mannsåker wrote:
>> And just after hitting send, I noticed I'd typed ALTER TABLE instead of
>> ALTER DATABASE, including in the commit message :(
>> Fixed patch attached.

> The patch seems reasonable. It fixes the lack of tab completion for
> ALTER DATABASE ... SET TABLESPACE ... .

LGTM too, pushed.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

*  Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 21:29  Thomas Munro <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2018-09-20 21:29 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On Fri, Sep 21, 2018 at 9:22 AM Tom Lane <[email protected]> wrote:
> Arthur Zakirov <[email protected]> writes:
> > On Thu, Aug 30, 2018 at 05:54:23PM +0100, Dagfinn Ilmari Mannsåker wrote:
> >> And just after hitting send, I noticed I'd typed ALTER TABLE instead of
> >> ALTER DATABASE, including in the commit message :(
> >> Fixed patch attached.
>
> > The patch seems reasonable. It fixes the lack of tab completion for
> > ALTER DATABASE ... SET TABLESPACE ... .
>
> LGTM too, pushed.

+   else if (Matches5("ALTER", "DATABASE", MatchAny, "SET", "TABLESPACE"))

. o O ( hmm, we now have variadic macros )

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




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 22:38  Tom Lane <[email protected]>
  parent: Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-20 22:38 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Thomas Munro <[email protected]> writes:
> . o O ( hmm, we now have variadic macros )

hmmm ... but even with variadic, C's macro facility is so weak that
I'm not sure we can reimplement these with it.  What would the
expansion look like?

(It constantly annoys me that C's so weak here.  In the language
I used for my first for-pay programming work, Bliss/11, the macro
facility could have done that easily.  That was 45 years ago.)

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 22:46  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2018-09-20 22:46 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On 2018-09-20 18:38:36 -0400, Tom Lane wrote:
> Thomas Munro <[email protected]> writes:
> > . o O ( hmm, we now have variadic macros )
> 
> hmmm ... but even with variadic, C's macro facility is so weak that
> I'm not sure we can reimplement these with it.  What would the
> expansion look like?

There's a dirty hack to count arguments in vararg macros:
https://groups.google.com/d/msg/comp.std.c/d-6Mj5Lko_s/5fW1bP6T3RIJ

Afaict that ought to be enough to implement something like the current
macros? Whether that's too ugly or not, I don't know.


> It constantly annoys me that C's so weak here.

Yea. Weak and fragile, both.


Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 23:03  Tom Lane <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-20 23:03 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Andres Freund <[email protected]> writes:
> On 2018-09-20 18:38:36 -0400, Tom Lane wrote:
>> hmmm ... but even with variadic, C's macro facility is so weak that
>> I'm not sure we can reimplement these with it.  What would the
>> expansion look like?

> There's a dirty hack to count arguments in vararg macros:
> https://groups.google.com/d/msg/comp.std.c/d-6Mj5Lko_s/5fW1bP6T3RIJ

Doesn't seem to help for this case.  What we really want is to expand
a given pattern once for each variadic argument, and I don't see how
to get there from here.

Although maybe I'm thinking too much inside-the-box.  The expansion
doesn't necessarily have to be identical to the code the macros
generate today.  In fact, that code is kinda bulky.  I wonder if
we could go over to something involving a variadic function, or
maybe an array of string-pointer constants?

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-20 23:19  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2018-09-20 23:19 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On 2018-09-20 19:03:16 -0400, Tom Lane wrote:
> Andres Freund <[email protected]> writes:
> > On 2018-09-20 18:38:36 -0400, Tom Lane wrote:
> >> hmmm ... but even with variadic, C's macro facility is so weak that
> >> I'm not sure we can reimplement these with it.  What would the
> >> expansion look like?
> 
> > There's a dirty hack to count arguments in vararg macros:
> > https://groups.google.com/d/msg/comp.std.c/d-6Mj5Lko_s/5fW1bP6T3RIJ
> 
> Doesn't seem to help for this case.  What we really want is to expand
> a given pattern once for each variadic argument, and I don't see how
> to get there from here.

Depends on whether your goal is to simplify *using* the macro, or the
infrastructure for the macro.  Afaict it should be relatively
straightforward to use a, possibly simplified, macro like referenced
above to have TailMatches(...), TailMatchesCS(...), Matches(...) that
then expand to the current *N macros.


> Although maybe I'm thinking too much inside-the-box.  The expansion
> doesn't necessarily have to be identical to the code the macros
> generate today.  In fact, that code is kinda bulky.  I wonder if
> we could go over to something involving a variadic function, or
> maybe an array of string-pointer constants?

Yea, that might be a way to simplify both the macros and the use of the
macros.  Assuming we have something like PP_NARG, ISTM it should be
relatively straightforward to define something like

#define Matches(...) CheckMatchesFor(PP_NARG(__VA_ARGS__), __VA_ARGS__)

and then have a CheckMatchesFor() first check previous_words_count, and
then just have a simple for loop through previous_words[] - afaict the
number of arguments ought to suffice to make that possible?

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 05:52  Andres Freund <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2018-09-21 05:52 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On 2018-09-20 16:19:26 -0700, Andres Freund wrote:
> On 2018-09-20 19:03:16 -0400, Tom Lane wrote:
> > Andres Freund <[email protected]> writes:
> > > On 2018-09-20 18:38:36 -0400, Tom Lane wrote:
> > >> hmmm ... but even with variadic, C's macro facility is so weak that
> > >> I'm not sure we can reimplement these with it.  What would the
> > >> expansion look like?
> > 
> > > There's a dirty hack to count arguments in vararg macros:
> > > https://groups.google.com/d/msg/comp.std.c/d-6Mj5Lko_s/5fW1bP6T3RIJ
> > 
> > Doesn't seem to help for this case.  What we really want is to expand
> > a given pattern once for each variadic argument, and I don't see how
> > to get there from here.
> 
> Depends on whether your goal is to simplify *using* the macro, or the
> infrastructure for the macro.  Afaict it should be relatively
> straightforward to use a, possibly simplified, macro like referenced
> above to have TailMatches(...), TailMatchesCS(...), Matches(...) that
> then expand to the current *N macros.
> 
> 
> > Although maybe I'm thinking too much inside-the-box.  The expansion
> > doesn't necessarily have to be identical to the code the macros
> > generate today.  In fact, that code is kinda bulky.  I wonder if
> > we could go over to something involving a variadic function, or
> > maybe an array of string-pointer constants?
> 
> Yea, that might be a way to simplify both the macros and the use of the
> macros.  Assuming we have something like PP_NARG, ISTM it should be
> relatively straightforward to define something like
> 
> #define Matches(...) CheckMatchesFor(PP_NARG(__VA_ARGS__), __VA_ARGS__)
> 
> and then have a CheckMatchesFor() first check previous_words_count, and
> then just have a simple for loop through previous_words[] - afaict the
> number of arguments ought to suffice to make that possible?

Here's a very quick-and-dirty implementation of this approach. Some very
very brief testing seems to indicate it works, although I'm sure not
perfectly.

The current duplication of the new functions doing the actual checking
(like CheckMatchesFor(), CheckTailMatchesFor()) would probably need to
be reduced. But I don't want to invest actual time before we decide that
this could be something we'd actually want to pursue.

Greetings,

Andres Freund


Attachments:

  [text/x-diff] varargify-tab-complete.diff (125.3K, ../../[email protected]/2-varargify-tab-complete.diff)
  download | inline diff:
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 47a1a19688f..21a4a807d93 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1220,6 +1220,110 @@ ends_with(const char *s, char c)
 	return (length > 0 && s[length - 1] == c);
 }
 
+static bool
+CheckTailMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count != narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckTailMatchesCSFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches_cs(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckHeadMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[previous_words_count - (argno + 1)]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
 /*
  * The completion function.
  *
@@ -1260,149 +1364,23 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+		/* Macros for matching the last N words before point, case-insensitively. */
+#define TailMatches(...) CheckTailMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
+
 	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 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 TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 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 TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 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 TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 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 TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 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) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#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))
-#define TailMatchesCS3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd) && \
-	 word_matches_cs(p3, prev3_wd))
-#define TailMatchesCS4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd) && \
-	 word_matches_cs(p3, prev3_wd) && \
-	 word_matches_cs(p4, prev4_wd))
+#define TailMatchesCS(...) CheckTailMatchesCSFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/*
 	 * Macros for matching N words beginning at the start of the line,
 	 * case-insensitively.
 	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#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))
+#define Matches(...) CheckMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
 	 * what is after them, case-insensitively.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
-
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
-
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+#define HeadMatches(...) CheckHeadMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1492,38 +1470,38 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (TailMatches("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
+	else if (Matches("DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	else if (Matches("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	else if (Matches("ALTER"))
 		matches = completion_matches(text, alter_command_generator);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
+	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
+	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1531,107 +1509,107 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	}
 	/* ALTER PUBLICATION <name> */
-	else if (Matches3("ALTER", "PUBLICATION", MatchAny))
+	else if (Matches("ALTER", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_LIST5("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
 	/* ALTER PUBLICATION <name> SET */
-	else if (Matches4("ALTER", "PUBLICATION", MatchAny, "SET"))
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLE");
 	/* ALTER PUBLICATION <name> SET ( */
-	else if (HeadMatches3("ALTER", "PUBLICATION", MatchAny) && TailMatches2("SET", "("))
+	else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
 		COMPLETE_WITH_CONST("publish");
 	/* ALTER SUBSCRIPTION <name> */
-	else if (Matches3("ALTER", "SUBSCRIPTION", MatchAny))
+	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH_LIST7("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
 							"RENAME TO", "REFRESH PUBLICATION", "SET");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches2("REFRESH", "PUBLICATION"))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("REFRESH", "PUBLICATION"))
 		COMPLETE_WITH_CONST("WITH (");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches4("REFRESH", "PUBLICATION", "WITH", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("REFRESH", "PUBLICATION", "WITH", "("))
 		COMPLETE_WITH_CONST("copy_data");
 	/* ALTER SUBSCRIPTION <name> SET */
-	else if (Matches4("ALTER", "SUBSCRIPTION", MatchAny, "SET"))
+	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "PUBLICATION");
 	/* ALTER SUBSCRIPTION <name> SET ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("SET", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "("))
 		COMPLETE_WITH_LIST2("slot_name", "synchronous_commit");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("SET", "PUBLICATION"))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "PUBLICATION"))
 	{
 		/* complete with nothing here as this refers to remote publications */
 	}
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches3("SET", "PUBLICATION", MatchAny))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("SET", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_CONST("WITH (");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> WITH ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches5("SET", "PUBLICATION", MatchAny, "WITH", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("SET", "PUBLICATION", MatchAny, "WITH", "("))
 		COMPLETE_WITH_LIST2("copy_data", "refresh");
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	else if (Matches("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	else if (Matches("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	else if (Matches("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	else if (Matches("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER DATABASE <name> SET TABLESPACE */
-	else if (Matches5("ALTER", "DATABASE", MatchAny, "SET", "TABLESPACE"))
+	else if (Matches("ALTER", "DATABASE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	else if (Matches("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	else if (Matches("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	else if (Matches("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	else if (Matches("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1642,35 +1620,35 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	else if (Matches("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	else if (Matches("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST6("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
 							"RESET", "ATTACH PARTITION");
-	else if (Matches4("ALTER", "INDEX", MatchAny, "ATTACH"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH"))
 		COMPLETE_WITH_CONST("PARTITION");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	/* ALTER INDEX <name> ALTER COLUMN <colnum> */
-	else if (Matches6("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
 		COMPLETE_WITH_CONST("SET STATISTICS");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	else if (Matches("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST8("fillfactor", "recheck_on_update",
 							"vacuum_cleanup_index_scale_factor",	/* BTREE */
 							"fastupdate", "gin_pending_list_limit", /* GIN */
 							"buffering",	/* GiST */
 							"pages_per_range", "autosummarize"	/* BRIN */
 			);
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	else if (Matches("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST8("fillfactor =", "recheck_on_update =",
 							"vacuum_cleanup_index_scale_factor =",	/* BTREE */
 							"fastupdate =", "gin_pending_list_limit =", /* GIN */
@@ -1679,21 +1657,21 @@ psql_completion(const char *text, int start, int end)
 			);
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	else if (Matches("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	else if (Matches("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	else if (Matches("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	else if (Matches("ALTER", "USER|ROLE", MatchAny) &&
+			 !TailMatches("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1707,7 +1685,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	else if (Matches("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1722,58 +1700,58 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST2("FOR ROLE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "FOR ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR */
-	else if (Matches7("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... IN SCHEMA ... */
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR ROLE|USER ... */
-	else if (Matches9("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny, "IN", "SCHEMA", MatchAny) ||
-			 Matches9("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+			 Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR", "ROLE|USER", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	else if (Matches("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches("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 (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1782,79 +1760,79 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	else if (Matches("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	else if (Matches("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	else if (Matches("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	else if (Matches("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	else if (Matches4("ALTER", "SYSTEM", "SET", MatchAny))
+	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	else if (Matches("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	else if (Matches("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	else if (Matches("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	else if (Matches("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 (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	else if (Matches("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	else if (Matches("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 (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER STATISTICS <name> */
-	else if (Matches3("ALTER", "STATISTICS", MatchAny))
+	else if (Matches("ALTER", "STATISTICS", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1863,17 +1841,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	else if (Matches("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1884,114 +1862,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	else if (Matches("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 (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	else if (Matches("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 (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches("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 (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST6("TYPE", "SET", "RESET", "RESTART", "ADD", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("DEFAULT", "IDENTITY", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1999,19 +1977,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -2050,146 +2028,146 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/*
 	 * If we have ALTER TABLE <foo> ATTACH PARTITION, provide a list of
 	 * tables.
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
-	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
+	else if (TailMatches("ATTACH", "PARTITION", MatchAny))
 		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
-	else if (TailMatches2("FOR", "VALUES"))
+	else if (TailMatches("FOR", "VALUES"))
 		COMPLETE_WITH_LIST3("FROM (", "IN (", "WITH (");
 
 	/*
 	 * If we have ALTER TABLE <foo> DETACH PARTITION, provide a list of
 	 * partitions of <foo>.
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_partition_of_table);
 	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	else if (Matches("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	else if (Matches("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	else if (Matches("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches("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 (Matches3("ALTER", "TYPE", MatchAny))
+	else if (Matches("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 (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	else if (Matches("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	else if (Matches("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	else if (Matches("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	else if (Matches("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	else if (Matches("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	else if (Matches("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CALL */
-	else if (Matches1("CALL"))
+	else if (Matches("CALL"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
-	else if (Matches2("CALL", MatchAny))
+	else if (Matches("CALL", MatchAny))
 		COMPLETE_WITH_CONST("(");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	else if (Matches("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	else if (Matches("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	else if (Matches("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	else if (Matches("CLUSTER", MatchAny, "USING") ||
+			 Matches("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	else if (Matches("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	else if (Matches("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -2204,28 +2182,28 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	else if (Matches("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	else if (Matches("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	else if (Matches("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	else if (Matches("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-			 Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
+			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2234,92 +2212,92 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	else if (Matches("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	else if (Matches("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	else if (Matches("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	else if (Matches("COPY|\\copy", MatchAny) ||
+			 Matches("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO") ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	else if (Matches("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	else if (Matches("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 (Matches2("CREATE", "EXTENSION"))
+	else if (Matches("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	else if (Matches("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	else if (Matches("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	else if (TailMatches("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
@@ -2328,294 +2306,294 @@ psql_completion(const char *text, int start, int end)
 	 * Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of relations
 	 * that can indexes can be created on
 	 */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
+	else if (TailMatches("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm, NULL);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) ||
+			 TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny) ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	else if (TailMatches("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	else if (TailMatches("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+			 TailMatches("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+			 TailMatches("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	else if (TailMatches("ON", MatchAny, "USING", MatchAny) &&
+			 !TailMatches("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
+			 !TailMatches("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST5("AS", "FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
 		COMPLETE_WITH_LIST2("PERMISSIVE", "RESTRICTIVE");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
 	 * FOR|TO|USING|WITH CHECK
 	 */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches("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 (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * INSERT TO|WITH CHECK"
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * SELECT|DELETE TO|USING"
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|UPDATE TO|USING|WITH CHECK
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE TO
 	 * <role>"
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
 	 * USING ("
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 
 /* CREATE PUBLICATION */
-	else if (Matches3("CREATE", "PUBLICATION", MatchAny))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_LIST3("FOR TABLE", "FOR ALL TABLES", "WITH (");
-	else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR"))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST2("TABLE", "ALL TABLES");
 	/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>" */
-	else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR TABLE"))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE PUBLICATION <name> [...] WITH" */
-	else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches2("WITH", "("))
+	else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
 		COMPLETE_WITH_CONST("publish");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	else if (Matches("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	else if (Matches("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	else if (Matches("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-			 TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "NO") ||
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	else if (Matches("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE STATISTICS <name> */
-	else if (Matches3("CREATE", "STATISTICS", MatchAny))
+	else if (Matches("CREATE", "STATISTICS", MatchAny))
 		COMPLETE_WITH_LIST2("(", "ON");
-	else if (Matches4("CREATE", "STATISTICS", MatchAny, "("))
+	else if (Matches("CREATE", "STATISTICS", MatchAny, "("))
 		COMPLETE_WITH_LIST2("ndistinct", "dependencies");
-	else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) &&
+	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
 			 previous_words[0][0] == '(' &&
 			 previous_words[0][strlen(previous_words[0]) - 1] == ')')
 		COMPLETE_WITH_CONST("ON");
-	else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) &&
-			 TailMatches1("FROM"))
+	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
+			 TailMatches("FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
-	else if (TailMatches2("PARTITION", "BY"))
+	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH_LIST3("RANGE (", "LIST (", "HASH (");
 	/* If we have xxx PARTITION OF, provide a list of partitioned tables */
-	else if (TailMatches2("PARTITION", "OF"))
+	else if (TailMatches("PARTITION", "OF"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
-	else if (TailMatches3("PARTITION", "OF", MatchAny))
+	else if (TailMatches("PARTITION", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	else if (Matches("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	else if (Matches("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE SUBSCRIPTION */
-	else if (Matches3("CREATE", "SUBSCRIPTION", MatchAny))
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH_CONST("CONNECTION");
-	else if (Matches5("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION", MatchAny))
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION", MatchAny))
 		COMPLETE_WITH_CONST("PUBLICATION");
-	else if (Matches6("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",
 					  MatchAny, "PUBLICATION"))
 	{
 		/* complete with nothing here as this refers to remote publications */
 	}
-	else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("PUBLICATION", MatchAny))
+	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("PUBLICATION", MatchAny))
 		COMPLETE_WITH_CONST("WITH (");
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
-	else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("WITH", "("))
+	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH_LIST6("copy_data", "connect", "create_slot", "enabled",
 							"slot_name", "synchronous_commit");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (TailMatches("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 (TailMatches("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 (TailMatches("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 (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+			 TailMatches("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 (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	else if (TailMatches("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 (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("DEFERRABLE") || TailMatches("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("OLD|NEW", "TABLE"))
 		COMPLETE_WITH_CONST("AS");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-			  TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR"))
 		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR", "EACH"))
 		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
-			  TailMatches2("FOR", "ROW|STATEMENT")))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("FOR", "EACH", "ROW|STATEMENT") ||
+			  TailMatches("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+			 !TailMatches("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2629,7 +2607,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2644,166 +2622,166 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	else if (Matches("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (TailMatches("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	else if (Matches("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DEALLOCATE */
-	else if (Matches1("DEALLOCATE"))
+	else if (Matches("DEALLOCATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	else if (Matches("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	else if (HeadMatches("DECLARE") && TailMatches("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	else if (Matches("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	else if (TailMatches("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	else if (TailMatches("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	else if (Matches("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	else if (Matches("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	else if (Matches("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|SUBSCRIPTION|STATISTICS|TABLE|TYPE|VIEW",
 					  MatchAny) ||
-			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
-			 (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
+			 Matches("DROP", "ACCESS", "METHOD", MatchAny) ||
+			 (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
 			  ends_with(prev_wd, ')')) ||
-			 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))
+			 Matches("DROP", "EVENT", "TRIGGER", MatchAny) ||
+			 Matches("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+			 Matches("DROP", "FOREIGN", "TABLE", MatchAny) ||
+			 Matches("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
+	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
+	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	else if (Matches("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	else if (Matches("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	else if (Matches("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	else if (Matches("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	else if (Matches("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	else if (Matches("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	else if (Matches("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	else if (Matches("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/* DROP TEXT SEARCH */
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	else if (Matches("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	else if (Matches("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	else if (Matches("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	else if (Matches("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	else if (Matches("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	else if (Matches("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	else if (Matches("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	else if (Matches("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	else if (Matches("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	else if (Matches("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2811,22 +2789,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	else if (Matches("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	else if (Matches("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
-			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	else if (Matches("EXPLAIN", "VERBOSE") ||
+			 Matches("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	else if (Matches("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	else if (Matches("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2834,26 +2812,26 @@ 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 (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	else if (Matches("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
-			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
+	else if (TailMatches("FOREIGN", "DATA", "WRAPPER") &&
+			 !TailMatches("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
-			 HeadMatches2("CREATE", "SERVER"))
+	else if (TailMatches("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+			 HeadMatches("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches("FOREIGN", "TABLE") &&
+			 !TailMatches("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	else if (TailMatches("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /*
@@ -2861,13 +2839,13 @@ psql_completion(const char *text, int start, int end)
  * ALTER DEFAULT PRIVILEGES, so use TailMatches
  */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	else if (TailMatches("GRANT|REVOKE"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion to grantable
 		 * privileges (can't grant roles)
 		 */
-		if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH_LIST10("SELECT", "INSERT", "UPDATE",
 								 "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
 								 "EXECUTE", "USAGE", "ALL");
@@ -2892,11 +2870,11 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny))
 	{
-		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2913,13 +2891,13 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion to the kinds of
 		 * objects supported.
 		 */
-		if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH_LIST7("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
@@ -2943,13 +2921,13 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
 							"PROCEDURES IN SCHEMA",
 							"ROUTINES IN SCHEMA",
 							"SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2958,31 +2936,31 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
-		if (TailMatches1("DATABASE"))
+		if (TailMatches("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		else if (TailMatches("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		else if (TailMatches("FUNCTION"))
 			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		else if (TailMatches("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("PROCEDURE"))
+		else if (TailMatches("PROCEDURE"))
 			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
-		else if (TailMatches1("ROUTINE"))
+		else if (TailMatches("ROUTINE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-		else if (TailMatches1("SCHEMA"))
+		else if (TailMatches("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		else if (TailMatches("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		else if (TailMatches("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		else if (TailMatches("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2992,111 +2970,111 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
-			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
+	else if ((HeadMatches("GRANT") && TailMatches("TO")) ||
+			 (HeadMatches("REVOKE") && TailMatches("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
-	else if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches1("TO|FROM"))
+	else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	else if (HeadMatches("GRANT") && TailMatches("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	else if (HeadMatches("REVOKE") && TailMatches("ON", MatchAny, MatchAny))
 		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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	else if (TailMatches("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	else if (Matches("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	else if (Matches("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	else if (TailMatches("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	else if (TailMatches("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 (TailMatches("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES" or "OVERRIDING"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	else if (TailMatches("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST6("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE" or "OVERRIDING"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	else if (TailMatches("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST4("SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/* Complete OVERRIDING */
-	else if (TailMatches1("OVERRIDING"))
+	else if (TailMatches("OVERRIDING"))
 		COMPLETE_WITH_LIST2("SYSTEM VALUE", "USER VALUE");
 
 	/* Complete after OVERRIDING clause */
-	else if (TailMatches3("OVERRIDING", MatchAny, "VALUE"))
+	else if (TailMatches("OVERRIDING", MatchAny, "VALUE"))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	else if (Matches("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	else if (Matches("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 (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 Matches3("LOCK", "TABLE", MatchAny))
+	else if (Matches("LOCK", MatchAnyExcept("TABLE")) ||
+			 Matches("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
-			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
+	else if (Matches("LOCK", MatchAny, "IN") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -3104,36 +3082,36 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
+	else if (Matches("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
 		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
+	else if (Matches("LOCK", MatchAny, "IN", "SHARE") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
 		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	else if (TailMatches("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 (TailMatches1("OPTIONS"))
+	else if (TailMatches("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	else if (TailMatches("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	else if (TailMatches("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	else if (TailMatches("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	else if (Matches("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -3142,61 +3120,61 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	else if (Matches("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	else if (Matches("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	else if (Matches("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	else if (Matches("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	else if (Matches("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	else if (Matches("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	else if (Matches("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	else if (Matches("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	else if (Matches("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	else if (Matches("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	else if (Matches("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	else if (Matches("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	else if (Matches("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
-			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
+	else if (Matches("SECURITY", "LABEL", "ON") ||
+			 Matches("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
@@ -3206,7 +3184,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	else if (Matches("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3214,76 +3192,76 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	else if (TailMatches("SET|RESET") && !TailMatches("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	else if (Matches("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	else if (Matches("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-			 Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	else if (Matches("BEGIN|START", "TRANSACTION") ||
+			 Matches("BEGIN", "WORK") ||
+			 Matches("BEGIN") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+			 Matches("BEGIN", "NOT") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+			 Matches("BEGIN", "ISOLATION") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+			 Matches("BEGIN", "READ") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	else if (Matches("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 (Matches3("SET", "CONSTRAINTS", MatchAny))
+	else if (Matches("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	else if (Matches("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	else if (Matches("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	else if (Matches("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	else if (Matches("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	else if (Matches("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * Complete ALTER DATABASE|FUNCTION||PROCEDURE|ROLE|ROUTINE|USER ... SET
 	 * <name>
 	 */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
-			 TailMatches2("SET", MatchAny))
+	else if (HeadMatches("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
+			 TailMatches("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	else if (TailMatches("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
-		if (TailMatches2("DateStyle", "TO|="))
+		if (TailMatches("DateStyle", "TO|="))
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3293,7 +3271,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		else if (TailMatches("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
@@ -3322,83 +3300,83 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	else if (Matches("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	else if (Matches("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	else if (TailMatches("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	else if (TailMatches("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	else if (Matches("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	else if (Matches("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 --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	else if (TailMatches("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	else if (TailMatches("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	else if (TailMatches("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	else if (Matches("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	else if (Matches("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	else if (Matches("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	else if (Matches("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	else if (Matches("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	else if (Matches("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	else if (Matches("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	else if (Matches("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	else if (Matches("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	else if (Matches("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	else if (HeadMatches("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3407,114 +3385,114 @@ 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 (Matches1("WITH"))
+	else if (Matches("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	else if (Matches("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 (TailMatches2(MatchAny, "WHERE"))
+	else if (TailMatches(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	else if (TailMatches("FROM") && !Matches("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	else if (TailMatches("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	else if (TailMatchesCS("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	else if (TailMatchesCS("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	else if (TailMatchesCS("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	else if (TailMatchesCS("\\da*"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	else if (TailMatchesCS("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	else if (TailMatchesCS("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	else if (TailMatchesCS("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	else if (TailMatchesCS("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	else if (TailMatchesCS("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	else if (TailMatchesCS("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	else if (TailMatchesCS("\\df*"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	else if (TailMatchesCS("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	else if (TailMatchesCS("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	else if (TailMatchesCS("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	else if (TailMatchesCS("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	else if (TailMatchesCS("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	else if (TailMatchesCS("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	else if (TailMatchesCS("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	else if (TailMatchesCS("\\dp") || TailMatchesCS("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	else if (TailMatchesCS("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	else if (TailMatchesCS("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	else if (TailMatchesCS("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	else if (TailMatchesCS("\\du*") || TailMatchesCS("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	else if (TailMatchesCS("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	else if (TailMatchesCS("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	else if (TailMatchesCS("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	else if (TailMatchesCS("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	else if (TailMatchesCS("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	else if (TailMatchesCS("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	else if (TailMatchesCS("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	else if (TailMatchesCS("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	else if (TailMatchesCS("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h|\\help"))
+	else if (TailMatchesCS("\\h|\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS2("\\h|\\help", MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny))
 	{
-		if (TailMatches1("DROP"))
+		if (TailMatches("DROP"))
 			matches = completion_matches(text, drop_command_generator);
-		else if (TailMatches1("ALTER"))
+		else if (TailMatches("ALTER"))
 			matches = completion_matches(text, alter_command_generator);
 
 		/*
@@ -3522,39 +3500,39 @@ psql_completion(const char *text, int start, int end)
 		 * repeated here
 		 */
 	}
-	else if (TailMatchesCS3("\\h|\\help", MatchAny, MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny))
 	{
-		if (TailMatches2("CREATE|DROP", "ACCESS"))
+		if (TailMatches("CREATE|DROP", "ACCESS"))
 			COMPLETE_WITH_CONST("METHOD");
-		else if (TailMatches2("ALTER", "DEFAULT"))
+		else if (TailMatches("ALTER", "DEFAULT"))
 			COMPLETE_WITH_CONST("PRIVILEGES");
-		else if (TailMatches2("CREATE|ALTER|DROP", "EVENT"))
+		else if (TailMatches("CREATE|ALTER|DROP", "EVENT"))
 			COMPLETE_WITH_CONST("TRIGGER");
-		else if (TailMatches2("CREATE|ALTER|DROP", "FOREIGN"))
+		else if (TailMatches("CREATE|ALTER|DROP", "FOREIGN"))
 			COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-		else if (TailMatches2("ALTER", "LARGE"))
+		else if (TailMatches("ALTER", "LARGE"))
 			COMPLETE_WITH_CONST("OBJECT");
-		else if (TailMatches2("CREATE|ALTER|DROP", "MATERIALIZED"))
+		else if (TailMatches("CREATE|ALTER|DROP", "MATERIALIZED"))
 			COMPLETE_WITH_CONST("VIEW");
-		else if (TailMatches2("CREATE|ALTER|DROP", "TEXT"))
+		else if (TailMatches("CREATE|ALTER|DROP", "TEXT"))
 			COMPLETE_WITH_CONST("SEARCH");
-		else if (TailMatches2("CREATE|ALTER|DROP", "USER"))
+		else if (TailMatches("CREATE|ALTER|DROP", "USER"))
 			COMPLETE_WITH_CONST("MAPPING FOR");
 	}
-	else if (TailMatchesCS4("\\h|\\help", MatchAny, MatchAny, MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny, MatchAny))
 	{
-		if (TailMatches3("CREATE|ALTER|DROP", "FOREIGN", "DATA"))
+		if (TailMatches("CREATE|ALTER|DROP", "FOREIGN", "DATA"))
 			COMPLETE_WITH_CONST("WRAPPER");
-		else if (TailMatches3("CREATE|ALTER|DROP", "TEXT", "SEARCH"))
+		else if (TailMatches("CREATE|ALTER|DROP", "TEXT", "SEARCH"))
 			COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-		else if (TailMatches3("CREATE|ALTER|DROP", "USER", "MAPPING"))
+		else if (TailMatches("CREATE|ALTER|DROP", "USER", "MAPPING"))
 			COMPLETE_WITH_CONST("FOR");
 	}
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	else if (TailMatchesCS("\\l*") && !TailMatchesCS("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	else if (TailMatchesCS("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	else if (TailMatchesCS("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3565,9 +3543,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	else if (TailMatchesCS("\\pset", MatchAny))
 	{
-		if (TailMatchesCS1("format"))
+		if (TailMatchesCS("format"))
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -3575,46 +3553,46 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		else if (TailMatchesCS("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("pager"))
+		else if (TailMatchesCS("pager"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "always");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		else if (TailMatchesCS("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	else if (TailMatchesCS("\\unset"))
 		matches = complete_from_variables(text, "", "", true);
-	else if (TailMatchesCS1("\\set"))
+	else if (TailMatchesCS("\\set"))
 		matches = complete_from_variables(text, "", "", false);
-	else if (TailMatchesCS2("\\set", MatchAny))
+	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		else if (TailMatchesCS("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		else if (TailMatchesCS("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		else if (TailMatchesCS("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		else if (TailMatchesCS("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		else if (TailMatchesCS("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		else if (TailMatchesCS("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	else if (TailMatchesCS("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	else if (TailMatchesCS("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	else if (TailMatchesCS("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
diff --git a/src/include/c.h b/src/include/c.h
index 901d7911980..23dd9a41358 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -224,6 +224,32 @@
 #define CppAsString2(x)			CppAsString(x)
 #define CppConcat(x, y)			x##y
 
+/*
+ * PP_NARG - return the number of arguments a vararg macro gets passed.
+ *
+ * Making C pretty by making it ugly again.
+ */
+#define PP_NARG(...) \
+	PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
+#define PP_NARG_(...) \
+	PP_ARG_N(__VA_ARGS__)
+#define PP_ARG_N( \
+	_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
+	_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
+	_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
+	_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
+	_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
+	_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
+	_61,_62,_63,  N, ...) N
+#define PP_RSEQ_N() \
+	63,62,61,60,                   \
+	59,58,57,56,55,54,53,52,51,50, \
+	49,48,47,46,45,44,43,42,41,40, \
+	39,38,37,36,35,34,33,32,31,30, \
+	29,28,27,26,25,24,23,22,21,20, \
+	19,18,17,16,15,14,13,12,11,10, \
+	9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+
 /*
  * dummyret is used to set return values in macros that use ?: to make
  * assignments.  gcc wants these to be void, other compilers like char


^ permalink  raw  reply  [nested|flat] 40+ messages in thread

*  Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 08:23  Thomas Munro <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2018-09-21 08:23 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On Fri, Sep 21, 2018 at 5:52 PM Andres Freund <[email protected]> wrote:
> Here's a very quick-and-dirty implementation of this approach. Some very
> very brief testing seems to indicate it works, although I'm sure not
> perfectly.
>
> The current duplication of the new functions doing the actual checking
> (like CheckMatchesFor(), CheckTailMatchesFor()) would probably need to
> be reduced. But I don't want to invest actual time before we decide that
> this could be something we'd actually want to pursue.

+1

And here is a quick-and-dirty variadic COMPLETE_WITH(...).  Together:

    else if (Matches("PREPARE", MatchAny, "AS"))
        COMPLETE_WITH("SELECT", "UPDATE", "INSERT", "DELETE FROM");

+ * Making C pretty by making it ugly again.

Heh.

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


Attachments:

  [application/octet-stream] 0001-Variadic-Matches-.-macros.patch (126.1K, ../../CAEepm=1S6ZgrbtyAms5RJHS2OE4v2Sa2XKi7pNc9BC3e4=CSQg@mail.gmail.com/2-0001-Variadic-Matches-.-macros.patch)
  download | inline diff:
From 2b618c656d6aca83680abaab621a4a234aa1dea6 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Fri, 21 Sep 2018 19:44:54 +1200
Subject: [PATCH 1/2] Variadic Matches(...) macros.

---
 src/backend/commands/event_trigger.c |    1 +
 src/bin/psql/tab-complete.c          | 1416 +++++++++++++-------------
 src/include/c.h                      |   26 +
 3 files changed, 724 insertions(+), 719 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..8ad833aa47 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1739,6 +1739,7 @@ void
 EventTriggerAlterTableRelid(Oid objectId)
 {
 	if (!currentEventTriggerState ||
+		!currentEventTriggerState->currentCommand ||
 		currentEventTriggerState->commandCollectionInhibited)
 		return;
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 47a1a19688..21a4a807d9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1220,6 +1220,110 @@ ends_with(const char *s, char c)
 	return (length > 0 && s[length - 1] == c);
 }
 
+static bool
+CheckTailMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count != narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckTailMatchesCSFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches_cs(arg, previous_words[narg - argno - 1]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
+static bool
+CheckHeadMatchesFor(int previous_words_count, char **previous_words, int narg, ...)
+{
+	va_list		args;
+
+	if (previous_words_count < narg)
+		return false;
+
+	va_start(args, narg);
+
+	for (int argno = 0; argno < narg; argno++)
+	{
+		const char *arg = va_arg(args, const char *);
+
+		if (!word_matches(arg, previous_words[previous_words_count - (argno + 1)]))
+		{
+			va_end(args);
+			return false;
+		}
+	}
+
+	va_end(args);
+
+	return true;
+}
+
 /*
  * The completion function.
  *
@@ -1260,149 +1364,23 @@ psql_completion(const char *text, int start, int end)
 #define prev8_wd  (previous_words[7])
 #define prev9_wd  (previous_words[8])
 
+		/* Macros for matching the last N words before point, case-insensitively. */
+#define TailMatches(...) CheckTailMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
+
 	/* Macros for matching the last N words before point, case-insensitively. */
-#define TailMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, prev_wd))
-
-#define TailMatches2(p2, p1) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd))
-
-#define TailMatches3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd))
-
-#define TailMatches4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches(p1, prev_wd) && \
-	 word_matches(p2, prev2_wd) && \
-	 word_matches(p3, prev3_wd) && \
-	 word_matches(p4, prev4_wd))
-
-#define TailMatches5(p5, p4, p3, p2, p1) \
-	(previous_words_count >= 5 && \
-	 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 TailMatches6(p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 6 && \
-	 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 TailMatches7(p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 7 && \
-	 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 TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 8 && \
-	 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 TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \
-	(previous_words_count >= 9 && \
-	 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) && \
-	 word_matches(p9, prev9_wd))
-
-	/* Macros for matching the last N words before point, case-sensitively. */
-#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))
-#define TailMatchesCS3(p3, p2, p1) \
-	(previous_words_count >= 3 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd) && \
-	 word_matches_cs(p3, prev3_wd))
-#define TailMatchesCS4(p4, p3, p2, p1) \
-	(previous_words_count >= 4 && \
-	 word_matches_cs(p1, prev_wd) && \
-	 word_matches_cs(p2, prev2_wd) && \
-	 word_matches_cs(p3, prev3_wd) && \
-	 word_matches_cs(p4, prev4_wd))
+#define TailMatchesCS(...) CheckTailMatchesCSFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/*
 	 * Macros for matching N words beginning at the start of the line,
 	 * case-insensitively.
 	 */
-#define Matches1(p1) \
-	(previous_words_count == 1 && \
-	 TailMatches1(p1))
-#define Matches2(p1, p2) \
-	(previous_words_count == 2 && \
-	 TailMatches2(p1, p2))
-#define Matches3(p1, p2, p3) \
-	(previous_words_count == 3 && \
-	 TailMatches3(p1, p2, p3))
-#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))
+#define Matches(...) CheckMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
 	 * what is after them, case-insensitively.
 	 */
-#define HeadMatches1(p1) \
-	(previous_words_count >= 1 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]))
-
-#define HeadMatches2(p1, p2) \
-	(previous_words_count >= 2 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]))
-
-#define HeadMatches3(p1, p2, p3) \
-	(previous_words_count >= 3 && \
-	 word_matches(p1, previous_words[previous_words_count - 1]) && \
-	 word_matches(p2, previous_words[previous_words_count - 2]) && \
-	 word_matches(p3, previous_words[previous_words_count - 3]))
+#define HeadMatches(...) CheckHeadMatchesFor(previous_words_count, previous_words, PP_NARG(__VA_ARGS__), __VA_ARGS__)
 
 	/* Known command-starting keywords. */
 	static const char *const sql_commands[] = {
@@ -1492,38 +1470,38 @@ psql_completion(const char *text, int start, int end)
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (TailMatches1("CREATE"))
+	else if (TailMatches("CREATE"))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (Matches1("DROP"))
+	else if (Matches("DROP"))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (Matches2("ALTER", "TABLE"))
+	else if (Matches("ALTER", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER something */
-	else if (Matches1("ALTER"))
+	else if (Matches("ALTER"))
 		matches = completion_matches(text, alter_command_generator);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
-	else if (TailMatches4("ALL", "IN", "TABLESPACE", MatchAny))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
-	else if (TailMatches6("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
-	else if (TailMatches7("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
+	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> */
-	else if (Matches3("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
+	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> (...) */
-	else if (Matches4("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
+	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
 			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
@@ -1531,107 +1509,107 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	}
 	/* ALTER PUBLICATION <name> */
-	else if (Matches3("ALTER", "PUBLICATION", MatchAny))
+	else if (Matches("ALTER", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_LIST5("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
 	/* ALTER PUBLICATION <name> SET */
-	else if (Matches4("ALTER", "PUBLICATION", MatchAny, "SET"))
+	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLE");
 	/* ALTER PUBLICATION <name> SET ( */
-	else if (HeadMatches3("ALTER", "PUBLICATION", MatchAny) && TailMatches2("SET", "("))
+	else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
 		COMPLETE_WITH_CONST("publish");
 	/* ALTER SUBSCRIPTION <name> */
-	else if (Matches3("ALTER", "SUBSCRIPTION", MatchAny))
+	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH_LIST7("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
 							"RENAME TO", "REFRESH PUBLICATION", "SET");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches2("REFRESH", "PUBLICATION"))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("REFRESH", "PUBLICATION"))
 		COMPLETE_WITH_CONST("WITH (");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches4("REFRESH", "PUBLICATION", "WITH", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("REFRESH", "PUBLICATION", "WITH", "("))
 		COMPLETE_WITH_CONST("copy_data");
 	/* ALTER SUBSCRIPTION <name> SET */
-	else if (Matches4("ALTER", "SUBSCRIPTION", MatchAny, "SET"))
+	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "PUBLICATION");
 	/* ALTER SUBSCRIPTION <name> SET ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("SET", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "("))
 		COMPLETE_WITH_LIST2("slot_name", "synchronous_commit");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("SET", "PUBLICATION"))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "PUBLICATION"))
 	{
 		/* complete with nothing here as this refers to remote publications */
 	}
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches3("SET", "PUBLICATION", MatchAny))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("SET", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_CONST("WITH (");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> WITH ( */
-	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) &&
-			 TailMatches5("SET", "PUBLICATION", MatchAny, "WITH", "("))
+	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
+			 TailMatches("SET", "PUBLICATION", MatchAny, "WITH", "("))
 		COMPLETE_WITH_LIST2("copy_data", "refresh");
 	/* ALTER SCHEMA <name> */
-	else if (Matches3("ALTER", "SCHEMA", MatchAny))
+	else if (Matches("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
-	else if (Matches3("ALTER", "COLLATION", MatchAny))
+	else if (Matches("ALTER", "COLLATION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
-	else if (Matches3("ALTER", "CONVERSION", MatchAny))
+	else if (Matches("ALTER", "CONVERSION", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
-	else if (Matches3("ALTER", "DATABASE", MatchAny))
+	else if (Matches("ALTER", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
 	/* ALTER DATABASE <name> SET TABLESPACE */
-	else if (Matches5("ALTER", "DATABASE", MatchAny, "SET", "TABLESPACE"))
+	else if (Matches("ALTER", "DATABASE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 
 	/* ALTER EVENT TRIGGER */
-	else if (Matches3("ALTER", "EVENT", "TRIGGER"))
+	else if (Matches("ALTER", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (Matches4("ALTER", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (Matches5("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
+	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
-	else if (Matches3("ALTER", "EXTENSION", MatchAny))
+	else if (Matches("ALTER", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
-	else if (Matches4("ALTER", "EXTENSION", MatchAny, "UPDATE"))
+	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
-	else if (Matches5("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
+	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
-	else if (Matches2("ALTER", "FOREIGN"))
+	else if (Matches("ALTER", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
-	else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny))
+	else if (Matches("ALTER", "FOREIGN", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER_FOREIGN_TABLE[] =
 		{"ADD", "ALTER", "DISABLE TRIGGER", "DROP", "ENABLE", "INHERIT",
@@ -1642,35 +1620,35 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER INDEX */
-	else if (Matches2("ALTER", "INDEX"))
+	else if (Matches("ALTER", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
-	else if (Matches3("ALTER", "INDEX", MatchAny))
+	else if (Matches("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST6("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
 							"RESET", "ATTACH PARTITION");
-	else if (Matches4("ALTER", "INDEX", MatchAny, "ATTACH"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH"))
 		COMPLETE_WITH_CONST("PARTITION");
-	else if (Matches5("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	/* ALTER INDEX <name> ALTER COLUMN <colnum> */
-	else if (Matches6("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
+	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
 		COMPLETE_WITH_CONST("SET STATISTICS");
 	/* ALTER INDEX <name> SET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "SET"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "SET"))
 		COMPLETE_WITH_LIST2("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
-	else if (Matches4("ALTER", "INDEX", MatchAny, "RESET"))
+	else if (Matches("ALTER", "INDEX", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "INDEX", MatchAny, "RESET", "("))
+	else if (Matches("ALTER", "INDEX", MatchAny, "RESET", "("))
 		COMPLETE_WITH_LIST8("fillfactor", "recheck_on_update",
 							"vacuum_cleanup_index_scale_factor",	/* BTREE */
 							"fastupdate", "gin_pending_list_limit", /* GIN */
 							"buffering",	/* GiST */
 							"pages_per_range", "autosummarize"	/* BRIN */
 			);
-	else if (Matches5("ALTER", "INDEX", MatchAny, "SET", "("))
+	else if (Matches("ALTER", "INDEX", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST8("fillfactor =", "recheck_on_update =",
 							"vacuum_cleanup_index_scale_factor =",	/* BTREE */
 							"fastupdate =", "gin_pending_list_limit =", /* GIN */
@@ -1679,21 +1657,21 @@ psql_completion(const char *text, int start, int end)
 			);
 
 	/* ALTER LANGUAGE <name> */
-	else if (Matches3("ALTER", "LANGUAGE", MatchAny))
+	else if (Matches("ALTER", "LANGUAGE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
-	else if (Matches4("ALTER", "LARGE", "OBJECT", MatchAny))
+	else if (Matches("ALTER", "LARGE", "OBJECT", MatchAny))
 		COMPLETE_WITH_CONST("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
-	else if (Matches3("ALTER", "MATERIALIZED", "VIEW"))
+	else if (Matches("ALTER", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 
 	/* ALTER USER,ROLE <name> */
-	else if (Matches3("ALTER", "USER|ROLE", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	else if (Matches("ALTER", "USER|ROLE", MatchAny) &&
+			 !TailMatches("USER", "MAPPING"))
 	{
 		static const char *const list_ALTERUSER[] =
 		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -1707,7 +1685,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER USER,ROLE <name> WITH */
-	else if (Matches4("ALTER", "USER|ROLE", MatchAny, "WITH"))
+	else if (Matches("ALTER", "USER|ROLE", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
@@ -1722,58 +1700,58 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DEFAULT PRIVILEGES */
-	else if (Matches3("ALTER", "DEFAULT", "PRIVILEGES"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES"))
 		COMPLETE_WITH_LIST2("FOR ROLE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN */
-	else if (Matches4("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... */
-	else if (Matches6("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny))
 		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "FOR ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR */
-	else if (Matches7("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR"))
 		COMPLETE_WITH_CONST("ROLE");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... IN SCHEMA ... */
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR ROLE|USER ... */
-	else if (Matches9("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
+	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny, "IN", "SCHEMA", MatchAny) ||
-			 Matches9("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
+			 Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR", "ROLE|USER", MatchAny))
 		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
-	else if (Matches3("ALTER", "DOMAIN", MatchAny))
+	else if (Matches("ALTER", "DOMAIN", MatchAny))
 		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
-	else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches("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 (Matches4("ALTER", "DOMAIN", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
-	else if (Matches6("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER DOMAIN <sth> SET */
-	else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET"))
+	else if (Matches("ALTER", "DOMAIN", MatchAny, "SET"))
 		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
-	else if (Matches3("ALTER", "SEQUENCE", MatchAny))
+	else if (Matches("ALTER", "SEQUENCE", MatchAny))
 	{
 		static const char *const list_ALTERSEQUENCE[] =
 		{"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
@@ -1782,79 +1760,79 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
 	}
 	/* ALTER SEQUENCE <name> NO */
-	else if (Matches4("ALTER", "SEQUENCE", MatchAny, "NO"))
+	else if (Matches("ALTER", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
-	else if (Matches3("ALTER", "SERVER", MatchAny))
+	else if (Matches("ALTER", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
-	else if (Matches5("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
+	else if (Matches("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
-	else if (Matches2("ALTER", "SYSTEM"))
+	else if (Matches("ALTER", "SYSTEM"))
 		COMPLETE_WITH_LIST2("SET", "RESET");
-	else if (Matches3("ALTER", "SYSTEM", "SET|RESET"))
+	else if (Matches("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
-	else if (Matches4("ALTER", "SYSTEM", "SET", MatchAny))
+	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 	/* ALTER VIEW <name> */
-	else if (Matches3("ALTER", "VIEW", MatchAny))
+	else if (Matches("ALTER", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
-	else if (Matches4("ALTER", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
-	else if (Matches2("ALTER", "POLICY"))
+	else if (Matches("ALTER", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
-	else if (Matches3("ALTER", "POLICY", MatchAny))
+	else if (Matches("ALTER", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* ALTER POLICY <name> ON <table> */
-	else if (Matches4("ALTER", "POLICY", MatchAny, "ON"))
+	else if (Matches("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 (Matches5("ALTER", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
-	else if (Matches6("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
-	else if (Matches7("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
+	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH_CONST("(");
 
 	/* ALTER RULE <name>, add ON */
-	else if (Matches3("ALTER", "RULE", MatchAny))
+	else if (Matches("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
-	else if (Matches4("ALTER", "RULE", MatchAny, "ON"))
+	else if (Matches("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 (Matches5("ALTER", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/* ALTER STATISTICS <name> */
-	else if (Matches3("ALTER", "STATISTICS", MatchAny))
+	else if (Matches("ALTER", "STATISTICS", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER TRIGGER <name>, add ON */
-	else if (Matches3("ALTER", "TRIGGER", MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
@@ -1863,17 +1841,17 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
-	else if (Matches4("ALTER", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 	/* ALTER TRIGGER <name> ON <name> */
-	else if (Matches5("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_CONST("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
 	 */
-	else if (Matches3("ALTER", "TABLE", MatchAny))
+	else if (Matches("ALTER", "TABLE", MatchAny))
 	{
 		static const char *const list_ALTER2[] =
 		{"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
@@ -1884,114 +1862,114 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER2);
 	}
 	/* ALTER TABLE xxx ENABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ENABLE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE"))
 		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
 		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
 		completion_info_charp = prev4_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
+	else if (Matches("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 (Matches4("ALTER", "TABLE", MatchAny, "INHERIT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx NO INHERIT */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DISABLE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
 	}
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "ALTER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
 
 	/* ALTER TABLE xxx RENAME */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME"))
 		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 	/* ALTER TABLE xxx RENAME yyy */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
-	else if (Matches6("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
 		COMPLETE_WITH_CONST("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "DROP"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
+	else if (Matches("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 (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT"))
+	else if (Matches("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 (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
-			 Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
 		COMPLETE_WITH_LIST6("TYPE", "SET", "RESET", "RESTART", "ADD", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
 		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
-			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
-	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
-			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
 		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
-	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
-			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
+			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
 		COMPLETE_WITH_LIST3("DEFAULT", "IDENTITY", "NOT NULL");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "SET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET"))
 		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
@@ -1999,19 +1977,19 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TABLE <sth> SET TABLESPACE provide a list of
 	 * tablespaces
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "TABLESPACE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITH"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITH"))
 		COMPLETE_WITH_CONST("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
 		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
-	else if (Matches4("ALTER", "TABLE", MatchAny, "RESET"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
+	else if (Matches("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
 		static const char *const list_TABLEOPTIONS[] =
 		{
@@ -2050,146 +2028,146 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (Matches7("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
-	else if (Matches6("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH_CONST("INDEX");
-	else if (Matches5("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
 		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
-	else if (Matches4("ALTER", "TABLE", MatchAny, "REPLICA"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA"))
 		COMPLETE_WITH_CONST("IDENTITY");
 
 	/*
 	 * If we have ALTER TABLE <foo> ATTACH PARTITION, provide a list of
 	 * tables.
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
-	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
+	else if (TailMatches("ATTACH", "PARTITION", MatchAny))
 		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
-	else if (TailMatches2("FOR", "VALUES"))
+	else if (TailMatches("FOR", "VALUES"))
 		COMPLETE_WITH_LIST3("FROM (", "IN (", "WITH (");
 
 	/*
 	 * If we have ALTER TABLE <foo> DETACH PARTITION, provide a list of
 	 * partitions of <foo>.
 	 */
-	else if (Matches5("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
+	else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
 	{
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_partition_of_table);
 	}
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
-	else if (Matches3("ALTER", "TABLESPACE", MatchAny))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
-	else if (Matches4("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
-	else if (Matches5("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
+	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
-	else if (Matches3("ALTER", "TEXT", "SEARCH"))
+	else if (Matches("ALTER", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
+	else if (Matches("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
 		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
+	else if (Matches("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
 		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
-	else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches("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 (Matches3("ALTER", "TYPE", MatchAny))
+	else if (Matches("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 (Matches4("ALTER", "TYPE", MatchAny, "ADD"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
 		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
-	else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME"))
 		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
 	 * of attributes
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
-	else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
+	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* complete ALTER GROUP <foo> */
-	else if (Matches3("ALTER", "GROUP", MatchAny))
+	else if (Matches("ALTER", "GROUP", MatchAny))
 		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
-	else if (Matches4("ALTER", "GROUP", MatchAny, "ADD|DROP"))
+	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP"))
 		COMPLETE_WITH_CONST("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
-	else if (Matches5("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
+	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/*
 	 * If we have ALTER TYPE <sth> RENAME VALUE, provide list of enum values
 	 */
-	else if (Matches5("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
+	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME", "VALUE"))
 		COMPLETE_WITH_ENUM_VALUE(prev3_wd);
 
 /* BEGIN */
-	else if (Matches1("BEGIN"))
+	else if (Matches("BEGIN"))
 		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
-	else if (Matches1("END|ABORT"))
+	else if (Matches("END|ABORT"))
 		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
 /* COMMIT */
-	else if (Matches1("COMMIT"))
+	else if (Matches("COMMIT"))
 		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
-	else if (Matches1("RELEASE"))
+	else if (Matches("RELEASE"))
 		COMPLETE_WITH_CONST("SAVEPOINT");
 /* ROLLBACK */
-	else if (Matches1("ROLLBACK"))
+	else if (Matches("ROLLBACK"))
 		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CALL */
-	else if (Matches1("CALL"))
+	else if (Matches("CALL"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
-	else if (Matches2("CALL", MatchAny))
+	else if (Matches("CALL", MatchAny))
 		COMPLETE_WITH_CONST("(");
 /* CLUSTER */
-	else if (Matches1("CLUSTER"))
+	else if (Matches("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
-	else if (Matches2("CLUSTER", "VERBOSE"))
+	else if (Matches("CLUSTER", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
-	else if (Matches2("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
+	else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
-	else if (Matches3("CLUSTER", "VERBOSE", MatchAny))
+	else if (Matches("CLUSTER", "VERBOSE", MatchAny))
 		COMPLETE_WITH_CONST("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
-	else if (Matches3("CLUSTER", MatchAny, "USING") ||
-			 Matches4("CLUSTER", "VERBOSE", MatchAny, "USING"))
+	else if (Matches("CLUSTER", MatchAny, "USING") ||
+			 Matches("CLUSTER", "VERBOSE", MatchAny, "USING"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 
 /* COMMENT */
-	else if (Matches1("COMMENT"))
+	else if (Matches("COMMENT"))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches2("COMMENT", "ON"))
+	else if (Matches("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
 		{"ACCESS METHOD", "CAST", "COLLATION", "CONVERSION", "DATABASE",
@@ -2204,28 +2182,28 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_COMMENT);
 	}
-	else if (Matches4("COMMENT", "ON", "ACCESS", "METHOD"))
+	else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (Matches3("COMMENT", "ON", "FOREIGN"))
+	else if (Matches("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-	else if (Matches4("COMMENT", "ON", "TEXT", "SEARCH"))
+	else if (Matches("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches3("COMMENT", "ON", "CONSTRAINT"))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
-	else if (Matches4("COMMENT", "ON", "CONSTRAINT", MatchAny))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches5("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
+	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
 	}
-	else if (Matches4("COMMENT", "ON", "MATERIALIZED", "VIEW"))
+	else if (Matches("COMMENT", "ON", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("COMMENT", "ON", "EVENT", "TRIGGER"))
+	else if (Matches("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
-	else if (Matches4("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
-			 Matches5("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
-			 Matches6("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
+	else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
+			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
+			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
 		COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -2234,92 +2212,92 @@ psql_completion(const char *text, int start, int end)
 	 * If we have COPY, offer list of tables or "(" (Also cover the analogous
 	 * backslash command).
 	 */
-	else if (Matches1("COPY|\\copy"))
+	else if (Matches("COPY|\\copy"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION ALL SELECT '('");
 	/* If we have COPY BINARY, complete with list of tables */
-	else if (Matches2("COPY", "BINARY"))
+	else if (Matches("COPY", "BINARY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
-	else if (Matches2("COPY|\\copy", "("))
+	else if (Matches("COPY|\\copy", "("))
 		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
-	else if (Matches2("COPY|\\copy", MatchAny) ||
-			 Matches3("COPY", "BINARY", MatchAny))
+	else if (Matches("COPY|\\copy", MatchAny) ||
+			 Matches("COPY", "BINARY", MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
-	else if (Matches3("COPY|\\copy", MatchAny, "FROM|TO") ||
-			 Matches4("COPY", "BINARY", MatchAny, "FROM|TO"))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO") ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO"))
 	{
 		completion_charp = "";
 		matches = completion_matches(text, complete_from_files);
 	}
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
-	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
-			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
 		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
-	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
-			 Matches6("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
+	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
+			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
 		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
-	else if (Matches4("CREATE", "ACCESS", "METHOD", MatchAny))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny))
 		COMPLETE_WITH_CONST("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
-	else if (Matches5("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
 		COMPLETE_WITH_CONST("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
-	else if (Matches6("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
+	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH_CONST("HANDLER");
 
 	/* CREATE DATABASE */
-	else if (Matches3("CREATE", "DATABASE", MatchAny))
+	else if (Matches("CREATE", "DATABASE", MatchAny))
 		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
 
-	else if (Matches4("CREATE", "DATABASE", MatchAny, "TEMPLATE"))
+	else if (Matches("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 (Matches2("CREATE", "EXTENSION"))
+	else if (Matches("CREATE", "EXTENSION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
-	else if (Matches3("CREATE", "EXTENSION", MatchAny))
+	else if (Matches("CREATE", "EXTENSION", MatchAny))
 		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
-	else if (Matches4("CREATE", "EXTENSION", MatchAny, "VERSION"))
+	else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
-	else if (Matches2("CREATE", "FOREIGN"))
+	else if (Matches("CREATE", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
-	else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
+	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
-	else if (TailMatches2("CREATE", "UNIQUE"))
+	else if (TailMatches("CREATE", "UNIQUE"))
 		COMPLETE_WITH_CONST("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
 	 * existing indexes
 	 */
-	else if (TailMatches2("CREATE|UNIQUE", "INDEX"))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'"
 								   " UNION SELECT 'CONCURRENTLY'");
@@ -2328,294 +2306,294 @@ psql_completion(const char *text, int start, int end)
 	 * Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of relations
 	 * that can indexes can be created on
 	 */
-	else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
-			 TailMatches2("INDEX|CONCURRENTLY", "ON"))
+	else if (TailMatches("INDEX|CONCURRENTLY", MatchAny, "ON") ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm, NULL);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'ON'");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
-	else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) ||
-			 TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
+	else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) ||
+			 TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
 	 * should really be in parens)
 	 */
-	else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) ||
-			 TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny))
+	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny) ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny))
 		COMPLETE_WITH_LIST2("(", "USING");
-	else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") ||
-			 TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
+	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny, "(") ||
+			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* same if you put in USING */
-	else if (TailMatches5("ON", MatchAny, "USING", MatchAny, "("))
+	else if (TailMatches("ON", MatchAny, "USING", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (TailMatches6("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") ||
-			 TailMatches4("INDEX", "ON", MatchAny, "USING"))
+	else if (TailMatches("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
+			 TailMatches("INDEX", MatchAny, "ON", MatchAny, "USING") ||
+			 TailMatches("INDEX", "ON", MatchAny, "USING"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatches4("ON", MatchAny, "USING", MatchAny) &&
-			 !TailMatches6("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
-			 !TailMatches4("FOR", MatchAny, MatchAny, MatchAny))
+	else if (TailMatches("ON", MatchAny, "USING", MatchAny) &&
+			 !TailMatches("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
+			 !TailMatches("FOR", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
-	else if (Matches3("CREATE", "POLICY", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
-	else if (Matches4("CREATE", "POLICY", MatchAny, "ON"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
-	else if (Matches5("CREATE", "POLICY", MatchAny, "ON", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST5("AS", "FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
 		COMPLETE_WITH_LIST2("PERMISSIVE", "RESTRICTIVE");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
 	 * FOR|TO|USING|WITH CHECK
 	 */
-	else if (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches("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 (Matches7("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches("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 (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
-	else if (Matches6("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * INSERT TO|WITH CHECK"
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
 		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * SELECT|DELETE TO|USING"
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
 		COMPLETE_WITH_LIST2("TO", "USING (");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|UPDATE TO|USING|WITH CHECK
 	 */
-	else if (Matches9("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
 		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE TO
 	 * <role>"
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
 	 * USING ("
 	 */
-	else if (Matches8("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
+	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
 		COMPLETE_WITH_CONST("(");
 
 
 /* CREATE PUBLICATION */
-	else if (Matches3("CREATE", "PUBLICATION", MatchAny))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny))
 		COMPLETE_WITH_LIST3("FOR TABLE", "FOR ALL TABLES", "WITH (");
-	else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR"))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
 		COMPLETE_WITH_LIST2("TABLE", "ALL TABLES");
 	/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>" */
-	else if (Matches4("CREATE", "PUBLICATION", MatchAny, "FOR TABLE"))
+	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE PUBLICATION <name> [...] WITH" */
-	else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches2("WITH", "("))
+	else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
 		COMPLETE_WITH_CONST("publish");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
-	else if (Matches3("CREATE", "RULE", MatchAny))
+	else if (Matches("CREATE", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
-	else if (Matches4("CREATE", "RULE", MatchAny, "AS"))
+	else if (Matches("CREATE", "RULE", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
-	else if (Matches5("CREATE", "RULE", MatchAny, "AS", "ON"))
+	else if (Matches("CREATE", "RULE", MatchAny, "AS", "ON"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
-	else if (TailMatches3("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
+	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
 		COMPLETE_WITH_CONST("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
-	else if (TailMatches4("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
+	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
-	else if (TailMatches3("CREATE", "SEQUENCE", MatchAny) ||
-			 TailMatches4("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
+	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
-	else if (TailMatches4("CREATE", "SEQUENCE", MatchAny, "NO") ||
-			 TailMatches5("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
+	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "NO") ||
+			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
 		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
-	else if (Matches3("CREATE", "SERVER", MatchAny))
+	else if (Matches("CREATE", "SERVER", MatchAny))
 		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE STATISTICS <name> */
-	else if (Matches3("CREATE", "STATISTICS", MatchAny))
+	else if (Matches("CREATE", "STATISTICS", MatchAny))
 		COMPLETE_WITH_LIST2("(", "ON");
-	else if (Matches4("CREATE", "STATISTICS", MatchAny, "("))
+	else if (Matches("CREATE", "STATISTICS", MatchAny, "("))
 		COMPLETE_WITH_LIST2("ndistinct", "dependencies");
-	else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) &&
+	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
 			 previous_words[0][0] == '(' &&
 			 previous_words[0][strlen(previous_words[0]) - 1] == ')')
 		COMPLETE_WITH_CONST("ON");
-	else if (HeadMatches3("CREATE", "STATISTICS", MatchAny) &&
-			 TailMatches1("FROM"))
+	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
+			 TailMatches("FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
-	else if (TailMatches2("CREATE", "TEMP|TEMPORARY"))
+	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
 		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
-	else if (TailMatches2("CREATE", "UNLOGGED"))
+	else if (TailMatches("CREATE", "UNLOGGED"))
 		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
-	else if (TailMatches2("PARTITION", "BY"))
+	else if (TailMatches("PARTITION", "BY"))
 		COMPLETE_WITH_LIST3("RANGE (", "LIST (", "HASH (");
 	/* If we have xxx PARTITION OF, provide a list of partitioned tables */
-	else if (TailMatches2("PARTITION", "OF"))
+	else if (TailMatches("PARTITION", "OF"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
-	else if (TailMatches3("PARTITION", "OF", MatchAny))
+	else if (TailMatches("PARTITION", "OF", MatchAny))
 		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
-	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
+	else if (Matches("CREATE", "TABLESPACE", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
-	else if (Matches5("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
+	else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
 		COMPLETE_WITH_CONST("LOCATION");
 
 /* CREATE TEXT SEARCH */
-	else if (Matches3("CREATE", "TEXT", "SEARCH"))
+	else if (Matches("CREATE", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-	else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
+	else if (Matches("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* CREATE SUBSCRIPTION */
-	else if (Matches3("CREATE", "SUBSCRIPTION", MatchAny))
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH_CONST("CONNECTION");
-	else if (Matches5("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION", MatchAny))
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION", MatchAny))
 		COMPLETE_WITH_CONST("PUBLICATION");
-	else if (Matches6("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",
+	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",
 					  MatchAny, "PUBLICATION"))
 	{
 		/* complete with nothing here as this refers to remote publications */
 	}
-	else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("PUBLICATION", MatchAny))
+	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("PUBLICATION", MatchAny))
 		COMPLETE_WITH_CONST("WITH (");
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
-	else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("WITH", "("))
+	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH_LIST6("copy_data", "connect", "create_slot", "enabled",
 							"slot_name", "synchronous_commit");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
-	else if (TailMatches3("CREATE", "TRIGGER", MatchAny))
+	else if (TailMatches("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 (TailMatches("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 (TailMatches("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 (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
+			 TailMatches("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 (TailMatches6("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
+	else if (TailMatches("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 (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
+	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("ON", MatchAny))
 		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED")))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("DEFERRABLE") || TailMatches("INITIALLY", "IMMEDIATE|DEFERRED")))
 		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("REFERENCING"))
 		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("OLD|NEW", "TABLE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("OLD|NEW", "TABLE"))
 		COMPLETE_WITH_CONST("AS");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "OLD", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches5("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches4("REFERENCING", "NEW", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches9("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
-			  TailMatches8("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
-			  TailMatches7("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
+			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
 		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("FOR"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR"))
 		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("FOR", "EACH"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR", "EACH"))
 		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
-	else if (HeadMatches2("CREATE", "TRIGGER") &&
-			 (TailMatches3("FOR", "EACH", "ROW|STATEMENT") ||
-			  TailMatches2("FOR", "ROW|STATEMENT")))
+	else if (HeadMatches("CREATE", "TRIGGER") &&
+			 (TailMatches("FOR", "EACH", "ROW|STATEMENT") ||
+			  TailMatches("FOR", "ROW|STATEMENT")))
 		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE"))
 		COMPLETE_WITH_CONST("PROCEDURE");
-	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
+	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
-	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
-			 !TailMatches2("USER", "MAPPING"))
+	else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny) &&
+			 !TailMatches("USER", "MAPPING"))
 	{
 		static const char *const list_CREATEROLE[] =
 		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
@@ -2629,7 +2607,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* CREATE ROLE,USER,GROUP <name> WITH */
-	else if (Matches4("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
+	else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny, "WITH"))
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
@@ -2644,166 +2622,166 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
-	else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
+	else if (Matches("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
 		COMPLETE_WITH_LIST2("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
-	else if (TailMatches3("CREATE", "VIEW", MatchAny))
+	else if (TailMatches("CREATE", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
-	else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS"))
+	else if (TailMatches("CREATE", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
-	else if (Matches2("CREATE", "MATERIALIZED"))
+	else if (Matches("CREATE", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
-	else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
-	else if (Matches5("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
+	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
 		COMPLETE_WITH_CONST("SELECT");
 
 /* CREATE EVENT TRIGGER */
-	else if (Matches2("CREATE", "EVENT"))
+	else if (Matches("CREATE", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
-	else if (Matches4("CREATE", "EVENT", "TRIGGER", MatchAny))
+	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
-	else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
 		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DEALLOCATE */
-	else if (Matches1("DEALLOCATE"))
+	else if (Matches("DEALLOCATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* DECLARE */
-	else if (Matches2("DECLARE", MatchAny))
+	else if (Matches("DECLARE", MatchAny))
 		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
-	else if (HeadMatches1("DECLARE") && TailMatches1("CURSOR"))
+	else if (HeadMatches("DECLARE") && TailMatches("CURSOR"))
 		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
-	else if (Matches1("DELETE"))
+	else if (Matches("DELETE"))
 		COMPLETE_WITH_CONST("FROM");
 	/* Complete DELETE FROM with a list of tables */
-	else if (TailMatches2("DELETE", "FROM"))
+	else if (TailMatches("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
-	else if (TailMatches3("DELETE", "FROM", MatchAny))
+	else if (TailMatches("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH_LIST2("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
-	else if (Matches1("DISCARD"))
+	else if (Matches("DISCARD"))
 		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
-	else if (Matches1("DO"))
+	else if (Matches("DO"))
 		COMPLETE_WITH_CONST("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
-	else if (Matches3("DROP",
+	else if (Matches("DROP",
 					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|SUBSCRIPTION|STATISTICS|TABLE|TYPE|VIEW",
 					  MatchAny) ||
-			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
-			 (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
+			 Matches("DROP", "ACCESS", "METHOD", MatchAny) ||
+			 (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny) &&
 			  ends_with(prev_wd, ')')) ||
-			 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))
+			 Matches("DROP", "EVENT", "TRIGGER", MatchAny) ||
+			 Matches("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
+			 Matches("DROP", "FOREIGN", "TABLE", MatchAny) ||
+			 Matches("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
-	else if (Matches3("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
+	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
 		COMPLETE_WITH_CONST("(");
-	else if (Matches4("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
+	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
-	else if (Matches2("DROP", "FOREIGN"))
+	else if (Matches("DROP", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
-	else if (Matches2("DROP", "INDEX"))
+	else if (Matches("DROP", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches3("DROP", "INDEX", "CONCURRENTLY"))
+	else if (Matches("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches3("DROP", "INDEX", MatchAny))
+	else if (Matches("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
-	else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny))
+	else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
-	else if (Matches2("DROP", "MATERIALIZED"))
+	else if (Matches("DROP", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("DROP", "MATERIALIZED", "VIEW"))
+	else if (Matches("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
-	else if (Matches2("DROP", "OWNED"))
+	else if (Matches("DROP", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("DROP", "OWNED", "BY"))
+	else if (Matches("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/* DROP TEXT SEARCH */
-	else if (Matches3("DROP", "TEXT", "SEARCH"))
+	else if (Matches("DROP", "TEXT", "SEARCH"))
 		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
-	else if (Matches3("DROP", "TRIGGER", MatchAny))
+	else if (Matches("DROP", "TRIGGER", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "TRIGGER", MatchAny, "ON"))
+	else if (Matches("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
-	else if (Matches5("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
+	else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
-	else if (Matches2("DROP", "ACCESS"))
+	else if (Matches("DROP", "ACCESS"))
 		COMPLETE_WITH_CONST("METHOD");
-	else if (Matches3("DROP", "ACCESS", "METHOD"))
+	else if (Matches("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
-	else if (Matches2("DROP", "EVENT"))
+	else if (Matches("DROP", "EVENT"))
 		COMPLETE_WITH_CONST("TRIGGER");
-	else if (Matches3("DROP", "EVENT", "TRIGGER"))
+	else if (Matches("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* DROP POLICY <name>  */
-	else if (Matches2("DROP", "POLICY"))
+	else if (Matches("DROP", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
-	else if (Matches3("DROP", "POLICY", MatchAny))
+	else if (Matches("DROP", "POLICY", MatchAny))
 		COMPLETE_WITH_CONST("ON");
 	/* DROP POLICY <name> ON <table> */
-	else if (Matches4("DROP", "POLICY", MatchAny, "ON"))
+	else if (Matches("DROP", "POLICY", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
 	}
 
 	/* DROP RULE */
-	else if (Matches3("DROP", "RULE", MatchAny))
+	else if (Matches("DROP", "RULE", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches4("DROP", "RULE", MatchAny, "ON"))
+	else if (Matches("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
-	else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
+	else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
 
 /* EXECUTE */
-	else if (Matches1("EXECUTE"))
+	else if (Matches("EXECUTE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
 
 /* EXPLAIN */
@@ -2811,22 +2789,22 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
-	else if (Matches1("EXPLAIN"))
+	else if (Matches("EXPLAIN"))
 		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
-	else if (Matches2("EXPLAIN", "ANALYZE"))
+	else if (Matches("EXPLAIN", "ANALYZE"))
 		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
-	else if (Matches2("EXPLAIN", "VERBOSE") ||
-			 Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
+	else if (Matches("EXPLAIN", "VERBOSE") ||
+			 Matches("EXPLAIN", "ANALYZE", "VERBOSE"))
 		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
-	else if (Matches1("FETCH|MOVE"))
+	else if (Matches("FETCH|MOVE"))
 		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
-	else if (Matches2("FETCH|MOVE", MatchAny))
+	else if (Matches("FETCH|MOVE", MatchAny))
 		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
 
 	/*
@@ -2834,26 +2812,26 @@ 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 (Matches3("FETCH|MOVE", MatchAny, MatchAny))
+	else if (Matches("FETCH|MOVE", MatchAny, MatchAny))
 		COMPLETE_WITH_LIST2("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
-	else if (TailMatches3("FOREIGN", "DATA", "WRAPPER") &&
-			 !TailMatches4("CREATE", MatchAny, MatchAny, MatchAny))
+	else if (TailMatches("FOREIGN", "DATA", "WRAPPER") &&
+			 !TailMatches("CREATE", MatchAny, MatchAny, MatchAny))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	/* applies in CREATE SERVER */
-	else if (TailMatches4("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
-			 HeadMatches2("CREATE", "SERVER"))
+	else if (TailMatches("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
+			 HeadMatches("CREATE", "SERVER"))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /* FOREIGN TABLE */
-	else if (TailMatches2("FOREIGN", "TABLE") &&
-			 !TailMatches3("CREATE", MatchAny, MatchAny))
+	else if (TailMatches("FOREIGN", "TABLE") &&
+			 !TailMatches("CREATE", MatchAny, MatchAny))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
 
 /* FOREIGN SERVER */
-	else if (TailMatches2("FOREIGN", "SERVER"))
+	else if (TailMatches("FOREIGN", "SERVER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 
 /*
@@ -2861,13 +2839,13 @@ psql_completion(const char *text, int start, int end)
  * ALTER DEFAULT PRIVILEGES, so use TailMatches
  */
 	/* Complete GRANT/REVOKE with a list of roles and privileges */
-	else if (TailMatches1("GRANT|REVOKE"))
+	else if (TailMatches("GRANT|REVOKE"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion to grantable
 		 * privileges (can't grant roles)
 		 */
-		if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH_LIST10("SELECT", "INSERT", "UPDATE",
 								 "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
 								 "EXECUTE", "USAGE", "ALL");
@@ -2892,11 +2870,11 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny))
 	{
-		if (TailMatches1("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
+		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
 			COMPLETE_WITH_CONST("ON");
-		else if (TailMatches2("GRANT", MatchAny))
+		else if (TailMatches("GRANT", MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2913,13 +2891,13 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON"))
 	{
 		/*
 		 * With ALTER DEFAULT PRIVILEGES, restrict completion to the kinds of
 		 * objects supported.
 		 */
-		if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES"))
+		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH_LIST7("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
@@ -2943,13 +2921,13 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
 							"PROCEDURES IN SCHEMA",
 							"ROUTINES IN SCHEMA",
 							"SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
-	else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
+	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
 		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
 
 	/*
@@ -2958,31 +2936,31 @@ 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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", MatchAny))
 	{
-		if (TailMatches1("DATABASE"))
+		if (TailMatches("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-		else if (TailMatches1("DOMAIN"))
+		else if (TailMatches("DOMAIN"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-		else if (TailMatches1("FUNCTION"))
+		else if (TailMatches("FUNCTION"))
 			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-		else if (TailMatches1("LANGUAGE"))
+		else if (TailMatches("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-		else if (TailMatches1("PROCEDURE"))
+		else if (TailMatches("PROCEDURE"))
 			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
-		else if (TailMatches1("ROUTINE"))
+		else if (TailMatches("ROUTINE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-		else if (TailMatches1("SCHEMA"))
+		else if (TailMatches("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-		else if (TailMatches1("SEQUENCE"))
+		else if (TailMatches("SEQUENCE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-		else if (TailMatches1("TABLE"))
+		else if (TailMatches("TABLE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-		else if (TailMatches1("TABLESPACE"))
+		else if (TailMatches("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-		else if (TailMatches1("TYPE"))
+		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-		else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny))
+		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
@@ -2992,111 +2970,111 @@ psql_completion(const char *text, int start, int end)
 	 * Complete "GRANT/REVOKE ... TO/FROM" with username, PUBLIC,
 	 * CURRENT_USER, or SESSION_USER.
 	 */
-	else if ((HeadMatches1("GRANT") && TailMatches1("TO")) ||
-			 (HeadMatches1("REVOKE") && TailMatches1("FROM")))
+	else if ((HeadMatches("GRANT") && TailMatches("TO")) ||
+			 (HeadMatches("REVOKE") && TailMatches("FROM")))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
-	else if (HeadMatches3("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches1("TO|FROM"))
+	else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
-	else if (HeadMatches1("GRANT") && TailMatches3("ON", MatchAny, MatchAny))
+	else if (HeadMatches("GRANT") && TailMatches("ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (HeadMatches1("REVOKE") && TailMatches3("ON", MatchAny, MatchAny))
+	else if (HeadMatches("REVOKE") && TailMatches("ON", MatchAny, MatchAny))
 		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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
-		if (TailMatches8("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
-		if (TailMatches7("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
-		if (TailMatches6("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+		if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH_CONST("TO");
 		else
 			COMPLETE_WITH_CONST("FROM");
 	}
 
 /* GROUP BY */
-	else if (TailMatches3("FROM", MatchAny, "GROUP"))
+	else if (TailMatches("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH_CONST("BY");
 
 /* IMPORT FOREIGN SCHEMA */
-	else if (Matches1("IMPORT"))
+	else if (Matches("IMPORT"))
 		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
-	else if (Matches2("IMPORT", "FOREIGN"))
+	else if (Matches("IMPORT", "FOREIGN"))
 		COMPLETE_WITH_CONST("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
-	else if (TailMatches1("INSERT"))
+	else if (TailMatches("INSERT"))
 		COMPLETE_WITH_CONST("INTO");
 	/* Complete INSERT INTO with table names */
-	else if (TailMatches2("INSERT", "INTO"))
+	else if (TailMatches("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 (TailMatches("INSERT", "INTO", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
 	 * "TABLE" or "DEFAULT VALUES" or "OVERRIDING"
 	 */
-	else if (TailMatches3("INSERT", "INTO", MatchAny))
+	else if (TailMatches("INSERT", "INTO", MatchAny))
 		COMPLETE_WITH_LIST6("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
 	 * "TABLE" or "OVERRIDING"
 	 */
-	else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) &&
+	else if (TailMatches("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
 		COMPLETE_WITH_LIST4("SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/* Complete OVERRIDING */
-	else if (TailMatches1("OVERRIDING"))
+	else if (TailMatches("OVERRIDING"))
 		COMPLETE_WITH_LIST2("SYSTEM VALUE", "USER VALUE");
 
 	/* Complete after OVERRIDING clause */
-	else if (TailMatches3("OVERRIDING", MatchAny, "VALUE"))
+	else if (TailMatches("OVERRIDING", MatchAny, "VALUE"))
 		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
-	else if (TailMatches1("VALUES") && !TailMatches2("DEFAULT", "VALUES"))
+	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
 		COMPLETE_WITH_CONST("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
-	else if (Matches1("LOCK"))
+	else if (Matches("LOCK"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   " UNION SELECT 'TABLE'");
-	else if (Matches2("LOCK", "TABLE"))
+	else if (Matches("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 (Matches2("LOCK", MatchAnyExcept("TABLE")) ||
-			 Matches3("LOCK", "TABLE", MatchAny))
+	else if (Matches("LOCK", MatchAnyExcept("TABLE")) ||
+			 Matches("LOCK", "TABLE", MatchAny))
 		COMPLETE_WITH_CONST("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
-	else if (Matches3("LOCK", MatchAny, "IN") ||
-			 Matches4("LOCK", "TABLE", MatchAny, "IN"))
+	else if (Matches("LOCK", MatchAny, "IN") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN"))
 		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
@@ -3104,36 +3082,36 @@ psql_completion(const char *text, int start, int end)
 							"EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
+	else if (Matches("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
 		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
-	else if (Matches4("LOCK", MatchAny, "IN", "SHARE") ||
-			 Matches5("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
+	else if (Matches("LOCK", MatchAny, "IN", "SHARE") ||
+			 Matches("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
 		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
-	else if (TailMatches1("NOTIFY"))
+	else if (TailMatches("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 (TailMatches1("OPTIONS"))
+	else if (TailMatches("OPTIONS"))
 		COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
-	else if (TailMatches2("OWNER", "TO"))
+	else if (TailMatches("OWNER", "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
-	else if (TailMatches3("FROM", MatchAny, "ORDER"))
+	else if (TailMatches("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH_CONST("BY");
-	else if (TailMatches4("FROM", MatchAny, "ORDER", "BY"))
+	else if (TailMatches("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
-	else if (Matches3("PREPARE", MatchAny, "AS"))
+	else if (Matches("PREPARE", MatchAny, "AS"))
 		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
@@ -3142,61 +3120,61 @@ psql_completion(const char *text, int start, int end)
  */
 
 /* REASSIGN OWNED BY xxx TO yyy */
-	else if (Matches1("REASSIGN"))
+	else if (Matches("REASSIGN"))
 		COMPLETE_WITH_CONST("OWNED BY");
-	else if (Matches2("REASSIGN", "OWNED"))
+	else if (Matches("REASSIGN", "OWNED"))
 		COMPLETE_WITH_CONST("BY");
-	else if (Matches3("REASSIGN", "OWNED", "BY"))
+	else if (Matches("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (Matches4("REASSIGN", "OWNED", "BY", MatchAny))
+	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny))
 		COMPLETE_WITH_CONST("TO");
-	else if (Matches5("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
+	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
-	else if (Matches1("REFRESH"))
+	else if (Matches("REFRESH"))
 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
-	else if (Matches2("REFRESH", "MATERIALIZED"))
+	else if (Matches("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH_CONST("VIEW");
-	else if (Matches3("REFRESH", "MATERIALIZED", "VIEW"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (Matches4("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
 		COMPLETE_WITH_CONST("WITH");
-	else if (Matches5("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
 		COMPLETE_WITH_LIST2("NO DATA", "DATA");
-	else if (Matches6("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
-	else if (Matches7("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
+	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
 		COMPLETE_WITH_CONST("DATA");
 
 /* REINDEX */
-	else if (Matches1("REINDEX"))
+	else if (Matches("REINDEX"))
 		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
-	else if (Matches2("REINDEX", "TABLE"))
+	else if (Matches("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
-	else if (Matches2("REINDEX", "INDEX"))
+	else if (Matches("REINDEX", "INDEX"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (Matches2("REINDEX", "SCHEMA"))
+	else if (Matches("REINDEX", "SCHEMA"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (Matches2("REINDEX", "SYSTEM|DATABASE"))
+	else if (Matches("REINDEX", "SYSTEM|DATABASE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 
 /* SECURITY LABEL */
-	else if (Matches1("SECURITY"))
+	else if (Matches("SECURITY"))
 		COMPLETE_WITH_CONST("LABEL");
-	else if (Matches2("SECURITY", "LABEL"))
+	else if (Matches("SECURITY", "LABEL"))
 		COMPLETE_WITH_LIST2("ON", "FOR");
-	else if (Matches4("SECURITY", "LABEL", "FOR", MatchAny))
+	else if (Matches("SECURITY", "LABEL", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("ON");
-	else if (Matches3("SECURITY", "LABEL", "ON") ||
-			 Matches5("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
+	else if (Matches("SECURITY", "LABEL", "ON") ||
+			 Matches("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
 		static const char *const list_SECURITY_LABEL[] =
 		{"TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
@@ -3206,7 +3184,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
-	else if (Matches5("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
+	else if (Matches("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH_CONST("IS");
 
 /* SELECT */
@@ -3214,76 +3192,76 @@ psql_completion(const char *text, int start, int end)
 
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
-	else if (TailMatches1("SET|RESET") && !TailMatches3("UPDATE", MatchAny, "SET"))
+	else if (TailMatches("SET|RESET") && !TailMatches("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
-	else if (Matches1("SHOW"))
+	else if (Matches("SHOW"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
-	else if (Matches2("SET", "TRANSACTION"))
+	else if (Matches("SET", "TRANSACTION"))
 		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches2("BEGIN|START", "TRANSACTION") ||
-			 Matches2("BEGIN", "WORK") ||
-			 Matches1("BEGIN") ||
-			 Matches5("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
+	else if (Matches("BEGIN|START", "TRANSACTION") ||
+			 Matches("BEGIN", "WORK") ||
+			 Matches("BEGIN") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
 		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
-			 Matches2("BEGIN", "NOT") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
+			 Matches("BEGIN", "NOT") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
 		COMPLETE_WITH_CONST("DEFERRABLE");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
-			 Matches2("BEGIN", "ISOLATION") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
+			 Matches("BEGIN", "ISOLATION") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
 		COMPLETE_WITH_CONST("LEVEL");
-	else if (Matches4("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
-			 Matches3("BEGIN", "ISOLATION", "LEVEL") ||
-			 Matches7("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
 		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "READ") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL", "READ") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
 		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
-	else if (Matches5("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches4("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
-			 Matches8("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
+			 Matches("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
 		COMPLETE_WITH_CONST("READ");
-	else if (Matches3("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
-			 Matches2("BEGIN", "READ") ||
-			 Matches6("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
+	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
+			 Matches("BEGIN", "READ") ||
+			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
 		COMPLETE_WITH_LIST2("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
-	else if (Matches2("SET", "CONSTRAINTS"))
+	else if (Matches("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 (Matches3("SET", "CONSTRAINTS", MatchAny))
+	else if (Matches("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
-	else if (Matches2("SET", "ROLE"))
+	else if (Matches("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
-	else if (Matches2("SET", "SESSION"))
+	else if (Matches("SET", "SESSION"))
 		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
-	else if (Matches3("SET", "SESSION", "AUTHORIZATION"))
+	else if (Matches("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
-	else if (Matches2("RESET", "SESSION"))
+	else if (Matches("RESET", "SESSION"))
 		COMPLETE_WITH_CONST("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
-	else if (Matches2("SET", MatchAny))
+	else if (Matches("SET", MatchAny))
 		COMPLETE_WITH_CONST("TO");
 
 	/*
 	 * Complete ALTER DATABASE|FUNCTION||PROCEDURE|ROLE|ROUTINE|USER ... SET
 	 * <name>
 	 */
-	else if (HeadMatches2("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
-			 TailMatches2("SET", MatchAny))
+	else if (HeadMatches("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
+			 TailMatches("SET", MatchAny))
 		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
-	else if (TailMatches3("SET", MatchAny, "TO|="))
+	else if (TailMatches("SET", MatchAny, "TO|="))
 	{
 		/* special cased code for individual GUCs */
-		if (TailMatches2("DateStyle", "TO|="))
+		if (TailMatches("DateStyle", "TO|="))
 		{
 			static const char *const my_list[] =
 			{"ISO", "SQL", "Postgres", "German",
@@ -3293,7 +3271,7 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST(my_list);
 		}
-		else if (TailMatches2("search_path", "TO|="))
+		else if (TailMatches("search_path", "TO|="))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
 								" AND nspname not like 'pg\\_toast%%' "
 								" AND nspname not like 'pg\\_temp%%' "
@@ -3322,83 +3300,83 @@ psql_completion(const char *text, int start, int end)
 	}
 
 /* START TRANSACTION */
-	else if (Matches1("START"))
+	else if (Matches("START"))
 		COMPLETE_WITH_CONST("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
-	else if (Matches1("TABLE"))
+	else if (Matches("TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* TABLESAMPLE */
-	else if (TailMatches1("TABLESAMPLE"))
+	else if (TailMatches("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
-	else if (TailMatches2("TABLESAMPLE", MatchAny))
+	else if (TailMatches("TABLESAMPLE", MatchAny))
 		COMPLETE_WITH_CONST("(");
 
 /* TRUNCATE */
-	else if (Matches1("TRUNCATE"))
+	else if (Matches("TRUNCATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* UNLISTEN */
-	else if (Matches1("UNLISTEN"))
+	else if (Matches("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 --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
-	else if (TailMatches1("UPDATE"))
+	else if (TailMatches("UPDATE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
-	else if (TailMatches2("UPDATE", MatchAny))
+	else if (TailMatches("UPDATE", MatchAny))
 		COMPLETE_WITH_CONST("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
-	else if (TailMatches3("UPDATE", MatchAny, "SET"))
+	else if (TailMatches("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
-	else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
+	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAny))
 		COMPLETE_WITH_CONST("=");
 
 /* USER MAPPING */
-	else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
+	else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH_CONST("FOR");
-	else if (Matches4("CREATE", "USER", "MAPPING", "FOR"))
+	else if (Matches("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
 							" UNION SELECT 'PUBLIC'"
 							" UNION SELECT 'USER'");
-	else if (Matches4("ALTER|DROP", "USER", "MAPPING", "FOR"))
+	else if (Matches("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (Matches5("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
+	else if (Matches("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
 		COMPLETE_WITH_CONST("SERVER");
-	else if (Matches7("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
+	else if (Matches("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
 		COMPLETE_WITH_CONST("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
-	else if (Matches1("VACUUM"))
+	else if (Matches("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'FULL'"
 								   " UNION SELECT 'FREEZE'"
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches2("VACUUM", "FULL|FREEZE"))
+	else if (Matches("VACUUM", "FULL|FREEZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'"
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "ANALYZE"))
+	else if (Matches("VACUUM", "FULL|FREEZE", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (Matches3("VACUUM", "FULL|FREEZE", "VERBOSE"))
+	else if (Matches("VACUUM", "FULL|FREEZE", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "VERBOSE"))
+	else if (Matches("VACUUM", "VERBOSE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'ANALYZE'");
-	else if (Matches2("VACUUM", "ANALYZE"))
+	else if (Matches("VACUUM", "ANALYZE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
 								   " UNION SELECT 'VERBOSE'");
-	else if (HeadMatches1("VACUUM"))
+	else if (HeadMatches("VACUUM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
@@ -3407,114 +3385,114 @@ 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 (Matches1("WITH"))
+	else if (Matches("WITH"))
 		COMPLETE_WITH_CONST("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
-	else if (Matches1("ANALYZE"))
+	else if (Matches("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 (TailMatches2(MatchAny, "WHERE"))
+	else if (TailMatches(MatchAny, "WHERE"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
-	else if (TailMatches1("FROM") && !Matches3("COPY|\\copy", MatchAny, "FROM"))
+	else if (TailMatches("FROM") && !Matches("COPY|\\copy", MatchAny, "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
-	else if (TailMatches1("JOIN"))
+	else if (TailMatches("JOIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
-	else if (TailMatchesCS1("\\?"))
+	else if (TailMatchesCS("\\?"))
 		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
-	else if (TailMatchesCS1("\\connect|\\c"))
+	else if (TailMatchesCS("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (TailMatchesCS2("\\connect|\\c", MatchAny))
+	else if (TailMatchesCS("\\connect|\\c", MatchAny))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
-	else if (TailMatchesCS1("\\da*"))
+	else if (TailMatchesCS("\\da*"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-	else if (TailMatchesCS1("\\dA*"))
+	else if (TailMatchesCS("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
-	else if (TailMatchesCS1("\\db*"))
+	else if (TailMatchesCS("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-	else if (TailMatchesCS1("\\dD*"))
+	else if (TailMatchesCS("\\dD*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-	else if (TailMatchesCS1("\\des*"))
+	else if (TailMatchesCS("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
-	else if (TailMatchesCS1("\\deu*"))
+	else if (TailMatchesCS("\\deu*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
-	else if (TailMatchesCS1("\\dew*"))
+	else if (TailMatchesCS("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
-	else if (TailMatchesCS1("\\df*"))
+	else if (TailMatchesCS("\\df*"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
-	else if (TailMatchesCS1("\\dFd*"))
+	else if (TailMatchesCS("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-	else if (TailMatchesCS1("\\dFp*"))
+	else if (TailMatchesCS("\\dFp*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-	else if (TailMatchesCS1("\\dFt*"))
+	else if (TailMatchesCS("\\dFt*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	/* must be at end of \dF alternatives: */
-	else if (TailMatchesCS1("\\dF*"))
+	else if (TailMatchesCS("\\dF*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
-	else if (TailMatchesCS1("\\di*"))
+	else if (TailMatchesCS("\\di*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-	else if (TailMatchesCS1("\\dL*"))
+	else if (TailMatchesCS("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
-	else if (TailMatchesCS1("\\dn*"))
+	else if (TailMatchesCS("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-	else if (TailMatchesCS1("\\dp") || TailMatchesCS1("\\z"))
+	else if (TailMatchesCS("\\dp") || TailMatchesCS("\\z"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
-	else if (TailMatchesCS1("\\ds*"))
+	else if (TailMatchesCS("\\ds*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-	else if (TailMatchesCS1("\\dt*"))
+	else if (TailMatchesCS("\\dt*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-	else if (TailMatchesCS1("\\dT*"))
+	else if (TailMatchesCS("\\dT*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-	else if (TailMatchesCS1("\\du*") || TailMatchesCS1("\\dg*"))
+	else if (TailMatchesCS("\\du*") || TailMatchesCS("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\dv*"))
+	else if (TailMatchesCS("\\dv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\dx*"))
+	else if (TailMatchesCS("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
-	else if (TailMatchesCS1("\\dm*"))
+	else if (TailMatchesCS("\\dm*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
-	else if (TailMatchesCS1("\\dE*"))
+	else if (TailMatchesCS("\\dE*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
-	else if (TailMatchesCS1("\\dy*"))
+	else if (TailMatchesCS("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
-	else if (TailMatchesCS1("\\d*"))
+	else if (TailMatchesCS("\\d*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
-	else if (TailMatchesCS1("\\ef"))
+	else if (TailMatchesCS("\\ef"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-	else if (TailMatchesCS1("\\ev"))
+	else if (TailMatchesCS("\\ev"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
-	else if (TailMatchesCS1("\\encoding"))
+	else if (TailMatchesCS("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
-	else if (TailMatchesCS1("\\h|\\help"))
+	else if (TailMatchesCS("\\h|\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
-	else if (TailMatchesCS2("\\h|\\help", MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny))
 	{
-		if (TailMatches1("DROP"))
+		if (TailMatches("DROP"))
 			matches = completion_matches(text, drop_command_generator);
-		else if (TailMatches1("ALTER"))
+		else if (TailMatches("ALTER"))
 			matches = completion_matches(text, alter_command_generator);
 
 		/*
@@ -3522,39 +3500,39 @@ psql_completion(const char *text, int start, int end)
 		 * repeated here
 		 */
 	}
-	else if (TailMatchesCS3("\\h|\\help", MatchAny, MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny))
 	{
-		if (TailMatches2("CREATE|DROP", "ACCESS"))
+		if (TailMatches("CREATE|DROP", "ACCESS"))
 			COMPLETE_WITH_CONST("METHOD");
-		else if (TailMatches2("ALTER", "DEFAULT"))
+		else if (TailMatches("ALTER", "DEFAULT"))
 			COMPLETE_WITH_CONST("PRIVILEGES");
-		else if (TailMatches2("CREATE|ALTER|DROP", "EVENT"))
+		else if (TailMatches("CREATE|ALTER|DROP", "EVENT"))
 			COMPLETE_WITH_CONST("TRIGGER");
-		else if (TailMatches2("CREATE|ALTER|DROP", "FOREIGN"))
+		else if (TailMatches("CREATE|ALTER|DROP", "FOREIGN"))
 			COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
-		else if (TailMatches2("ALTER", "LARGE"))
+		else if (TailMatches("ALTER", "LARGE"))
 			COMPLETE_WITH_CONST("OBJECT");
-		else if (TailMatches2("CREATE|ALTER|DROP", "MATERIALIZED"))
+		else if (TailMatches("CREATE|ALTER|DROP", "MATERIALIZED"))
 			COMPLETE_WITH_CONST("VIEW");
-		else if (TailMatches2("CREATE|ALTER|DROP", "TEXT"))
+		else if (TailMatches("CREATE|ALTER|DROP", "TEXT"))
 			COMPLETE_WITH_CONST("SEARCH");
-		else if (TailMatches2("CREATE|ALTER|DROP", "USER"))
+		else if (TailMatches("CREATE|ALTER|DROP", "USER"))
 			COMPLETE_WITH_CONST("MAPPING FOR");
 	}
-	else if (TailMatchesCS4("\\h|\\help", MatchAny, MatchAny, MatchAny))
+	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny, MatchAny))
 	{
-		if (TailMatches3("CREATE|ALTER|DROP", "FOREIGN", "DATA"))
+		if (TailMatches("CREATE|ALTER|DROP", "FOREIGN", "DATA"))
 			COMPLETE_WITH_CONST("WRAPPER");
-		else if (TailMatches3("CREATE|ALTER|DROP", "TEXT", "SEARCH"))
+		else if (TailMatches("CREATE|ALTER|DROP", "TEXT", "SEARCH"))
 			COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
-		else if (TailMatches3("CREATE|ALTER|DROP", "USER", "MAPPING"))
+		else if (TailMatches("CREATE|ALTER|DROP", "USER", "MAPPING"))
 			COMPLETE_WITH_CONST("FOR");
 	}
-	else if (TailMatchesCS1("\\l*") && !TailMatchesCS1("\\lo*"))
+	else if (TailMatchesCS("\\l*") && !TailMatchesCS("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-	else if (TailMatchesCS1("\\password"))
+	else if (TailMatchesCS("\\password"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	else if (TailMatchesCS1("\\pset"))
+	else if (TailMatchesCS("\\pset"))
 	{
 		static const char *const my_list[] =
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
@@ -3565,9 +3543,9 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (TailMatchesCS2("\\pset", MatchAny))
+	else if (TailMatchesCS("\\pset", MatchAny))
 	{
-		if (TailMatchesCS1("format"))
+		if (TailMatchesCS("format"))
 		{
 			static const char *const my_list[] =
 			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
@@ -3575,46 +3553,46 @@ psql_completion(const char *text, int start, int end)
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
-		else if (TailMatchesCS1("linestyle"))
+		else if (TailMatchesCS("linestyle"))
 			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
-		else if (TailMatchesCS1("pager"))
+		else if (TailMatchesCS("pager"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "always");
-		else if (TailMatchesCS1("unicode_border_linestyle|"
+		else if (TailMatchesCS("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
 			COMPLETE_WITH_LIST_CS2("single", "double");
 	}
-	else if (TailMatchesCS1("\\unset"))
+	else if (TailMatchesCS("\\unset"))
 		matches = complete_from_variables(text, "", "", true);
-	else if (TailMatchesCS1("\\set"))
+	else if (TailMatchesCS("\\set"))
 		matches = complete_from_variables(text, "", "", false);
-	else if (TailMatchesCS2("\\set", MatchAny))
+	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS1("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_LIST_CS2("on", "off");
-		else if (TailMatchesCS1("COMP_KEYWORD_CASE"))
+		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_LIST_CS4("lower", "upper",
 								   "preserve-lower", "preserve-upper");
-		else if (TailMatchesCS1("ECHO"))
+		else if (TailMatchesCS("ECHO"))
 			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
-		else if (TailMatchesCS1("ECHO_HIDDEN"))
+		else if (TailMatchesCS("ECHO_HIDDEN"))
 			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
-		else if (TailMatchesCS1("HISTCONTROL"))
+		else if (TailMatchesCS("HISTCONTROL"))
 			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
 								   "ignoreboth", "none");
-		else if (TailMatchesCS1("ON_ERROR_ROLLBACK"))
+		else if (TailMatchesCS("ON_ERROR_ROLLBACK"))
 			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
-		else if (TailMatchesCS1("SHOW_CONTEXT"))
+		else if (TailMatchesCS("SHOW_CONTEXT"))
 			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
-		else if (TailMatchesCS1("VERBOSITY"))
+		else if (TailMatchesCS("VERBOSITY"))
 			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
 	}
-	else if (TailMatchesCS1("\\sf*"))
+	else if (TailMatchesCS("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
-	else if (TailMatchesCS1("\\sv*"))
+	else if (TailMatchesCS("\\sv*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
-	else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
+	else if (TailMatchesCS("\\cd|\\e|\\edit|\\g|\\i|\\include|"
 							"\\ir|\\include_relative|\\o|\\out|"
 							"\\s|\\w|\\write|\\lo_import"))
 	{
diff --git a/src/include/c.h b/src/include/c.h
index 901d791198..23dd9a4135 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -224,6 +224,32 @@
 #define CppAsString2(x)			CppAsString(x)
 #define CppConcat(x, y)			x##y
 
+/*
+ * PP_NARG - return the number of arguments a vararg macro gets passed.
+ *
+ * Making C pretty by making it ugly again.
+ */
+#define PP_NARG(...) \
+	PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
+#define PP_NARG_(...) \
+	PP_ARG_N(__VA_ARGS__)
+#define PP_ARG_N( \
+	_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
+	_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
+	_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
+	_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
+	_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
+	_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
+	_61,_62,_63,  N, ...) N
+#define PP_RSEQ_N() \
+	63,62,61,60,                   \
+	59,58,57,56,55,54,53,52,51,50, \
+	49,48,47,46,45,44,43,42,41,40, \
+	39,38,37,36,35,34,33,32,31,30, \
+	29,28,27,26,25,24,23,22,21,20, \
+	19,18,17,16,15,14,13,12,11,10, \
+	9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+
 /*
  * dummyret is used to set return values in macros that use ?: to make
  * assignments.  gcc wants these to be void, other compilers like char
-- 
2.17.0



  [application/octet-stream] 0002-Variadic-COMPLETE_WITH-.-macros.patch (84.0K, ../../CAEepm=1S6ZgrbtyAms5RJHS2OE4v2Sa2XKi7pNc9BC3e4=CSQg@mail.gmail.com/3-0002-Variadic-COMPLETE_WITH-.-macros.patch)
  download | inline diff:
From 2bb8398a344872b02c7df27f61c41027f874068e Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Fri, 21 Sep 2018 20:16:19 +1200
Subject: [PATCH 2/2] Variadic COMPLETE_WITH(...) macros.

---
 src/bin/psql/tab-complete.c | 749 ++++++++++++++++--------------------
 1 file changed, 337 insertions(+), 412 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 21a4a807d9..b64928355d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -173,7 +173,7 @@ static bool completion_case_sensitive;	/* completion is case sensitive */
  * 2) The results from a schema query you pass it.
  *	  We support both simple and versioned schema queries.
  * 3) The items from a null-pointer-terminated list (with or without
- *	  case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
+ *	  case-sensitive comparison; see also COMPLETE_WITH, below).
  * 4) A string constant.
  * 5) The list of attributes of the given table (possibly schema-qualified).
  * 6/ The list of arguments to the given function (possibly schema-qualified).
@@ -204,27 +204,37 @@ do { \
 	matches = completion_matches(text, complete_from_versioned_schema_query); \
 } while (0)
 
-#define COMPLETE_WITH_LIST_CS(list) \
+#define COMPLETE_WITH_LIST_X(cs, list) \
 do { \
-	completion_charpp = list; \
-	completion_case_sensitive = true; \
-	matches = completion_matches(text, complete_from_list); \
+	completion_case_sensitive = (cs); \
+	if (!(list)[1]) \
+	{ \
+		completion_charp = (list)[0]; \
+		matches = completion_matches(text, complete_from_const); \
+	} \
+	else \
+	{ \
+		completion_charpp = (list); \
+		matches = completion_matches(text, complete_from_list); \
+	} \
 } while (0)
 
-#define COMPLETE_WITH_LIST(list) \
+#define COMPLETE_WITH_LIST(list) COMPLETE_WITH_LIST_X(false, (list))
+#define COMPLETE_WITH_LIST_CS(list) COMPLETE_WITH_LIST_X(true, (list))
+
+#define COMPLETE_WITH(...) \
 do { \
-	completion_charpp = list; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_list); \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_LIST(list); \
 } while (0)
 
-#define COMPLETE_WITH_CONST(string) \
+#define COMPLETE_WITH_CS(...) \
 do { \
-	completion_charp = string; \
-	completion_case_sensitive = false; \
-	matches = completion_matches(text, complete_from_const); \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
+
 #define COMPLETE_WITH_ATTR(relation, addon) \
 do { \
 	char   *_completion_schema; \
@@ -300,91 +310,6 @@ do { \
 	matches = completion_matches(text, complete_from_query); \
 } while (0)
 
-/*
- * These macros simplify use of COMPLETE_WITH_LIST for short, fixed lists.
- * There is no COMPLETE_WITH_LIST1; use COMPLETE_WITH_CONST for that case.
- */
-#define COMPLETE_WITH_LIST2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST(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 }; \
-	COMPLETE_WITH_LIST(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 }; \
-	COMPLETE_WITH_LIST(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 }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST9(s1, s2, s3, s4, s5, s6, s7, s8, s9) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, s6, s7, s8, s9, NULL }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST10(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 }; \
-	COMPLETE_WITH_LIST(list); \
-} while (0)
-
-/*
- * Likewise for COMPLETE_WITH_LIST_CS.
- */
-#define COMPLETE_WITH_LIST_CS2(s1, s2) \
-do { \
-	static const char *const list[] = { s1, s2, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS3(s1, s2, s3) \
-do { \
-	static const char *const list[] = { s1, s2, s3, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS4(s1, s2, s3, s4) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
-#define COMPLETE_WITH_LIST_CS5(s1, s2, s3, s4, s5) \
-do { \
-	static const char *const list[] = { s1, s2, s3, s4, s5, NULL }; \
-	COMPLETE_WITH_LIST_CS(list); \
-} while (0)
-
 /*
  * Assembly instructions for schema queries
  */
@@ -1490,51 +1415,51 @@ psql_completion(const char *text, int start, int end)
 		matches = completion_matches(text, alter_command_generator);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx */
 	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny))
-		COMPLETE_WITH_LIST2("SET TABLESPACE", "OWNED BY");
+		COMPLETE_WITH("SET TABLESPACE", "OWNED BY");
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY */
 	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW ALL IN TABLESPACE xxx OWNED BY xxx */
 	else if (TailMatches("ALL", "IN", "TABLESPACE", MatchAny, "OWNED", "BY", MatchAny))
-		COMPLETE_WITH_CONST("SET TABLESPACE");
+		COMPLETE_WITH("SET TABLESPACE");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> */
 	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	/* ALTER AGGREGATE,FUNCTION,PROCEDURE,ROUTINE <name> (...) */
 	else if (Matches("ALTER", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, MatchAny))
 	{
 		if (ends_with(prev_wd, ')'))
-			COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+			COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 		else
 			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	}
 	/* ALTER PUBLICATION <name> */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny))
-		COMPLETE_WITH_LIST5("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
+		COMPLETE_WITH("ADD TABLE", "DROP TABLE", "OWNER TO", "RENAME TO", "SET");
 	/* ALTER PUBLICATION <name> SET */
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
-		COMPLETE_WITH_LIST2("(", "TABLE");
+		COMPLETE_WITH("(", "TABLE");
 	/* ALTER PUBLICATION <name> SET ( */
 	else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
-		COMPLETE_WITH_CONST("publish");
+		COMPLETE_WITH("publish");
 	/* ALTER SUBSCRIPTION <name> */
 	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny))
-		COMPLETE_WITH_LIST7("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
+		COMPLETE_WITH("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
 							"RENAME TO", "REFRESH PUBLICATION", "SET");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
 			 TailMatches("REFRESH", "PUBLICATION"))
-		COMPLETE_WITH_CONST("WITH (");
+		COMPLETE_WITH("WITH (");
 	/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
 			 TailMatches("REFRESH", "PUBLICATION", "WITH", "("))
-		COMPLETE_WITH_CONST("copy_data");
+		COMPLETE_WITH("copy_data");
 	/* ALTER SUBSCRIPTION <name> SET */
 	else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, "SET"))
-		COMPLETE_WITH_LIST2("(", "PUBLICATION");
+		COMPLETE_WITH("(", "PUBLICATION");
 	/* ALTER SUBSCRIPTION <name> SET ( */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "("))
-		COMPLETE_WITH_LIST2("slot_name", "synchronous_commit");
+		COMPLETE_WITH("slot_name", "synchronous_commit");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches("SET", "PUBLICATION"))
 	{
@@ -1543,26 +1468,26 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
 			 TailMatches("SET", "PUBLICATION", MatchAny))
-		COMPLETE_WITH_CONST("WITH (");
+		COMPLETE_WITH("WITH (");
 	/* ALTER SUBSCRIPTION <name> SET PUBLICATION <name> WITH ( */
 	else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny) &&
 			 TailMatches("SET", "PUBLICATION", MatchAny, "WITH", "("))
-		COMPLETE_WITH_LIST2("copy_data", "refresh");
+		COMPLETE_WITH("copy_data", "refresh");
 	/* ALTER SCHEMA <name> */
 	else if (Matches("ALTER", "SCHEMA", MatchAny))
-		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
+		COMPLETE_WITH("OWNER TO", "RENAME TO");
 
 	/* ALTER COLLATION <name> */
 	else if (Matches("ALTER", "COLLATION", MatchAny))
-		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER CONVERSION <name> */
 	else if (Matches("ALTER", "CONVERSION", MatchAny))
-		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER DATABASE <name> */
 	else if (Matches("ALTER", "DATABASE", MatchAny))
-		COMPLETE_WITH_LIST7("RESET", "SET", "OWNER TO", "RENAME TO",
+		COMPLETE_WITH("RESET", "SET", "OWNER TO", "RENAME TO",
 							"IS_TEMPLATE", "ALLOW_CONNECTIONS",
 							"CONNECTION LIMIT");
 
@@ -1576,15 +1501,15 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER EVENT TRIGGER <name> */
 	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny))
-		COMPLETE_WITH_LIST4("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
+		COMPLETE_WITH("DISABLE", "ENABLE", "OWNER TO", "RENAME TO");
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
 	else if (Matches("ALTER", "EVENT", "TRIGGER", MatchAny, "ENABLE"))
-		COMPLETE_WITH_LIST2("REPLICA", "ALWAYS");
+		COMPLETE_WITH("REPLICA", "ALWAYS");
 
 	/* ALTER EXTENSION <name> */
 	else if (Matches("ALTER", "EXTENSION", MatchAny))
-		COMPLETE_WITH_LIST4("ADD", "DROP", "UPDATE", "SET SCHEMA");
+		COMPLETE_WITH("ADD", "DROP", "UPDATE", "SET SCHEMA");
 
 	/* ALTER EXTENSION <name> UPDATE */
 	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
@@ -1602,11 +1527,11 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER FOREIGN */
 	else if (Matches("ALTER", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+		COMPLETE_WITH("DATA WRAPPER", "TABLE");
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
 	else if (Matches("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-		COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
+		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO");
 
 	/* ALTER FOREIGN TABLE <name> */
 	else if (Matches("ALTER", "FOREIGN", "TABLE", MatchAny))
@@ -1625,31 +1550,31 @@ psql_completion(const char *text, int start, int end)
 								   "UNION SELECT 'ALL IN TABLESPACE'");
 	/* ALTER INDEX <name> */
 	else if (Matches("ALTER", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST6("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
+		COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
 							"RESET", "ATTACH PARTITION");
 	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH"))
-		COMPLETE_WITH_CONST("PARTITION");
+		COMPLETE_WITH("PARTITION");
 	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	/* ALTER INDEX <name> ALTER COLUMN <colnum> */
 	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
-		COMPLETE_WITH_CONST("SET STATISTICS");
+		COMPLETE_WITH("SET STATISTICS");
 	/* ALTER INDEX <name> SET */
 	else if (Matches("ALTER", "INDEX", MatchAny, "SET"))
-		COMPLETE_WITH_LIST2("(", "TABLESPACE");
+		COMPLETE_WITH("(", "TABLESPACE");
 	/* ALTER INDEX <name> RESET */
 	else if (Matches("ALTER", "INDEX", MatchAny, "RESET"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	/* ALTER INDEX <foo> SET|RESET ( */
 	else if (Matches("ALTER", "INDEX", MatchAny, "RESET", "("))
-		COMPLETE_WITH_LIST8("fillfactor", "recheck_on_update",
+		COMPLETE_WITH("fillfactor", "recheck_on_update",
 							"vacuum_cleanup_index_scale_factor",	/* BTREE */
 							"fastupdate", "gin_pending_list_limit", /* GIN */
 							"buffering",	/* GiST */
 							"pages_per_range", "autosummarize"	/* BRIN */
 			);
 	else if (Matches("ALTER", "INDEX", MatchAny, "SET", "("))
-		COMPLETE_WITH_LIST8("fillfactor =", "recheck_on_update =",
+		COMPLETE_WITH("fillfactor =", "recheck_on_update =",
 							"vacuum_cleanup_index_scale_factor =",	/* BTREE */
 							"fastupdate =", "gin_pending_list_limit =", /* GIN */
 							"buffering =",	/* GiST */
@@ -1658,11 +1583,11 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER LANGUAGE <name> */
 	else if (Matches("ALTER", "LANGUAGE", MatchAny))
-		COMPLETE_WITH_LIST2("OWNER_TO", "RENAME TO");
+		COMPLETE_WITH("OWNER_TO", "RENAME TO");
 
 	/* ALTER LARGE OBJECT <oid> */
 	else if (Matches("ALTER", "LARGE", "OBJECT", MatchAny))
-		COMPLETE_WITH_CONST("OWNER TO");
+		COMPLETE_WITH("OWNER TO");
 
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW"))
@@ -1701,39 +1626,39 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER DEFAULT PRIVILEGES */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES"))
-		COMPLETE_WITH_LIST2("FOR ROLE", "IN SCHEMA");
+		COMPLETE_WITH("FOR ROLE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR"))
-		COMPLETE_WITH_CONST("ROLE");
+		COMPLETE_WITH("ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN"))
-		COMPLETE_WITH_CONST("SCHEMA");
+		COMPLETE_WITH("SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny))
-		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "IN SCHEMA");
+		COMPLETE_WITH("GRANT", "REVOKE", "IN SCHEMA");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny))
-		COMPLETE_WITH_LIST3("GRANT", "REVOKE", "FOR ROLE");
+		COMPLETE_WITH("GRANT", "REVOKE", "FOR ROLE");
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR"))
-		COMPLETE_WITH_CONST("ROLE");
+		COMPLETE_WITH("ROLE");
 	/* ALTER DEFAULT PRIVILEGES FOR ROLE|USER ... IN SCHEMA ... */
 	/* ALTER DEFAULT PRIVILEGES IN SCHEMA ... FOR ROLE|USER ... */
 	else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", "FOR", "ROLE|USER",
 					  MatchAny, "IN", "SCHEMA", MatchAny) ||
 			 Matches("ALTER", "DEFAULT", "PRIVILEGES", "IN", "SCHEMA",
 					  MatchAny, "FOR", "ROLE|USER", MatchAny))
-		COMPLETE_WITH_LIST2("GRANT", "REVOKE");
+		COMPLETE_WITH("GRANT", "REVOKE");
 	/* ALTER DOMAIN <name> */
 	else if (Matches("ALTER", "DOMAIN", MatchAny))
-		COMPLETE_WITH_LIST6("ADD", "DROP", "OWNER TO", "RENAME", "SET",
+		COMPLETE_WITH("ADD", "DROP", "OWNER TO", "RENAME", "SET",
 							"VALIDATE CONSTRAINT");
 	/* ALTER DOMAIN <sth> DROP */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP"))
-		COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL");
+		COMPLETE_WITH("CONSTRAINT", "DEFAULT", "NOT NULL");
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
@@ -1742,14 +1667,14 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME"))
-		COMPLETE_WITH_LIST2("CONSTRAINT", "TO");
+		COMPLETE_WITH("CONSTRAINT", "TO");
 	/* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME", "CONSTRAINT", MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 
 	/* ALTER DOMAIN <sth> SET */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "SET"))
-		COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA");
+		COMPLETE_WITH("DEFAULT", "NOT NULL", "SCHEMA");
 	/* ALTER SEQUENCE <name> */
 	else if (Matches("ALTER", "SEQUENCE", MatchAny))
 	{
@@ -1761,27 +1686,27 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* ALTER SEQUENCE <name> NO */
 	else if (Matches("ALTER", "SEQUENCE", MatchAny, "NO"))
-		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
+		COMPLETE_WITH("MINVALUE", "MAXVALUE", "CYCLE");
 	/* ALTER SERVER <name> */
 	else if (Matches("ALTER", "SERVER", MatchAny))
-		COMPLETE_WITH_LIST4("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
+		COMPLETE_WITH("VERSION", "OPTIONS", "OWNER TO", "RENAME TO");
 	/* ALTER SERVER <name> VERSION <version> */
 	else if (Matches("ALTER", "SERVER", MatchAny, "VERSION", MatchAny))
-		COMPLETE_WITH_CONST("OPTIONS");
+		COMPLETE_WITH("OPTIONS");
 	/* ALTER SYSTEM SET, RESET, RESET ALL */
 	else if (Matches("ALTER", "SYSTEM"))
-		COMPLETE_WITH_LIST2("SET", "RESET");
+		COMPLETE_WITH("SET", "RESET");
 	else if (Matches("ALTER", "SYSTEM", "SET|RESET"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
 	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 	/* ALTER VIEW <name> */
 	else if (Matches("ALTER", "VIEW", MatchAny))
-		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
+		COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 	/* ALTER MATERIALIZED VIEW <name> */
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny))
-		COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO",
+		COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO",
 							"SET SCHEMA");
 
 	/* ALTER POLICY <name> */
@@ -1789,7 +1714,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* ALTER POLICY <name> ON */
 	else if (Matches("ALTER", "POLICY", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON"))
 	{
@@ -1798,20 +1723,20 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_LIST4("RENAME TO", "TO", "USING (", "WITH CHECK (");
+		COMPLETE_WITH("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 	/* ALTER RULE <name>, add ON */
 	else if (Matches("ALTER", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
 	else if (Matches("ALTER", "RULE", MatchAny, "ON"))
@@ -1822,15 +1747,15 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER RULE <name> ON <name> */
 	else if (Matches("ALTER", "RULE", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_CONST("RENAME TO");
+		COMPLETE_WITH("RENAME TO");
 
 	/* ALTER STATISTICS <name> */
 	else if (Matches("ALTER", "STATISTICS", MatchAny))
-		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* ALTER TRIGGER <name>, add ON */
 	else if (Matches("ALTER", "TRIGGER", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 
 	else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny))
 	{
@@ -1846,7 +1771,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_CONST("RENAME TO");
+		COMPLETE_WITH("RENAME TO");
 
 	/*
 	 * If we detect ALTER TABLE <name>, suggest sub commands
@@ -1863,10 +1788,10 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* ALTER TABLE xxx ENABLE */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE"))
-		COMPLETE_WITH_LIST5("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
+		COMPLETE_WITH("ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE",
 							"TRIGGER");
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "REPLICA|ALWAYS"))
-		COMPLETE_WITH_LIST2("RULE", "TRIGGER");
+		COMPLETE_WITH("RULE", "TRIGGER");
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
@@ -1895,7 +1820,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* ALTER TABLE xxx DISABLE */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE"))
-		COMPLETE_WITH_LIST3("ROW LEVEL SECURITY", "RULE", "TRIGGER");
+		COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER");
 	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
 		completion_info_charp = prev3_wd;
@@ -1919,15 +1844,15 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TABLE xxx RENAME yyy */
 	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 
 	/* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
 	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", "COLUMN|CONSTRAINT", MatchAnyExcept("TO")))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 
 	/* If we have ALTER TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DROP"))
-		COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT");
+		COMPLETE_WITH("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
@@ -1944,25 +1869,25 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
 			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny))
-		COMPLETE_WITH_LIST6("TYPE", "SET", "RESET", "RESTART", "ADD", "DROP");
+		COMPLETE_WITH("TYPE", "SET", "RESET", "RESTART", "ADD", "DROP");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
-		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
+		COMPLETE_WITH("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
-		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
+		COMPLETE_WITH("n_distinct", "n_distinct_inherited");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
-		COMPLETE_WITH_LIST4("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
+		COMPLETE_WITH("PLAIN", "EXTERNAL", "EXTENDED", "MAIN");
 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") ||
 			 Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP"))
-		COMPLETE_WITH_LIST3("DEFAULT", "IDENTITY", "NOT NULL");
+		COMPLETE_WITH("DEFAULT", "IDENTITY", "NOT NULL");
 	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER"))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
 		completion_info_charp = prev3_wd;
@@ -1970,7 +1895,7 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches("ALTER", "TABLE", MatchAny, "SET"))
-		COMPLETE_WITH_LIST7("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
+		COMPLETE_WITH("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED",
 							"WITH", "WITHOUT");
 
 	/*
@@ -1981,13 +1906,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	/* If we have ALTER TABLE <sth> SET WITH provide OIDS */
 	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITH"))
-		COMPLETE_WITH_CONST("OIDS");
+		COMPLETE_WITH("OIDS");
 	/* If we have ALTER TABLE <sth> SET WITHOUT provide CLUSTER or OIDS */
 	else if (Matches("ALTER", "TABLE", MatchAny, "SET", "WITHOUT"))
-		COMPLETE_WITH_LIST2("CLUSTER", "OIDS");
+		COMPLETE_WITH("CLUSTER", "OIDS");
 	/* ALTER TABLE <foo> RESET */
 	else if (Matches("ALTER", "TABLE", MatchAny, "RESET"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	/* ALTER TABLE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLE", MatchAny, "SET|RESET", "("))
 	{
@@ -2034,11 +1959,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
-		COMPLETE_WITH_CONST("INDEX");
+		COMPLETE_WITH("INDEX");
 	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY"))
-		COMPLETE_WITH_LIST4("FULL", "NOTHING", "DEFAULT", "USING");
+		COMPLETE_WITH("FULL", "NOTHING", "DEFAULT", "USING");
 	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA"))
-		COMPLETE_WITH_CONST("IDENTITY");
+		COMPLETE_WITH("IDENTITY");
 
 	/*
 	 * If we have ALTER TABLE <foo> ATTACH PARTITION, provide a list of
@@ -2048,9 +1973,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
+		COMPLETE_WITH("FOR VALUES", "DEFAULT");
 	else if (TailMatches("FOR", "VALUES"))
-		COMPLETE_WITH_LIST3("FROM (", "IN (", "WITH (");
+		COMPLETE_WITH("FROM (", "IN (", "WITH (");
 
 	/*
 	 * If we have ALTER TABLE <foo> DETACH PARTITION, provide a list of
@@ -2064,41 +1989,41 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny))
-		COMPLETE_WITH_LIST4("RENAME TO", "OWNER TO", "SET", "RESET");
+		COMPLETE_WITH("RENAME TO", "OWNER TO", "SET", "RESET");
 	/* ALTER TABLESPACE <foo> SET|RESET */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
-		COMPLETE_WITH_LIST3("seq_page_cost", "random_page_cost",
+		COMPLETE_WITH("seq_page_cost", "random_page_cost",
 							"effective_io_concurrency");
 
 	/* ALTER TEXT SEARCH */
 	else if (Matches("ALTER", "TEXT", "SEARCH"))
-		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches("ALTER", "TEXT", "SEARCH", "TEMPLATE|PARSER", MatchAny))
-		COMPLETE_WITH_LIST2("RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("RENAME TO", "SET SCHEMA");
 	else if (Matches("ALTER", "TEXT", "SEARCH", "DICTIONARY", MatchAny))
-		COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
 	else if (Matches("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
-		COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING",
+		COMPLETE_WITH("ADD MAPPING FOR", "ALTER MAPPING",
 							"DROP MAPPING FOR",
 							"OWNER TO", "RENAME TO", "SET SCHEMA");
 
 	/* complete ALTER TYPE <foo> with actions */
 	else if (Matches("ALTER", "TYPE", MatchAny))
-		COMPLETE_WITH_LIST7("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
+		COMPLETE_WITH("ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE",
 							"DROP ATTRIBUTE",
 							"OWNER TO", "RENAME", "SET SCHEMA");
 	/* complete ALTER TYPE <foo> ADD with actions */
 	else if (Matches("ALTER", "TYPE", MatchAny, "ADD"))
-		COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE");
+		COMPLETE_WITH("ATTRIBUTE", "VALUE");
 	/* ALTER TYPE <foo> RENAME	*/
 	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME"))
-		COMPLETE_WITH_LIST3("ATTRIBUTE", "TO", "VALUE");
+		COMPLETE_WITH("ATTRIBUTE", "TO", "VALUE");
 	/* ALTER TYPE xxx RENAME (ATTRIBUTE|VALUE) yyy */
 	else if (Matches("ALTER", "TYPE", MatchAny, "RENAME", "ATTRIBUTE|VALUE", MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 
 	/*
 	 * If we have ALTER TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list
@@ -2108,13 +2033,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
-		COMPLETE_WITH_CONST("TYPE");
+		COMPLETE_WITH("TYPE");
 	/* complete ALTER GROUP <foo> */
 	else if (Matches("ALTER", "GROUP", MatchAny))
-		COMPLETE_WITH_LIST3("ADD USER", "DROP USER", "RENAME TO");
+		COMPLETE_WITH("ADD USER", "DROP USER", "RENAME TO");
 	/* complete ALTER GROUP <foo> ADD|DROP with USER */
 	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP"))
-		COMPLETE_WITH_CONST("USER");
+		COMPLETE_WITH("USER");
 	/* complete ALTER GROUP <foo> ADD|DROP USER with a user name */
 	else if (Matches("ALTER", "GROUP", MatchAny, "ADD|DROP", "USER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
@@ -2127,24 +2052,24 @@ psql_completion(const char *text, int start, int end)
 
 /* BEGIN */
 	else if (Matches("BEGIN"))
-		COMPLETE_WITH_LIST6("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
+		COMPLETE_WITH("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 /* END, ABORT */
 	else if (Matches("END|ABORT"))
-		COMPLETE_WITH_LIST2("WORK", "TRANSACTION");
+		COMPLETE_WITH("WORK", "TRANSACTION");
 /* COMMIT */
 	else if (Matches("COMMIT"))
-		COMPLETE_WITH_LIST3("WORK", "TRANSACTION", "PREPARED");
+		COMPLETE_WITH("WORK", "TRANSACTION", "PREPARED");
 /* RELEASE SAVEPOINT */
 	else if (Matches("RELEASE"))
-		COMPLETE_WITH_CONST("SAVEPOINT");
+		COMPLETE_WITH("SAVEPOINT");
 /* ROLLBACK */
 	else if (Matches("ROLLBACK"))
-		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
+		COMPLETE_WITH("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CALL */
 	else if (Matches("CALL"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
 	else if (Matches("CALL", MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 /* CLUSTER */
 	else if (Matches("CLUSTER"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
@@ -2152,10 +2077,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON")))
-		COMPLETE_WITH_CONST("USING");
+		COMPLETE_WITH("USING");
 	/* If we have CLUSTER VERBOSE <sth>, then add "USING" */
 	else if (Matches("CLUSTER", "VERBOSE", MatchAny))
-		COMPLETE_WITH_CONST("USING");
+		COMPLETE_WITH("USING");
 	/* If we have CLUSTER <sth> USING, then add the index as well */
 	else if (Matches("CLUSTER", MatchAny, "USING") ||
 			 Matches("CLUSTER", "VERBOSE", MatchAny, "USING"))
@@ -2166,7 +2091,7 @@ psql_completion(const char *text, int start, int end)
 
 /* COMMENT */
 	else if (Matches("COMMENT"))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON"))
 	{
 		static const char *const list_COMMENT[] =
@@ -2185,13 +2110,13 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("COMMENT", "ON", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (Matches("COMMENT", "ON", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+		COMPLETE_WITH("DATA WRAPPER", "TABLE");
 	else if (Matches("COMMENT", "ON", "TEXT", "SEARCH"))
-		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches("COMMENT", "ON", "CONSTRAINT"))
 		COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
@@ -2204,7 +2129,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
 			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAny, MatchAnyExcept("IS")))
-		COMPLETE_WITH_CONST("IS");
+		COMPLETE_WITH("IS");
 
 /* COPY */
 
@@ -2220,11 +2145,11 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* If we have COPY (, complete it with legal commands */
 	else if (Matches("COPY|\\copy", "("))
-		COMPLETE_WITH_LIST7("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
+		COMPLETE_WITH("SELECT", "TABLE", "VALUES", "INSERT", "UPDATE", "DELETE", "WITH");
 	/* If we have COPY [BINARY] <sth>, complete it with "TO" or "FROM" */
 	else if (Matches("COPY|\\copy", MatchAny) ||
 			 Matches("COPY", "BINARY", MatchAny))
-		COMPLETE_WITH_LIST2("FROM", "TO");
+		COMPLETE_WITH("FROM", "TO");
 	/* If we have COPY [BINARY] <sth> FROM|TO, complete with filename */
 	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO") ||
 			 Matches("COPY", "BINARY", MatchAny, "FROM|TO"))
@@ -2236,29 +2161,29 @@ psql_completion(const char *text, int start, int end)
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
 	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
-		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
+		COMPLETE_WITH("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
 							"ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
 	else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
 			 Matches("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny, "CSV"))
-		COMPLETE_WITH_LIST5("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
+		COMPLETE_WITH("HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE",
 							"FORCE NOT NULL");
 
 	/* CREATE ACCESS METHOD */
 	/* Complete "CREATE ACCESS METHOD <name>" */
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny))
-		COMPLETE_WITH_CONST("TYPE");
+		COMPLETE_WITH("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
-		COMPLETE_WITH_CONST("INDEX");
+		COMPLETE_WITH("INDEX");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
-		COMPLETE_WITH_CONST("HANDLER");
+		COMPLETE_WITH("HANDLER");
 
 	/* CREATE DATABASE */
 	else if (Matches("CREATE", "DATABASE", MatchAny))
-		COMPLETE_WITH_LIST9("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
+		COMPLETE_WITH("OWNER", "TEMPLATE", "ENCODING", "TABLESPACE",
 							"IS_TEMPLATE",
 							"ALLOW_CONNECTIONS", "CONNECTION LIMIT",
 							"LC_COLLATE", "LC_CTYPE");
@@ -2272,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
 	/* CREATE EXTENSION <name> */
 	else if (Matches("CREATE", "EXTENSION", MatchAny))
-		COMPLETE_WITH_LIST3("WITH SCHEMA", "CASCADE", "VERSION");
+		COMPLETE_WITH("WITH SCHEMA", "CASCADE", "VERSION");
 	/* CREATE EXTENSION <name> VERSION */
 	else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
@@ -2282,16 +2207,16 @@ psql_completion(const char *text, int start, int end)
 
 	/* CREATE FOREIGN */
 	else if (Matches("CREATE", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+		COMPLETE_WITH("DATA WRAPPER", "TABLE");
 
 	/* CREATE FOREIGN DATA WRAPPER */
 	else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny))
-		COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS");
+		COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS");
 
 	/* CREATE INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (TailMatches("CREATE", "UNIQUE"))
-		COMPLETE_WITH_CONST("INDEX");
+		COMPLETE_WITH("INDEX");
 
 	/*
 	 * If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", and
@@ -2320,7 +2245,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 
 	/*
 	 * Complete INDEX <name> ON <table> with a list of table columns (which
@@ -2328,7 +2253,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny) ||
 			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny))
-		COMPLETE_WITH_LIST2("(", "USING");
+		COMPLETE_WITH("(", "USING");
 	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny, "(") ||
 			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
@@ -2343,74 +2268,74 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("ON", MatchAny, "USING", MatchAny) &&
 			 !TailMatches("POLICY", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny) &&
 			 !TailMatches("FOR", MatchAny, MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 	/* CREATE POLICY */
 	/* Complete "CREATE POLICY <name> ON" */
 	else if (Matches("CREATE", "POLICY", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_LIST5("AS", "FOR", "TO", "USING (", "WITH CHECK (");
+		COMPLETE_WITH("AS", "FOR", "TO", "USING (", "WITH CHECK (");
 	/* CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS"))
-		COMPLETE_WITH_LIST2("PERMISSIVE", "RESTRICTIVE");
+		COMPLETE_WITH("PERMISSIVE", "RESTRICTIVE");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
 	 * FOR|TO|USING|WITH CHECK
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny))
-		COMPLETE_WITH_LIST4("FOR", "TO", "USING", "WITH CHECK");
+		COMPLETE_WITH("FOR", "TO", "USING", "WITH CHECK");
 	/* CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR"))
-		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
+		COMPLETE_WITH("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "INSERT"))
-		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
+		COMPLETE_WITH("TO", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "SELECT|DELETE"))
-		COMPLETE_WITH_LIST2("TO", "USING (");
+		COMPLETE_WITH("TO", "USING (");
 	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "FOR", "ALL|UPDATE"))
-		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
+		COMPLETE_WITH("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|SELECT|INSERT|UPDATE|DELETE
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR"))
-		COMPLETE_WITH_LIST5("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
+		COMPLETE_WITH("ALL", "SELECT", "INSERT", "UPDATE", "DELETE");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * INSERT TO|WITH CHECK"
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "INSERT"))
-		COMPLETE_WITH_LIST2("TO", "WITH CHECK (");
+		COMPLETE_WITH("TO", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * SELECT|DELETE TO|USING"
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "SELECT|DELETE"))
-		COMPLETE_WITH_LIST2("TO", "USING (");
+		COMPLETE_WITH("TO", "USING (");
 
 	/*
 	 * CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE FOR
 	 * ALL|UPDATE TO|USING|WITH CHECK
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "FOR", "ALL|UPDATE"))
-		COMPLETE_WITH_LIST3("TO", "USING (", "WITH CHECK (");
+		COMPLETE_WITH("TO", "USING (", "WITH CHECK (");
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE TO
@@ -2424,34 +2349,34 @@ psql_completion(const char *text, int start, int end)
 	 * USING ("
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 
 /* CREATE PUBLICATION */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny))
-		COMPLETE_WITH_LIST3("FOR TABLE", "FOR ALL TABLES", "WITH (");
+		COMPLETE_WITH("FOR TABLE", "FOR ALL TABLES", "WITH (");
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR"))
-		COMPLETE_WITH_LIST2("TABLE", "ALL TABLES");
+		COMPLETE_WITH("TABLE", "ALL TABLES");
 	/* Complete "CREATE PUBLICATION <name> FOR TABLE <table>" */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE PUBLICATION <name> [...] WITH" */
 	else if (HeadMatches("CREATE", "PUBLICATION") && TailMatches("WITH", "("))
-		COMPLETE_WITH_CONST("publish");
+		COMPLETE_WITH("publish");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
 	else if (Matches("CREATE", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("AS ON");
+		COMPLETE_WITH("AS ON");
 	/* Complete "CREATE RULE <sth> AS" with "ON" */
 	else if (Matches("CREATE", "RULE", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	/* Complete "CREATE RULE <sth> AS ON" with SELECT|UPDATE|INSERT|DELETE */
 	else if (Matches("CREATE", "RULE", MatchAny, "AS", "ON"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE");
+		COMPLETE_WITH("SELECT", "UPDATE", "INSERT", "DELETE");
 	/* Complete "AS ON SELECT|UPDATE|INSERT|DELETE" with a "TO" */
 	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE"))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2459,25 +2384,25 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
-		COMPLETE_WITH_LIST8("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
+		COMPLETE_WITH("INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", "CACHE",
 							"CYCLE", "OWNED BY", "START WITH");
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "NO") ||
 			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "NO"))
-		COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE");
+		COMPLETE_WITH("MINVALUE", "MAXVALUE", "CYCLE");
 
 /* CREATE SERVER <name> */
 	else if (Matches("CREATE", "SERVER", MatchAny))
-		COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
+		COMPLETE_WITH("TYPE", "VERSION", "FOREIGN DATA WRAPPER");
 
 /* CREATE STATISTICS <name> */
 	else if (Matches("CREATE", "STATISTICS", MatchAny))
-		COMPLETE_WITH_LIST2("(", "ON");
+		COMPLETE_WITH("(", "ON");
 	else if (Matches("CREATE", "STATISTICS", MatchAny, "("))
-		COMPLETE_WITH_LIST2("ndistinct", "dependencies");
+		COMPLETE_WITH("ndistinct", "dependencies");
 	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
 			 previous_words[0][0] == '(' &&
 			 previous_words[0][strlen(previous_words[0]) - 1] == ')')
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
 			 TailMatches("FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2485,64 +2410,64 @@ psql_completion(const char *text, int start, int end)
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (TailMatches("CREATE", "TEMP|TEMPORARY"))
-		COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW");
+		COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW");
 	/* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */
 	else if (TailMatches("CREATE", "UNLOGGED"))
-		COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW");
+		COMPLETE_WITH("TABLE", "MATERIALIZED VIEW");
 	/* Complete PARTITION BY with RANGE ( or LIST ( or ... */
 	else if (TailMatches("PARTITION", "BY"))
-		COMPLETE_WITH_LIST3("RANGE (", "LIST (", "HASH (");
+		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
 	/* If we have xxx PARTITION OF, provide a list of partitioned tables */
 	else if (TailMatches("PARTITION", "OF"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
+		COMPLETE_WITH("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches("CREATE", "TABLESPACE", MatchAny))
-		COMPLETE_WITH_LIST2("OWNER", "LOCATION");
+		COMPLETE_WITH("OWNER", "LOCATION");
 	/* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */
 	else if (Matches("CREATE", "TABLESPACE", MatchAny, "OWNER", MatchAny))
-		COMPLETE_WITH_CONST("LOCATION");
+		COMPLETE_WITH("LOCATION");
 
 /* CREATE TEXT SEARCH */
 	else if (Matches("CREATE", "TEXT", "SEARCH"))
-		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 	else if (Matches("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 /* CREATE SUBSCRIPTION */
 	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny))
-		COMPLETE_WITH_CONST("CONNECTION");
+		COMPLETE_WITH("CONNECTION");
 	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION", MatchAny))
-		COMPLETE_WITH_CONST("PUBLICATION");
+		COMPLETE_WITH("PUBLICATION");
 	else if (Matches("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",
 					  MatchAny, "PUBLICATION"))
 	{
 		/* complete with nothing here as this refers to remote publications */
 	}
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("PUBLICATION", MatchAny))
-		COMPLETE_WITH_CONST("WITH (");
+		COMPLETE_WITH("WITH (");
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
-		COMPLETE_WITH_LIST6("copy_data", "connect", "create_slot", "enabled",
+		COMPLETE_WITH("copy_data", "connect", "create_slot", "enabled",
 							"slot_name", "synchronous_commit");
 
 /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* complete CREATE TRIGGER <name> with BEFORE,AFTER,INSTEAD OF */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny))
-		COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF");
+		COMPLETE_WITH("BEFORE", "AFTER", "INSTEAD OF");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER"))
-		COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE");
+		COMPLETE_WITH("INSERT", "DELETE", "UPDATE", "TRUNCATE");
 	/* complete CREATE TRIGGER <name> INSTEAD OF with an event */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF"))
-		COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE");
+		COMPLETE_WITH("INSERT", "DELETE", "UPDATE");
 	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) ||
 			 TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny))
-		COMPLETE_WITH_LIST2("ON", "OR");
+		COMPLETE_WITH("ON", "OR");
 
 	/*
 	 * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
@@ -2554,40 +2479,40 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("ON", MatchAny))
-		COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
+		COMPLETE_WITH("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY",
 							"REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") &&
 			 (TailMatches("DEFERRABLE") || TailMatches("INITIALLY", "IMMEDIATE|DEFERRED")))
-		COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
+		COMPLETE_WITH("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("REFERENCING"))
-		COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE");
+		COMPLETE_WITH("OLD TABLE", "NEW TABLE");
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("OLD|NEW", "TABLE"))
-		COMPLETE_WITH_CONST("AS");
+		COMPLETE_WITH("AS");
 	else if (HeadMatches("CREATE", "TRIGGER") &&
 			 (TailMatches("REFERENCING", "OLD", "TABLE", "AS", MatchAny) ||
 			  TailMatches("REFERENCING", "OLD", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST4("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
+		COMPLETE_WITH("NEW TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") &&
 			 (TailMatches("REFERENCING", "NEW", "TABLE", "AS", MatchAny) ||
 			  TailMatches("REFERENCING", "NEW", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST4("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
+		COMPLETE_WITH("OLD TABLE", "FOR", "WHEN (", "EXECUTE PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") &&
 			 (TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
 			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", "AS", MatchAny) ||
 			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", "AS", MatchAny, "OLD|NEW", "TABLE", MatchAny) ||
 			  TailMatches("REFERENCING", "OLD|NEW", "TABLE", MatchAny, "OLD|NEW", "TABLE", MatchAny)))
-		COMPLETE_WITH_LIST3("FOR", "WHEN (", "EXECUTE PROCEDURE");
+		COMPLETE_WITH("FOR", "WHEN (", "EXECUTE PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR"))
-		COMPLETE_WITH_LIST3("EACH", "ROW", "STATEMENT");
+		COMPLETE_WITH("EACH", "ROW", "STATEMENT");
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("FOR", "EACH"))
-		COMPLETE_WITH_LIST2("ROW", "STATEMENT");
+		COMPLETE_WITH("ROW", "STATEMENT");
 	else if (HeadMatches("CREATE", "TRIGGER") &&
 			 (TailMatches("FOR", "EACH", "ROW|STATEMENT") ||
 			  TailMatches("FOR", "ROW|STATEMENT")))
-		COMPLETE_WITH_LIST2("WHEN (", "EXECUTE PROCEDURE");
+		COMPLETE_WITH("WHEN (", "EXECUTE PROCEDURE");
 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE"))
-		COMPLETE_WITH_CONST("PROCEDURE");
+		COMPLETE_WITH("PROCEDURE");
 	else if (HeadMatches("CREATE", "TRIGGER") && TailMatches("EXECUTE", "PROCEDURE"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
@@ -2623,35 +2548,35 @@ psql_completion(const char *text, int start, int end)
 
 	/* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
 	else if (Matches("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
-		COMPLETE_WITH_LIST2("GROUP", "ROLE");
+		COMPLETE_WITH("GROUP", "ROLE");
 
 /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete CREATE VIEW <name> with AS */
 	else if (TailMatches("CREATE", "VIEW", MatchAny))
-		COMPLETE_WITH_CONST("AS");
+		COMPLETE_WITH("AS");
 	/* Complete "CREATE VIEW <sth> AS with "SELECT" */
 	else if (TailMatches("CREATE", "VIEW", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("SELECT");
+		COMPLETE_WITH("SELECT");
 
 /* CREATE MATERIALIZED VIEW */
 	else if (Matches("CREATE", "MATERIALIZED"))
-		COMPLETE_WITH_CONST("VIEW");
+		COMPLETE_WITH("VIEW");
 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
 	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny))
-		COMPLETE_WITH_CONST("AS");
+		COMPLETE_WITH("AS");
 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
 	else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS"))
-		COMPLETE_WITH_CONST("SELECT");
+		COMPLETE_WITH("SELECT");
 
 /* CREATE EVENT TRIGGER */
 	else if (Matches("CREATE", "EVENT"))
-		COMPLETE_WITH_CONST("TRIGGER");
+		COMPLETE_WITH("TRIGGER");
 	/* Complete CREATE EVENT TRIGGER <name> with ON */
 	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
 	else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop");
+		COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop");
 
 /* DEALLOCATE */
 	else if (Matches("DEALLOCATE"))
@@ -2659,30 +2584,30 @@ psql_completion(const char *text, int start, int end)
 
 /* DECLARE */
 	else if (Matches("DECLARE", MatchAny))
-		COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
+		COMPLETE_WITH("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL",
 							"CURSOR");
 	else if (HeadMatches("DECLARE") && TailMatches("CURSOR"))
-		COMPLETE_WITH_LIST3("WITH HOLD", "WITHOUT HOLD", "FOR");
+		COMPLETE_WITH("WITH HOLD", "WITHOUT HOLD", "FOR");
 
 /* DELETE --- can be inside EXPLAIN, RULE, etc */
 	/* ... despite which, only complete DELETE with FROM at start of line */
 	else if (Matches("DELETE"))
-		COMPLETE_WITH_CONST("FROM");
+		COMPLETE_WITH("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches("DELETE", "FROM"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches("DELETE", "FROM", MatchAny))
-		COMPLETE_WITH_LIST2("USING", "WHERE");
+		COMPLETE_WITH("USING", "WHERE");
 	/* XXX: implement tab completion for DELETE ... USING */
 
 /* DISCARD */
 	else if (Matches("DISCARD"))
-		COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP");
+		COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP");
 
 /* DO */
 	else if (Matches("DO"))
-		COMPLETE_WITH_CONST("LANGUAGE");
+		COMPLETE_WITH("LANGUAGE");
 
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
@@ -2696,15 +2621,15 @@ psql_completion(const char *text, int start, int end)
 			 Matches("DROP", "FOREIGN", "DATA", "WRAPPER", MatchAny) ||
 			 Matches("DROP", "FOREIGN", "TABLE", MatchAny) ||
 			 Matches("DROP", "TEXT", "SEARCH", "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+		COMPLETE_WITH("CASCADE", "RESTRICT");
 
 	/* help completing some of the variants */
 	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 	else if (Matches("DROP", "AGGREGATE|FUNCTION|PROCEDURE|ROUTINE", MatchAny, "("))
 		COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	else if (Matches("DROP", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+		COMPLETE_WITH("DATA WRAPPER", "TABLE");
 
 	/* DROP INDEX */
 	else if (Matches("DROP", "INDEX"))
@@ -2713,46 +2638,46 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("DROP", "INDEX", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
 	else if (Matches("DROP", "INDEX", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+		COMPLETE_WITH("CASCADE", "RESTRICT");
 	else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+		COMPLETE_WITH("CASCADE", "RESTRICT");
 
 	/* DROP MATERIALIZED VIEW */
 	else if (Matches("DROP", "MATERIALIZED"))
-		COMPLETE_WITH_CONST("VIEW");
+		COMPLETE_WITH("VIEW");
 	else if (Matches("DROP", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
 	/* DROP OWNED BY */
 	else if (Matches("DROP", "OWNED"))
-		COMPLETE_WITH_CONST("BY");
+		COMPLETE_WITH("BY");
 	else if (Matches("DROP", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 	/* DROP TEXT SEARCH */
 	else if (Matches("DROP", "TEXT", "SEARCH"))
-		COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 
 	/* DROP TRIGGER */
 	else if (Matches("DROP", "TRIGGER", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("DROP", "TRIGGER", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
 	}
 	else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+		COMPLETE_WITH("CASCADE", "RESTRICT");
 
 	/* DROP ACCESS METHOD */
 	else if (Matches("DROP", "ACCESS"))
-		COMPLETE_WITH_CONST("METHOD");
+		COMPLETE_WITH("METHOD");
 	else if (Matches("DROP", "ACCESS", "METHOD"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 
 	/* DROP EVENT TRIGGER */
 	else if (Matches("DROP", "EVENT"))
-		COMPLETE_WITH_CONST("TRIGGER");
+		COMPLETE_WITH("TRIGGER");
 	else if (Matches("DROP", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
@@ -2761,7 +2686,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	/* DROP POLICY <name> ON */
 	else if (Matches("DROP", "POLICY", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	/* DROP POLICY <name> ON <table> */
 	else if (Matches("DROP", "POLICY", MatchAny, "ON"))
 	{
@@ -2771,14 +2696,14 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP RULE */
 	else if (Matches("DROP", "RULE", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("DROP", "RULE", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
 		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
 	}
 	else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
-		COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+		COMPLETE_WITH("CASCADE", "RESTRICT");
 
 /* EXECUTE */
 	else if (Matches("EXECUTE"))
@@ -2790,22 +2715,22 @@ psql_completion(const char *text, int start, int end)
 	 * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
 	 */
 	else if (Matches("EXPLAIN"))
-		COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+		COMPLETE_WITH("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"ANALYZE", "VERBOSE");
 	else if (Matches("EXPLAIN", "ANALYZE"))
-		COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+		COMPLETE_WITH("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
 							"VERBOSE");
 	else if (Matches("EXPLAIN", "VERBOSE") ||
 			 Matches("EXPLAIN", "ANALYZE", "VERBOSE"))
-		COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+		COMPLETE_WITH("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
 
 /* FETCH && MOVE */
 	/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
 	else if (Matches("FETCH|MOVE"))
-		COMPLETE_WITH_LIST4("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
+		COMPLETE_WITH("ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE");
 	/* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */
 	else if (Matches("FETCH|MOVE", MatchAny))
-		COMPLETE_WITH_LIST3("ALL", "NEXT", "PRIOR");
+		COMPLETE_WITH("ALL", "NEXT", "PRIOR");
 
 	/*
 	 * Complete FETCH <sth1> <sth2> with "FROM" or "IN". These are equivalent,
@@ -2813,7 +2738,7 @@ psql_completion(const char *text, int start, int end)
 	 * variant or the other.
 	 */
 	else if (Matches("FETCH|MOVE", MatchAny, MatchAny))
-		COMPLETE_WITH_LIST2("FROM", "IN");
+		COMPLETE_WITH("FROM", "IN");
 
 /* FOREIGN DATA WRAPPER */
 	/* applies in ALTER/DROP FDW and in CREATE SERVER */
@@ -2823,7 +2748,7 @@ psql_completion(const char *text, int start, int end)
 	/* applies in CREATE SERVER */
 	else if (TailMatches("FOREIGN", "DATA", "WRAPPER", MatchAny) &&
 			 HeadMatches("CREATE", "SERVER"))
-		COMPLETE_WITH_CONST("OPTIONS");
+		COMPLETE_WITH("OPTIONS");
 
 /* FOREIGN TABLE */
 	else if (TailMatches("FOREIGN", "TABLE") &&
@@ -2846,9 +2771,9 @@ psql_completion(const char *text, int start, int end)
 		 * privileges (can't grant roles)
 		 */
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
-			COMPLETE_WITH_LIST10("SELECT", "INSERT", "UPDATE",
-								 "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
-								 "EXECUTE", "USAGE", "ALL");
+			COMPLETE_WITH("SELECT", "INSERT", "UPDATE",
+						  "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
+						  "EXECUTE", "USAGE", "ALL");
 		else
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles
 								" UNION SELECT 'SELECT'"
@@ -2873,11 +2798,11 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("GRANT|REVOKE", MatchAny))
 	{
 		if (TailMatches("SELECT|INSERT|UPDATE|DELETE|TRUNCATE|REFERENCES|TRIGGER|CREATE|CONNECT|TEMPORARY|TEMP|EXECUTE|USAGE|ALL"))
-			COMPLETE_WITH_CONST("ON");
+			COMPLETE_WITH("ON");
 		else if (TailMatches("GRANT", MatchAny))
-			COMPLETE_WITH_CONST("TO");
+			COMPLETE_WITH("TO");
 		else
-			COMPLETE_WITH_CONST("FROM");
+			COMPLETE_WITH("FROM");
 	}
 
 	/*
@@ -2898,7 +2823,7 @@ psql_completion(const char *text, int start, int end)
 		 * objects supported.
 		 */
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
-			COMPLETE_WITH_LIST7("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
+			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
 		else
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
 									   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
@@ -2922,13 +2847,13 @@ psql_completion(const char *text, int start, int end)
 									   " UNION SELECT 'TYPE'");
 	}
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
-		COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
+		COMPLETE_WITH("FUNCTIONS IN SCHEMA",
 							"PROCEDURES IN SCHEMA",
 							"ROUTINES IN SCHEMA",
 							"SEQUENCES IN SCHEMA",
 							"TABLES IN SCHEMA");
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
-		COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
+		COMPLETE_WITH("DATA WRAPPER", "SERVER");
 
 	/*
 	 * Complete "GRANT/REVOKE * ON DATABASE/DOMAIN/..." with a list of
@@ -2961,9 +2886,9 @@ psql_completion(const char *text, int start, int end)
 		else if (TailMatches("TYPE"))
 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
+			COMPLETE_WITH("TO");
 		else
-			COMPLETE_WITH_CONST("FROM");
+			COMPLETE_WITH("FROM");
 	}
 
 	/*
@@ -2978,51 +2903,51 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches("GRANT") && TailMatches("ON", MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 	else if (HeadMatches("REVOKE") && TailMatches("ON", MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("FROM");
+		COMPLETE_WITH("FROM");
 
 	/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny))
 	{
 		if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
+			COMPLETE_WITH("TO");
 		else
-			COMPLETE_WITH_CONST("FROM");
+			COMPLETE_WITH("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN DATA WRAPPER *" with TO/FROM */
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "DATA", "WRAPPER", MatchAny))
 	{
 		if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
+			COMPLETE_WITH("TO");
 		else
-			COMPLETE_WITH_CONST("FROM");
+			COMPLETE_WITH("FROM");
 	}
 
 	/* Complete "GRANT/REVOKE * ON FOREIGN SERVER *" with TO/FROM */
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN", "SERVER", MatchAny))
 	{
 		if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
-			COMPLETE_WITH_CONST("TO");
+			COMPLETE_WITH("TO");
 		else
-			COMPLETE_WITH_CONST("FROM");
+			COMPLETE_WITH("FROM");
 	}
 
 /* GROUP BY */
 	else if (TailMatches("FROM", MatchAny, "GROUP"))
-		COMPLETE_WITH_CONST("BY");
+		COMPLETE_WITH("BY");
 
 /* IMPORT FOREIGN SCHEMA */
 	else if (Matches("IMPORT"))
-		COMPLETE_WITH_CONST("FOREIGN SCHEMA");
+		COMPLETE_WITH("FOREIGN SCHEMA");
 	else if (Matches("IMPORT", "FOREIGN"))
-		COMPLETE_WITH_CONST("SCHEMA");
+		COMPLETE_WITH("SCHEMA");
 
 /* INSERT --- can be inside EXPLAIN, RULE, etc */
 	/* Complete INSERT with "INTO" */
 	else if (TailMatches("INSERT"))
-		COMPLETE_WITH_CONST("INTO");
+		COMPLETE_WITH("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches("INSERT", "INTO"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
@@ -3035,7 +2960,7 @@ psql_completion(const char *text, int start, int end)
 	 * "TABLE" or "DEFAULT VALUES" or "OVERRIDING"
 	 */
 	else if (TailMatches("INSERT", "INTO", MatchAny))
-		COMPLETE_WITH_LIST6("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING");
+		COMPLETE_WITH("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/*
 	 * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
@@ -3043,19 +2968,19 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches("INSERT", "INTO", MatchAny, MatchAny) &&
 			 ends_with(prev_wd, ')'))
-		COMPLETE_WITH_LIST4("SELECT", "TABLE", "VALUES", "OVERRIDING");
+		COMPLETE_WITH("SELECT", "TABLE", "VALUES", "OVERRIDING");
 
 	/* Complete OVERRIDING */
 	else if (TailMatches("OVERRIDING"))
-		COMPLETE_WITH_LIST2("SYSTEM VALUE", "USER VALUE");
+		COMPLETE_WITH("SYSTEM VALUE", "USER VALUE");
 
 	/* Complete after OVERRIDING clause */
 	else if (TailMatches("OVERRIDING", MatchAny, "VALUE"))
-		COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES");
+		COMPLETE_WITH("SELECT", "TABLE", "VALUES");
 
 	/* Insert an open parenthesis after "VALUES" */
 	else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 /* LOCK */
 	/* Complete LOCK [TABLE] with a list of tables */
@@ -3070,12 +2995,12 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] <table> with "IN" */
 	else if (Matches("LOCK", MatchAnyExcept("TABLE")) ||
 			 Matches("LOCK", "TABLE", MatchAny))
-		COMPLETE_WITH_CONST("IN");
+		COMPLETE_WITH("IN");
 
 	/* Complete LOCK [TABLE] <table> IN with a lock mode */
 	else if (Matches("LOCK", MatchAny, "IN") ||
 			 Matches("LOCK", "TABLE", MatchAny, "IN"))
-		COMPLETE_WITH_LIST8("ACCESS SHARE MODE",
+		COMPLETE_WITH("ACCESS SHARE MODE",
 							"ROW SHARE MODE", "ROW EXCLUSIVE MODE",
 							"SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE",
 							"SHARE ROW EXCLUSIVE MODE",
@@ -3084,12 +3009,12 @@ psql_completion(const char *text, int start, int end)
 	/* Complete LOCK [TABLE] <table> IN ACCESS|ROW with rest of lock mode */
 	else if (Matches("LOCK", MatchAny, "IN", "ACCESS|ROW") ||
 			 Matches("LOCK", "TABLE", MatchAny, "IN", "ACCESS|ROW"))
-		COMPLETE_WITH_LIST2("EXCLUSIVE MODE", "SHARE MODE");
+		COMPLETE_WITH("EXCLUSIVE MODE", "SHARE MODE");
 
 	/* Complete LOCK [TABLE] <table> IN SHARE with rest of lock mode */
 	else if (Matches("LOCK", MatchAny, "IN", "SHARE") ||
 			 Matches("LOCK", "TABLE", MatchAny, "IN", "SHARE"))
-		COMPLETE_WITH_LIST3("MODE", "ROW EXCLUSIVE MODE",
+		COMPLETE_WITH("MODE", "ROW EXCLUSIVE MODE",
 							"UPDATE EXCLUSIVE MODE");
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
@@ -3098,7 +3023,7 @@ psql_completion(const char *text, int start, int end)
 
 /* OPTIONS */
 	else if (TailMatches("OPTIONS"))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 /* OWNER TO  - complete with available roles */
 	else if (TailMatches("OWNER", "TO"))
@@ -3106,13 +3031,13 @@ psql_completion(const char *text, int start, int end)
 
 /* ORDER BY */
 	else if (TailMatches("FROM", MatchAny, "ORDER"))
-		COMPLETE_WITH_CONST("BY");
+		COMPLETE_WITH("BY");
 	else if (TailMatches("FROM", MatchAny, "ORDER", "BY"))
 		COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
 	else if (Matches("PREPARE", MatchAny, "AS"))
-		COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+		COMPLETE_WITH("SELECT", "UPDATE", "INSERT", "DELETE FROM");
 
 /*
  * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3121,42 +3046,42 @@ psql_completion(const char *text, int start, int end)
 
 /* REASSIGN OWNED BY xxx TO yyy */
 	else if (Matches("REASSIGN"))
-		COMPLETE_WITH_CONST("OWNED BY");
+		COMPLETE_WITH("OWNED BY");
 	else if (Matches("REASSIGN", "OWNED"))
-		COMPLETE_WITH_CONST("BY");
+		COMPLETE_WITH("BY");
 	else if (Matches("REASSIGN", "OWNED", "BY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 	else if (Matches("REASSIGN", "OWNED", "BY", MatchAny, "TO"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REFRESH MATERIALIZED VIEW */
 	else if (Matches("REFRESH"))
-		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
+		COMPLETE_WITH("MATERIALIZED VIEW");
 	else if (Matches("REFRESH", "MATERIALIZED"))
-		COMPLETE_WITH_CONST("VIEW");
+		COMPLETE_WITH("VIEW");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
 								   " UNION SELECT 'CONCURRENTLY'");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
-		COMPLETE_WITH_CONST("WITH");
+		COMPLETE_WITH("WITH");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
-		COMPLETE_WITH_CONST("WITH");
+		COMPLETE_WITH("WITH");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH"))
-		COMPLETE_WITH_LIST2("NO DATA", "DATA");
+		COMPLETE_WITH("NO DATA", "DATA");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH"))
-		COMPLETE_WITH_LIST2("NO DATA", "DATA");
+		COMPLETE_WITH("NO DATA", "DATA");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny, "WITH", "NO"))
-		COMPLETE_WITH_CONST("DATA");
+		COMPLETE_WITH("DATA");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny, "WITH", "NO"))
-		COMPLETE_WITH_CONST("DATA");
+		COMPLETE_WITH("DATA");
 
 /* REINDEX */
 	else if (Matches("REINDEX"))
-		COMPLETE_WITH_LIST5("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
+		COMPLETE_WITH("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches("REINDEX", "TABLE"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 	else if (Matches("REINDEX", "INDEX"))
@@ -3168,11 +3093,11 @@ psql_completion(const char *text, int start, int end)
 
 /* SECURITY LABEL */
 	else if (Matches("SECURITY"))
-		COMPLETE_WITH_CONST("LABEL");
+		COMPLETE_WITH("LABEL");
 	else if (Matches("SECURITY", "LABEL"))
-		COMPLETE_WITH_LIST2("ON", "FOR");
+		COMPLETE_WITH("ON", "FOR");
 	else if (Matches("SECURITY", "LABEL", "FOR", MatchAny))
-		COMPLETE_WITH_CONST("ON");
+		COMPLETE_WITH("ON");
 	else if (Matches("SECURITY", "LABEL", "ON") ||
 			 Matches("SECURITY", "LABEL", "FOR", MatchAny, "ON"))
 	{
@@ -3185,7 +3110,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
 	}
 	else if (Matches("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
-		COMPLETE_WITH_CONST("IS");
+		COMPLETE_WITH("IS");
 
 /* SELECT */
 	/* naah . . . */
@@ -3198,57 +3123,57 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
 	/* Complete "SET TRANSACTION" */
 	else if (Matches("SET", "TRANSACTION"))
-		COMPLETE_WITH_LIST5("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
+		COMPLETE_WITH("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 	else if (Matches("BEGIN|START", "TRANSACTION") ||
 			 Matches("BEGIN", "WORK") ||
 			 Matches("BEGIN") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION"))
-		COMPLETE_WITH_LIST4("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
+		COMPLETE_WITH("ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "NOT") ||
 			 Matches("BEGIN", "NOT") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "NOT"))
-		COMPLETE_WITH_CONST("DEFERRABLE");
+		COMPLETE_WITH("DEFERRABLE");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION") ||
 			 Matches("BEGIN", "ISOLATION") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION"))
-		COMPLETE_WITH_CONST("LEVEL");
+		COMPLETE_WITH("LEVEL");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL") ||
 			 Matches("BEGIN", "ISOLATION", "LEVEL") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL"))
-		COMPLETE_WITH_LIST3("READ", "REPEATABLE READ", "SERIALIZABLE");
+		COMPLETE_WITH("READ", "REPEATABLE READ", "SERIALIZABLE");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "READ") ||
 			 Matches("BEGIN", "ISOLATION", "LEVEL", "READ") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "READ"))
-		COMPLETE_WITH_LIST2("UNCOMMITTED", "COMMITTED");
+		COMPLETE_WITH("UNCOMMITTED", "COMMITTED");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "ISOLATION", "LEVEL", "REPEATABLE") ||
 			 Matches("BEGIN", "ISOLATION", "LEVEL", "REPEATABLE") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "ISOLATION", "LEVEL", "REPEATABLE"))
-		COMPLETE_WITH_CONST("READ");
+		COMPLETE_WITH("READ");
 	else if (Matches("SET|BEGIN|START", "TRANSACTION|WORK", "READ") ||
 			 Matches("BEGIN", "READ") ||
 			 Matches("SET", "SESSION", "CHARACTERISTICS", "AS", "TRANSACTION", "READ"))
-		COMPLETE_WITH_LIST2("ONLY", "WRITE");
+		COMPLETE_WITH("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches("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 (Matches("SET", "CONSTRAINTS", MatchAny))
-		COMPLETE_WITH_LIST2("DEFERRED", "IMMEDIATE");
+		COMPLETE_WITH("DEFERRED", "IMMEDIATE");
 	/* Complete SET ROLE */
 	else if (Matches("SET", "ROLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	/* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
 	else if (Matches("SET", "SESSION"))
-		COMPLETE_WITH_LIST2("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
+		COMPLETE_WITH("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches("SET", "SESSION", "AUTHORIZATION"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches("RESET", "SESSION"))
-		COMPLETE_WITH_CONST("AUTHORIZATION");
+		COMPLETE_WITH("AUTHORIZATION");
 	/* Complete SET <var> with "TO" */
 	else if (Matches("SET", MatchAny))
-		COMPLETE_WITH_CONST("TO");
+		COMPLETE_WITH("TO");
 
 	/*
 	 * Complete ALTER DATABASE|FUNCTION||PROCEDURE|ROLE|ROUTINE|USER ... SET
@@ -3256,7 +3181,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (HeadMatches("ALTER", "DATABASE|FUNCTION|PROCEDURE|ROLE|ROUTINE|USER") &&
 			 TailMatches("SET", MatchAny))
-		COMPLETE_WITH_LIST2("FROM CURRENT", "TO");
+		COMPLETE_WITH("FROM CURRENT", "TO");
 	/* Suggest possible variable values */
 	else if (TailMatches("SET", MatchAny, "TO|="))
 	{
@@ -3289,10 +3214,10 @@ psql_completion(const char *text, int start, int end)
 				COMPLETE_WITH_QUERY(querybuf);
 			}
 			else if (guctype && strcmp(guctype, "bool") == 0)
-				COMPLETE_WITH_LIST9("on", "off", "true", "false", "yes", "no",
+				COMPLETE_WITH("on", "off", "true", "false", "yes", "no",
 									"1", "0", "DEFAULT");
 			else
-				COMPLETE_WITH_CONST("DEFAULT");
+				COMPLETE_WITH("DEFAULT");
 
 			if (guctype)
 				free(guctype);
@@ -3301,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 
 /* START TRANSACTION */
 	else if (Matches("START"))
-		COMPLETE_WITH_CONST("TRANSACTION");
+		COMPLETE_WITH("TRANSACTION");
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches("TABLE"))
@@ -3311,7 +3236,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("TABLESAMPLE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablesample_methods);
 	else if (TailMatches("TABLESAMPLE", MatchAny))
-		COMPLETE_WITH_CONST("(");
+		COMPLETE_WITH("(");
 
 /* TRUNCATE */
 	else if (Matches("TRUNCATE"))
@@ -3327,17 +3252,17 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches("UPDATE", MatchAny))
-		COMPLETE_WITH_CONST("SET");
+		COMPLETE_WITH("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
 	else if (TailMatches("UPDATE", MatchAny, "SET"))
 		COMPLETE_WITH_ATTR(prev2_wd, "");
 	/* UPDATE <table> SET <attr> = */
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAny))
-		COMPLETE_WITH_CONST("=");
+		COMPLETE_WITH("=");
 
 /* USER MAPPING */
 	else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
-		COMPLETE_WITH_CONST("FOR");
+		COMPLETE_WITH("FOR");
 	else if (Matches("CREATE", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles
 							" UNION SELECT 'CURRENT_USER'"
@@ -3346,9 +3271,9 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
-		COMPLETE_WITH_CONST("SERVER");
+		COMPLETE_WITH("SERVER");
 	else if (Matches("CREATE|ALTER", "USER", "MAPPING", "FOR", MatchAny, "SERVER", MatchAny))
-		COMPLETE_WITH_CONST("OPTIONS");
+		COMPLETE_WITH("OPTIONS");
 
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
@@ -3386,7 +3311,7 @@ psql_completion(const char *text, int start, int end)
 	 * other contexts.
 	 */
 	else if (Matches("WITH"))
-		COMPLETE_WITH_CONST("RECURSIVE");
+		COMPLETE_WITH("RECURSIVE");
 
 /* ANALYZE */
 	/* Complete with list of tables */
@@ -3410,7 +3335,7 @@ psql_completion(const char *text, int start, int end)
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
 	else if (TailMatchesCS("\\?"))
-		COMPLETE_WITH_LIST_CS3("commands", "options", "variables");
+		COMPLETE_WITH_CS("commands", "options", "variables");
 	else if (TailMatchesCS("\\connect|\\c"))
 	{
 		if (!recognized_connection_string(text))
@@ -3503,30 +3428,30 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny))
 	{
 		if (TailMatches("CREATE|DROP", "ACCESS"))
-			COMPLETE_WITH_CONST("METHOD");
+			COMPLETE_WITH("METHOD");
 		else if (TailMatches("ALTER", "DEFAULT"))
-			COMPLETE_WITH_CONST("PRIVILEGES");
+			COMPLETE_WITH("PRIVILEGES");
 		else if (TailMatches("CREATE|ALTER|DROP", "EVENT"))
-			COMPLETE_WITH_CONST("TRIGGER");
+			COMPLETE_WITH("TRIGGER");
 		else if (TailMatches("CREATE|ALTER|DROP", "FOREIGN"))
-			COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE");
+			COMPLETE_WITH("DATA WRAPPER", "TABLE");
 		else if (TailMatches("ALTER", "LARGE"))
-			COMPLETE_WITH_CONST("OBJECT");
+			COMPLETE_WITH("OBJECT");
 		else if (TailMatches("CREATE|ALTER|DROP", "MATERIALIZED"))
-			COMPLETE_WITH_CONST("VIEW");
+			COMPLETE_WITH("VIEW");
 		else if (TailMatches("CREATE|ALTER|DROP", "TEXT"))
-			COMPLETE_WITH_CONST("SEARCH");
+			COMPLETE_WITH("SEARCH");
 		else if (TailMatches("CREATE|ALTER|DROP", "USER"))
-			COMPLETE_WITH_CONST("MAPPING FOR");
+			COMPLETE_WITH("MAPPING FOR");
 	}
 	else if (TailMatchesCS("\\h|\\help", MatchAny, MatchAny, MatchAny))
 	{
 		if (TailMatches("CREATE|ALTER|DROP", "FOREIGN", "DATA"))
-			COMPLETE_WITH_CONST("WRAPPER");
+			COMPLETE_WITH("WRAPPER");
 		else if (TailMatches("CREATE|ALTER|DROP", "TEXT", "SEARCH"))
-			COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
+			COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
 		else if (TailMatches("CREATE|ALTER|DROP", "USER", "MAPPING"))
-			COMPLETE_WITH_CONST("FOR");
+			COMPLETE_WITH("FOR");
 	}
 	else if (TailMatchesCS("\\l*") && !TailMatchesCS("\\lo*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_databases);
@@ -3554,13 +3479,13 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
 		else if (TailMatchesCS("linestyle"))
-			COMPLETE_WITH_LIST_CS3("ascii", "old-ascii", "unicode");
+			COMPLETE_WITH_CS("ascii", "old-ascii", "unicode");
 		else if (TailMatchesCS("pager"))
-			COMPLETE_WITH_LIST_CS3("on", "off", "always");
+			COMPLETE_WITH_CS("on", "off", "always");
 		else if (TailMatchesCS("unicode_border_linestyle|"
 								"unicode_column_linestyle|"
 								"unicode_header_linestyle"))
-			COMPLETE_WITH_LIST_CS2("single", "double");
+			COMPLETE_WITH_CS("single", "double");
 	}
 	else if (TailMatchesCS("\\unset"))
 		matches = complete_from_variables(text, "", "", true);
@@ -3570,23 +3495,23 @@ psql_completion(const char *text, int start, int end)
 	{
 		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
 						   "SINGLELINE|SINGLESTEP"))
-			COMPLETE_WITH_LIST_CS2("on", "off");
+			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
-			COMPLETE_WITH_LIST_CS4("lower", "upper",
-								   "preserve-lower", "preserve-upper");
+			COMPLETE_WITH_CS("lower", "upper",
+							 "preserve-lower", "preserve-upper");
 		else if (TailMatchesCS("ECHO"))
-			COMPLETE_WITH_LIST_CS4("errors", "queries", "all", "none");
+			COMPLETE_WITH_CS("errors", "queries", "all", "none");
 		else if (TailMatchesCS("ECHO_HIDDEN"))
-			COMPLETE_WITH_LIST_CS3("noexec", "off", "on");
+			COMPLETE_WITH_CS("noexec", "off", "on");
 		else if (TailMatchesCS("HISTCONTROL"))
-			COMPLETE_WITH_LIST_CS4("ignorespace", "ignoredups",
-								   "ignoreboth", "none");
+			COMPLETE_WITH_CS("ignorespace", "ignoredups",
+							 "ignoreboth", "none");
 		else if (TailMatchesCS("ON_ERROR_ROLLBACK"))
-			COMPLETE_WITH_LIST_CS3("on", "off", "interactive");
+			COMPLETE_WITH_CS("on", "off", "interactive");
 		else if (TailMatchesCS("SHOW_CONTEXT"))
-			COMPLETE_WITH_LIST_CS3("never", "errors", "always");
+			COMPLETE_WITH_CS("never", "errors", "always");
 		else if (TailMatchesCS("VERBOSITY"))
-			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
+			COMPLETE_WITH_CS("default", "verbose", "terse");
 	}
 	else if (TailMatchesCS("\\sf*"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
@@ -3632,7 +3557,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	if (matches == NULL)
 	{
-		COMPLETE_WITH_CONST("");
+		COMPLETE_WITH("");
 #ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
 		rl_completion_append_character = '\0';
 #endif
-- 
2.17.0



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 20:20  Tom Lane <[email protected]>
  parent: Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-21 20:20 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Thomas Munro <[email protected]> writes:
> On Fri, Sep 21, 2018 at 5:52 PM Andres Freund <[email protected]> wrote:
>> Here's a very quick-and-dirty implementation of this approach. Some very
>> very brief testing seems to indicate it works, although I'm sure not
>> perfectly.

> And here is a quick-and-dirty variadic COMPLETE_WITH(...).  Together:

I'm a bit inclined to get rid of the need for PP_NARG() by instead making
the macros add a trailing NULL argument, viz

#define TailMatches(...) \
  CheckTailMatchesFor(previous_words_count, previous_words, __VA_ARGS__, NULL)

This'd require (some of) the implementation functions to iterate over
their variadic arguments twice, the first time merely counting how many
there are.  But we aren't exactly concerned about max runtime performance
here, and the PP_NARG() thing seems like a crock that could easily blow
out compilation time on some compilers.

If people are OK with that design decision, I'm happy to assemble these
pieces and push it through.  I just had to add two more versions of
HeadMatchesN :-( so I'm feeling motivated to make something happen.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 20:51  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2018-09-21 20:51 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Hi,

On 2018-09-21 16:20:42 -0400, Tom Lane wrote:
> Thomas Munro <[email protected]> writes:
> > On Fri, Sep 21, 2018 at 5:52 PM Andres Freund <[email protected]> wrote:
> >> Here's a very quick-and-dirty implementation of this approach. Some very
> >> very brief testing seems to indicate it works, although I'm sure not
> >> perfectly.
> 
> > And here is a quick-and-dirty variadic COMPLETE_WITH(...).  Together:
> 
> I'm a bit inclined to get rid of the need for PP_NARG() by instead making
> the macros add a trailing NULL argument, viz
> 
> #define TailMatches(...) \
>   CheckTailMatchesFor(previous_words_count, previous_words, __VA_ARGS__, NULL)

I don't think that'd *quite* work right now - MatchAny is also NULL. We
probably could make it work by redefining either MatchAny or the last
argument to CheckTailMatchesFor() to some other value however.


> This'd require (some of) the implementation functions to iterate over
> their variadic arguments twice, the first time merely counting how many
> there are.

Yea, leaving the above problem aside, I've a hard time to get excited
about that overhead.


> But we aren't exactly concerned about max runtime performance
> here, and the PP_NARG() thing seems like a crock that could easily blow
> out compilation time on some compilers.

It's not actually that complicated an expansion, and we've not
encountered many problems with expansions that are similarly complex,
e.g. ereport et al.  It's pretty likely at least as cheap as the current
approach, where we sometimes have 9 deep recursion. So I'm not too
concerned about the compile-time performance. FWIW, I tested it with gcc
-E yesterday, and I couldn't measure any difference.

I think there's some argument to be made about the "mental" complexity
of the macros - if we went for them, we'd certainly need to add some
docs about how they work.  One argument for having PP_NARGS (renamed) is
that it doesn't seem useful just here, but in a few other cases as well.

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

*  Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 21:15  Thomas Munro <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2018-09-21 21:15 UTC (permalink / raw)
  To: [email protected]; +Cc: [email protected]; [email protected]; [email protected]; pgsql-hackers

On Sat, Sep 22, 2018 at 8:51 AM Andres Freund <[email protected]> wrote:
> I think there's some argument to be made about the "mental" complexity
> of the macros - if we went for them, we'd certainly need to add some
> docs about how they work.  One argument for having PP_NARGS (renamed) is
> that it doesn't seem useful just here, but in a few other cases as well.

It's a nice general facility to have in the tree.  It seems to compile
OK on clang, gcc, MSVC (I added this thread as CF entry 20/1798 as a
lazy way to see if AppVeyor would build it OK, and it worked fine
until conflicting commits landed).  I wonder if xlc, icc, aCC and Sun
Studio can grok it.

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




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 21:46  Andres Freund <[email protected]>
  parent: Thomas Munro <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Andres Freund @ 2018-09-21 21:46 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: [email protected]; [email protected]; [email protected]; pgsql-hackers

Hi,

On 2018-09-22 09:15:27 +1200, Thomas Munro wrote:
> On Sat, Sep 22, 2018 at 8:51 AM Andres Freund <[email protected]> wrote:
> > I think there's some argument to be made about the "mental" complexity
> > of the macros - if we went for them, we'd certainly need to add some
> > docs about how they work.  One argument for having PP_NARGS (renamed) is
> > that it doesn't seem useful just here, but in a few other cases as well.
> 
> It's a nice general facility to have in the tree.  It seems to compile
> OK on clang, gcc, MSVC (I added this thread as CF entry 20/1798 as a
> lazy way to see if AppVeyor would build it OK, and it worked fine
> until conflicting commits landed).  I wonder if xlc, icc, aCC and Sun
> Studio can grok it.

I think unless $compiler doesn't correctly implement vararg macros, it
really should just work.  There's nothing but pretty smart use of
actually pretty plain vararg macros.  If any of the other compilers have
troubles with that, they'd really not implement vararg macros...

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

*  Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 21:56  Thomas Munro <[email protected]>
  parent: Andres Freund <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2018-09-21 21:56 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

On Sat, Sep 22, 2018 at 9:46 AM Andres Freund <[email protected]> wrote:
> On 2018-09-22 09:15:27 +1200, Thomas Munro wrote:
> > On Sat, Sep 22, 2018 at 8:51 AM Andres Freund <[email protected]> wrote:
> > > I think there's some argument to be made about the "mental" complexity
> > > of the macros - if we went for them, we'd certainly need to add some
> > > docs about how they work.  One argument for having PP_NARGS (renamed) is
> > > that it doesn't seem useful just here, but in a few other cases as well.
> >
> > It's a nice general facility to have in the tree.  It seems to compile
> > OK on clang, gcc, MSVC (I added this thread as CF entry 20/1798 as a
> > lazy way to see if AppVeyor would build it OK, and it worked fine
> > until conflicting commits landed).  I wonder if xlc, icc, aCC and Sun
> > Studio can grok it.
>
> I think unless $compiler doesn't correctly implement vararg macros, it
> really should just work.  There's nothing but pretty smart use of
> actually pretty plain vararg macros.  If any of the other compilers have
> troubles with that, they'd really not implement vararg macros...

I vote for doing it this way then.  It may turn out to be useful for
efficient SearchSysCache(...), DirectFunctionCall(...) and other
things like that.

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




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 22:00  Tom Lane <[email protected]>
  parent: Andres Freund <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-21 22:00 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; [email protected]; [email protected]; pgsql-hackers

Andres Freund <[email protected]> writes:
> On 2018-09-22 09:15:27 +1200, Thomas Munro wrote:
>> On Sat, Sep 22, 2018 at 8:51 AM Andres Freund <[email protected]> wrote:
>>> I think there's some argument to be made about the "mental" complexity
>>> of the macros - if we went for them, we'd certainly need to add some
>>> docs about how they work.  One argument for having PP_NARGS (renamed) is
>>> that it doesn't seem useful just here, but in a few other cases as well.

If you want to rename it, then to what?  VA_ARGS_NARGS, perhaps?

>> It's a nice general facility to have in the tree.

Yeah, that's a fair point.

>> It seems to compile
>> OK on clang, gcc, MSVC (I added this thread as CF entry 20/1798 as a
>> lazy way to see if AppVeyor would build it OK, and it worked fine
>> until conflicting commits landed).  I wonder if xlc, icc, aCC and Sun
>> Studio can grok it.

> I think unless $compiler doesn't correctly implement vararg macros, it
> really should just work.

Well, we'd find out pretty quickly if we try to use it here.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 22:53  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2018-09-21 22:53 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; [email protected]; [email protected]; pgsql-hackers

On 2018-09-21 18:00:35 -0400, Tom Lane wrote:
> Andres Freund <[email protected]> writes:
> > On 2018-09-22 09:15:27 +1200, Thomas Munro wrote:
> >> On Sat, Sep 22, 2018 at 8:51 AM Andres Freund <[email protected]> wrote:
> >>> I think there's some argument to be made about the "mental" complexity
> >>> of the macros - if we went for them, we'd certainly need to add some
> >>> docs about how they work.  One argument for having PP_NARGS (renamed) is
> >>> that it doesn't seem useful just here, but in a few other cases as well.
> 
> If you want to rename it, then to what?  VA_ARGS_NARGS, perhaps?

I like your suggestion. I mainly didn't like the PP_ prefix.


> >> It's a nice general facility to have in the tree.
> 
> Yeah, that's a fair point.
> 
> >> It seems to compile
> >> OK on clang, gcc, MSVC (I added this thread as CF entry 20/1798 as a
> >> lazy way to see if AppVeyor would build it OK, and it worked fine
> >> until conflicting commits landed).  I wonder if xlc, icc, aCC and Sun
> >> Studio can grok it.
> 
> > I think unless $compiler doesn't correctly implement vararg macros, it
> > really should just work.
> 
> Well, we'd find out pretty quickly if we try to use it here.

You earlier were talking about tackling this - do you still want to? I
can otherwise, but it'll not be today, but likely tomorrow.

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-21 23:27  Tom Lane <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2018-09-21 23:27 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; [email protected]; [email protected]; pgsql-hackers

Andres Freund <[email protected]> writes:
> On 2018-09-21 18:00:35 -0400, Tom Lane wrote:
>> If you want to rename it, then to what?  VA_ARGS_NARGS, perhaps?

> I like your suggestion. I mainly didn't like the PP_ prefix.

Sold.  The original author overcomplicated it anyway; I now have

/*
 * VA_ARGS_NARGS
 *        Returns the number of macro arguments it is passed.
 *
 * An empty argument still counts as an argument, so effectively, this is
 * "one more than the number of commas in the argument list".
 *
 * This works for up to 63 arguments.  Internally, VA_ARGS_NARGS_() is passed
 * 64+N arguments, and the C99 standard only requires macros to allow up to
 * 127 arguments, so we can't portably go higher.  The implementation is
 * pretty trivial: VA_ARGS_NARGS_() returns its 64th argument, and we set up
 * the call so that that is the appropriate one of the list of constants.
 */
#define VA_ARGS_NARGS(...) \
    VA_ARGS_NARGS_(__VA_ARGS__, \
                   63,62,61,60,                   \
                   59,58,57,56,55,54,53,52,51,50, \
                   49,48,47,46,45,44,43,42,41,40, \
                   39,38,37,36,35,34,33,32,31,30, \
                   29,28,27,26,25,24,23,22,21,20, \
                   19,18,17,16,15,14,13,12,11,10, \
                   9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define VA_ARGS_NARGS_( \
    _01,_02,_03,_04,_05,_06,_07,_08,_09,_10, \
    _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
    _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
    _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
    _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
    _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
    _61,_62,_63,  N, ...) \
    (N)

from which it's pretty obvious that this really is a simple macro
call.  I'd first thought it involved macro recursion, but it doesn't.

> You earlier were talking about tackling this - do you still want to? I
> can otherwise, but it'll not be today, but likely tomorrow.

On it now.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-22 01:52  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Andres Freund @ 2018-09-22 01:52 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; [email protected]; [email protected]; pgsql-hackers

Hi,

On 2018-09-21 19:27:48 -0400, Tom Lane wrote:
> > You earlier were talking about tackling this - do you still want to? I
> > can otherwise, but it'll not be today, but likely tomorrow.
> 
> On it now.

Thanks, looks good.  msvc and icc are, as expected, ok too.

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE
@ 2018-09-22 01:57  Andres Freund <[email protected]>
  parent: Thomas Munro <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Andres Freund @ 2018-09-22 01:57 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Tom Lane <[email protected]>; Arthur Zakirov <[email protected]>; Dagfinn Ilmari Mannsåker <[email protected]>; pgsql-hackers

Hi,

On 2018-09-22 09:56:00 +1200, Thomas Munro wrote:
> I vote for doing it this way then.  It may turn out to be useful for
> efficient SearchSysCache(...), DirectFunctionCall(...) and other
> things like that.

Yea, especially the *FunctionCall* stuff is awfully verbose.

I also wonder if it could make
http://archives.postgresql.org/message-id/20180605172952.x34m5uz6ju6enaem%40alap3.anarazel.de
a bit less painful.

Greetings,

Andres Freund




^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH 4/7] Add infrastructure to WAL-logging skip feature
@ 2018-10-11 07:00  Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Kyotaro Horiguchi @ 2018-10-11 07:00 UTC (permalink / raw)

We used to optimize WAL-logging for truncation of in-transaction
crated tables in minimal mode by just signaling by
HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can
emit WAL records that results in corrupt state for certain series of
in-transaction operations. This patch provides infrastructure to track
pending at-commit fsyncs for a relation and in-transaction truncations.
heap_register_sync() should be used to start tracking before batch
operations like COPY and CLUSTER, and use BufferNeedsWAL() instead of
RelationNeedsWAL() at the places related to WAL-logging about
heap-modifying operations.
---
 src/backend/access/heap/heapam.c    |  31 ++++
 src/backend/access/transam/xact.c   |  11 ++
 src/backend/catalog/storage.c       | 344 +++++++++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/storage/buffer/bufmgr.c |  40 ++++-
 src/backend/utils/cache/relcache.c  |   3 +
 src/include/access/heapam.h         |   1 +
 src/include/catalog/storage.h       |   5 +
 src/include/storage/bufmgr.h        |   2 +
 src/include/utils/rel.h             |   8 +
 10 files changed, 417 insertions(+), 31 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fe5d939c45..024620ddc1 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -51,6 +51,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -8829,3 +8830,33 @@ heap_mask(char *pagedata, BlockNumber blkno)
 		}
 	}
 }
+
+/*
+ *	heap_register_sync	- register a heap to be synced to disk at commit
+ *
+ * This can be used to skip WAL-logging changes on a relation file that has
+ * been created in the same transaction. This makes note of the current size of
+ * the relation, and ensures that when the relation is extended, any changes
+ * to the new blocks in the heap, in the same transaction, will not be
+ * WAL-logged. Instead, the heap contents are flushed to disk at commit,
+ * like heap_sync() does.
+ *
+ * This does the same for the TOAST heap, if any. Indexes are not affected.
+ */
+void
+heap_register_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	RecordWALSkipping(rel);
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		RecordWALSkipping(toastrel);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c3214d4f4d..32a6a877f3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2022,6 +2022,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2254,6 +2257,9 @@ PrepareTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2579,6 +2585,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	smgrDoPendingSyncs(false);	/* abandone pending syncs */
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4097,6 +4104,8 @@ ReleaseSavepoint(const char *name)
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
 				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
+	smgrProcessWALRequirementInval(s->subTransactionId, true);
+
 	/*
 	 * Mark "commit pending" all subtransactions up to the target
 	 * subtransaction.  The actual commits will happen when control gets to
@@ -4206,6 +4215,8 @@ RollbackToSavepoint(const char *name)
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
 				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
+	smgrProcessWALRequirementInval(s->subTransactionId, false);
+
 	/*
 	 * Mark "abort pending" all subtransactions up to the target
 	 * subtransaction.  The actual aborts will happen when control gets to
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 0302507e6f..a0cf8d3e27 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -28,6 +28,7 @@
 #include "catalog/storage_xlog.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
+#include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -62,6 +63,54 @@ typedef struct PendingRelDelete
 
 static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
 
+/*
+ * We also track relation files (RelFileNode values) that have been created
+ * in the same transaction, and that have been modified without WAL-logging
+ * the action (an optimization possible with wal_level=minimal). When we are
+ * about to skip WAL-logging, a PendingRelSync entry is created, and
+ * 'skip_wal_min_blk' is set to the current size of the relation. Any operations
+ * on blocks < skip_wal_min_blk need to be WAL-logged as usual, but for
+ * operations on higher blocks, WAL-logging is skipped.
+
+ *
+ * NB: after WAL-logging has been skipped for a block, we must not WAL-log
+ * any subsequent actions on the same block either. Replaying the WAL record
+ * of the subsequent action might fail otherwise, as the "before" state of
+ * the block might not match, as the earlier actions were not WAL-logged.
+ * Likewise, after we have WAL-logged an operation for a block, we must
+ * WAL-log any subsequent operations on the same page as well. Replaying
+ * a possible full-page-image from the earlier WAL record would otherwise
+ * revert the page to the old state, even if we sync the relation at end
+ * of transaction.
+ *
+ * If a relation is truncated (without creating a new relfilenode), and we
+ * emit a WAL record of the truncation, we can't skip WAL-logging for any
+ * of the truncated blocks anymore, as replaying the truncation record will
+ * destroy all the data inserted after that. But if we have already decided
+ * to skip WAL-logging changes to a relation, and the relation is truncated,
+ * we don't need to WAL-log the truncation either.
+ *
+ * This mechanism is currently only used by heaps. Indexes are always
+ * WAL-logged. Also, this only applies for wal_level=minimal; with higher
+ * WAL levels we need the WAL for PITR/replication anyway.
+ */
+typedef struct RelWalRequirement
+{
+	RelFileNode relnode;		/* relation created in same xact */
+	BlockNumber skip_wal_min_blk;/* WAL-logging skipped for blocks >=
+								  * skip_wal_min_blk */
+	BlockNumber wal_log_min_blk; /* The minimum blk number that requires
+								  * WAL-logging even if skipped by the above*/
+	SubTransactionId invalidate_sxid; /* subxid where this entry is
+									   * invalidated */
+}	RelWalRequirement;
+
+/* Relations that need to be fsync'd at commit */
+static HTAB *relWalRequirements = NULL;
+static int	 walreq_pending_invals = 0;
+
+static RelWalRequirement *getWalRequirementEntry(Relation rel, bool create);
+
 /*
  * RelationCreateStorage
  *		Create physical storage for a relation.
@@ -259,37 +308,114 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
 	 */
 	if (RelationNeedsWAL(rel))
 	{
-		/*
-		 * Make an XLOG entry reporting the file truncation.
-		 */
-		XLogRecPtr	lsn;
-		xl_smgr_truncate xlrec;
+		RelWalRequirement *walreq;
 
-		xlrec.blkno = nblocks;
-		xlrec.rnode = rel->rd_node;
-		xlrec.flags = SMGR_TRUNCATE_ALL;
+		/* get pending sync entry, create if not yet */
+		walreq = getWalRequirementEntry(rel, true);
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+		if (walreq->skip_wal_min_blk == InvalidBlockNumber ||
+			walreq->skip_wal_min_blk < nblocks)
+		{
+			/*
+			 * This is the first time truncation of this relation in this
+			 * transaction or truncation that leaves pages that need at-commit
+			 * fsync.  Make an XLOG entry reporting the file truncation.
+			 */
+			XLogRecPtr		lsn;
+			xl_smgr_truncate xlrec;
 
-		lsn = XLogInsert(RM_SMGR_ID,
-						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+			xlrec.blkno = nblocks;
+			xlrec.rnode = rel->rd_node;
+			xlrec.flags = SMGR_TRUNCATE_ALL;
 
-		/*
-		 * Flush, because otherwise the truncation of the main relation might
-		 * hit the disk before the WAL record, and the truncation of the FSM
-		 * or visibility map. If we crashed during that window, we'd be left
-		 * with a truncated heap, but the FSM or visibility map would still
-		 * contain entries for the non-existent heap pages.
-		 */
-		if (fsm || vm)
-			XLogFlush(lsn);
+			XLogBeginInsert();
+			XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+
+			lsn = XLogInsert(RM_SMGR_ID,
+							 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+
+			/*
+			 * Flush, because otherwise the truncation of the main relation
+			 * might hit the disk before the WAL record, and the truncation of
+			 * the FSM or visibility map. If we crashed during that window,
+			 * we'd be left with a truncated heap, but the FSM or visibility
+			 * map would still contain entries for the non-existent heap
+			 * pages.
+			 */
+			if (fsm || vm)
+				XLogFlush(lsn);
+
+			/* no longer skip WAL-logging for the blocks */
+			rel->rd_walrequirement->wal_log_min_blk = nblocks;
+		}
 	}
 
 	/* Do the real work */
 	smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
 }
 
+/*
+ * getWalRequirementEntry: get WAL requirement entry.
+ *
+ * Returns WAL requirement entry for the relation. The entry tracks
+ * WAL-skipping blocks for the relation.  The WAL-skipped blocks need fsync at
+ * commit time.  Creates one if needed when create is true.
+ */
+static RelWalRequirement *
+getWalRequirementEntry(Relation rel, bool create)
+{
+	RelWalRequirement *walreq_entry = NULL;
+	bool			found;
+
+	if (rel->rd_walrequirement)
+		return rel->rd_walrequirement;
+
+	/* we know we don't have pending sync entry */
+	if (!create && rel->rd_nowalrequirement)
+		return NULL;
+
+	if (!relWalRequirements)
+	{
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		if (!create)
+			return NULL;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(RelFileNode);
+		ctl.entrysize = sizeof(RelWalRequirement);
+		ctl.hash = tag_hash;
+		relWalRequirements = hash_create("pending relation sync table", 5,
+								   &ctl, HASH_ELEM | HASH_FUNCTION);
+	}
+
+	walreq_entry = (RelWalRequirement *)
+		hash_search(relWalRequirements, (void *) &rel->rd_node,
+					create ? HASH_ENTER: HASH_FIND,	&found);
+
+	if (!walreq_entry)
+	{
+		/* prevent further hash lookup */
+		rel->rd_nowalrequirement = true;
+		return NULL;
+	}
+
+	/* new entry created */
+	if (!found)
+	{
+		walreq_entry->wal_log_min_blk = InvalidBlockNumber;
+		walreq_entry->skip_wal_min_blk = InvalidBlockNumber;
+		walreq_entry->invalidate_sxid = InvalidSubTransactionId;
+	}
+
+	/* hold shortcut in Relation */
+	rel->rd_nowalrequirement = false;
+	rel->rd_walrequirement = walreq_entry;
+
+	return walreq_entry;
+}
+
 /*
  *	smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
  *
@@ -367,6 +493,34 @@ smgrDoPendingDeletes(bool isCommit)
 	}
 }
 
+/*
+ * RelationInvalidateWALRequirements() -- invalidate wal requirement entry
+ */
+void
+RelationInvalidateWALRequirements(Relation rel)
+{
+	RelWalRequirement *walreq;
+
+	/* we know we don't have one */
+	if (rel->rd_nowalrequirement)
+		return;
+
+	walreq = getWalRequirementEntry(rel, false);
+	
+	if (!walreq)
+		return;
+
+	/*
+	 * The state is reset at subtransaction commit/abort. No invalidation
+	 * request must not come for the same relation in the same subtransaction.
+	 */
+	Assert(walreq->invalidate_sxid == InvalidSubTransactionId);
+
+	walreq_pending_invals++;
+	walreq->invalidate_sxid = GetCurrentSubTransactionId();
+}
+
+
 /*
  * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted.
  *
@@ -418,6 +572,154 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
 	return nrels;
 }
 
+
+/*
+ * Remember that the given relation doesn't need WAL-logging for the blocks
+ * after the current block size and the blocks are going to be sync'd at
+ * commit.
+ */
+void
+RecordWALSkipping(Relation rel)
+{
+	BlockNumber nblocks;
+	RelWalRequirement *walreq;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* get pending sync entry, create if not yet  */
+	walreq = getWalRequirementEntry(rel, true);
+
+	nblocks = RelationGetNumberOfBlocks(rel);
+
+	/*
+	 *  Record only the first registration.
+	 */
+	if (walreq->skip_wal_min_blk != InvalidBlockNumber)
+		return;
+
+	walreq->skip_wal_min_blk = nblocks;
+}
+
+/*
+ * Do changes to given heap page need to be WAL-logged?
+ *
+ * This takes into account any previous RecordPendingSync() requests.
+ *
+ * Note that it is required to check this before creating any WAL records for
+ * heap pages - it is not merely an optimization! WAL-logging a record, when
+ * we have already skipped a previous WAL record for the same page could lead
+ * to failure at WAL replay, as the "before" state expected by the record
+ * might not match what's on disk. Also, if the heap was truncated earlier, we
+ * must WAL-log any changes to the once-truncated blocks, because replaying
+ * the truncation record will destroy them.
+ */
+bool
+BufferNeedsWAL(Relation rel, Buffer buf)
+{
+	BlockNumber		blkno = InvalidBlockNumber;
+	RelWalRequirement *walreq;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch exising pending sync entry */
+	walreq = getWalRequirementEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't have special
+	 * WAL requirement
+	 */
+	if (!walreq)
+		return true;
+
+	Assert(BufferIsValid(buf));
+
+	blkno = BufferGetBlockNumber(buf);
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walreq->skip_wal_min_blk == InvalidBlockNumber ||
+		walreq->skip_wal_min_blk > blkno)
+		return true;
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walreq->wal_log_min_blk != InvalidBlockNumber &&
+		walreq->wal_log_min_blk <= blkno)
+		return true;
+
+	return false;
+}
+
+/*
+ * Sync to disk any relations that we have skipped WAL-logging earlier.
+ */
+void
+smgrDoPendingSyncs(bool isCommit)
+{
+	if (!relWalRequirements)
+		return;
+
+	if (isCommit)
+	{
+		HASH_SEQ_STATUS status;
+		RelWalRequirement *walreq;
+
+		hash_seq_init(&status, relWalRequirements);
+
+		while ((walreq = hash_seq_search(&status)) != NULL)
+		{
+			if (walreq->skip_wal_min_blk != InvalidBlockNumber)
+			{
+				FlushRelationBuffersWithoutRelCache(walreq->relnode, false);
+				smgrimmedsync(smgropen(walreq->relnode, InvalidBackendId),
+							  MAIN_FORKNUM);
+			}
+		}
+	}
+
+	hash_destroy(relWalRequirements);
+	relWalRequirements = NULL;
+}
+
+/*
+ * Process pending invalidation of WAL requirements happened in the
+ * subtransaction
+ */
+void
+smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit)
+{
+	HASH_SEQ_STATUS status;
+	RelWalRequirement *walreq;
+
+	if (!relWalRequirements || walreq_pending_invals == 0)
+		return;
+
+	/*
+	 * It may take some time when there're many relWalRequirements entries.  We
+	 * expect that we don't have relWalRequirements in almost all cases.
+	 */
+	hash_seq_init(&status, relWalRequirements);
+
+	while ((walreq = hash_seq_search(&status)) != NULL)
+	{
+		if (walreq->invalidate_sxid == sxid)
+		{
+			Assert(walreq_pending_invals > 0);
+			walreq->invalidate_sxid = InvalidSubTransactionId;
+			walreq_pending_invals--;
+			if (isCommit)
+			{
+				walreq->skip_wal_min_blk = InvalidBlockNumber;
+				walreq->wal_log_min_blk = InvalidBlockNumber;
+			}
+		}		
+	}	
+}
+
 /*
  *	PostPrepare_smgr -- Clean up after a successful PREPARE
  *
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3183b2aaa1..45bb0b5614 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11587,11 +11587,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 
 	/*
 	 * Create and copy all forks of the relation, and schedule unlinking of
-	 * old physical files.
+	 * old physical files. Pending syncs for the old node is no longer needed.
 	 *
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
+	RelationInvalidateWALRequirements(rel);
 	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
 
 	/* copy main fork */
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 273e2f385f..a9741f138c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr,
 			BufferAccessStrategy strategy,
 			bool *foundPtr);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln);
+static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal);
 static void AtProcExit_Buffers(int code, Datum arg);
 static void CheckForBufferLeaks(void);
 static int	rnode_comparator(const void *p1, const void *p2);
@@ -3153,20 +3154,41 @@ PrintPinnedBufs(void)
 void
 FlushRelationBuffers(Relation rel)
 {
-	int			i;
-	BufferDesc *bufHdr;
-
 	/* Open rel at the smgr level if not already done */
 	RelationOpenSmgr(rel);
 
-	if (RelationUsesLocalBuffers(rel))
+	FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel));
+}
+
+/*
+ * Like FlushRelationBuffers(), but the relation is specified by a
+ * RelFileNode
+ */
+void
+FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal)
+{
+	FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal);
+}
+
+/*
+ * Code shared between functions FlushRelationBuffers() and
+ * FlushRelationBuffersWithoutRelCache().
+ */
+static void
+FlushRelationBuffers_common(SMgrRelation smgr, bool islocal)
+{
+	RelFileNode rnode = smgr->smgr_rnode.node;
+	int			i;
+	BufferDesc *bufHdr;
+
+	if (islocal)
 	{
 		for (i = 0; i < NLocBuffer; i++)
 		{
 			uint32		buf_state;
 
 			bufHdr = GetLocalBufferDescriptor(i);
-			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+			if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 				((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
 				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 			{
@@ -3183,7 +3205,7 @@ FlushRelationBuffers(Relation rel)
 
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
-				smgrwrite(rel->rd_smgr,
+				smgrwrite(smgr,
 						  bufHdr->tag.forkNum,
 						  bufHdr->tag.blockNum,
 						  localpage,
@@ -3213,18 +3235,18 @@ FlushRelationBuffers(Relation rel)
 		 * As in DropRelFileNodeBuffers, an unlocked precheck should be safe
 		 * and saves some cycles.
 		 */
-		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+		if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
 			continue;
 
 		ReservePrivateRefCountEntry();
 
 		buf_state = LockBufHdr(bufHdr);
-		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+		if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 			(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 		{
 			PinBuffer_Locked(bufHdr);
 			LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
-			FlushBuffer(bufHdr, rel->rd_smgr);
+			FlushBuffer(bufHdr, smgr);
 			LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
 			UnpinBuffer(bufHdr, true);
 		}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 84609e0725..95e834d45e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -75,6 +75,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
+#include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/array.h"
@@ -5625,6 +5626,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
 		MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
+		rel->rd_nowalrequirement = false;
+		rel->rd_walrequirement = NULL;
 
 		/*
 		 * Recompute lock and physical addressing info.  This is needed in
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 945ca50616..509394bb35 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -174,6 +174,7 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
+extern void heap_register_sync(Relation relation);
 extern void heap_sync(Relation relation);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9f638be924..76178b87f2 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,6 +22,7 @@ extern void RelationCreateStorage(RelFileNode rnode, char relpersistence);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
+extern void RelationInvalidateWALRequirements(Relation rel);
 
 /*
  * These functions used to be in storage/smgr/smgr.c, which explains the
@@ -29,6 +30,10 @@ extern void RelationTruncate(Relation rel, BlockNumber nblocks);
  */
 extern void smgrDoPendingDeletes(bool isCommit);
 extern int	smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
+extern void smgrDoPendingSyncs(bool isCommit);
+extern void smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit);
+extern void RecordWALSkipping(Relation rel);
+bool BufferNeedsWAL(Relation rel, Buffer buf);
 extern void AtSubCommit_smgr(void);
 extern void AtSubAbort_smgr(void);
 extern void PostPrepare_smgr(void);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index c5826f691d..8a9ea041dd 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
 extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
+extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode,
+									bool islocal);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
 					   ForkNumber forkNum, BlockNumber firstDelBlock);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 54028515a7..30f0d5bd83 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -198,6 +198,14 @@ typedef struct RelationData
 
 	/* use "struct" here to avoid needing to include pgstat.h: */
 	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+	/*
+	 * rd_nowalrequirement is true if this relation is known not to have
+	 * special WAL requirements.  Otherwise we need to ask smgr for an entry
+	 * if rd_walrequirement is NULL.
+	 */
+	bool						rd_nowalrequirement;
+	struct RelWalRequirement   *rd_walrequirement;
 } RelationData;
 
 
-- 
2.16.3


----Next_Part(Mon_Mar_25_21_32_04_2019_838)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0005-Fix-WAL-skipping-feature.patch"



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH 3/4] Add infrastructure to WAL-logging skip feature
@ 2018-10-11 07:00  Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Kyotaro Horiguchi @ 2018-10-11 07:00 UTC (permalink / raw)

We used to optimize WAL-logging for truncation of in-transaction
crated tables in minimal mode by just singaling by
HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can
emit WAL records that results in corrupt state for certain series of
in-transaction operations. This patch provides infrastructure to track
pending at-comit fsyncs for a relation and in-transaction truncations.
heap_register_sync() should be used to start tracking before batch
operations like COPY and CLUSTER, and use BufferNeedsWAL() instead of
RelationNeedsWAL() at the places related to WAL-logging about
heap-modifying operations.
---
 src/backend/access/heap/heapam.c    |  31 ++++
 src/backend/access/transam/xact.c   |   7 +
 src/backend/catalog/storage.c       | 317 +++++++++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c    |   3 +-
 src/backend/storage/buffer/bufmgr.c |  40 ++++-
 src/backend/utils/cache/relcache.c  |  13 ++
 src/include/access/heapam.h         |   1 +
 src/include/catalog/storage.h       |   5 +-
 src/include/storage/bufmgr.h        |   2 +
 src/include/utils/rel.h             |   8 +
 10 files changed, 395 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index dc3499349b..5ea5ff5848 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -50,6 +50,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -9079,3 +9080,33 @@ heap_mask(char *pagedata, BlockNumber blkno)
 		}
 	}
 }
+
+/*
+ *	heap_register_sync	- register a heap to be synced to disk at commit
+ *
+ * This can be used to skip WAL-logging changes on a relation file that has
+ * been created in the same transaction. This makes note of the current size of
+ * the relation, and ensures that when the relation is extended, any changes
+ * to the new blocks in the heap, in the same transaction, will not be
+ * WAL-logged. Instead, the heap contents are flushed to disk at commit,
+ * like heap_sync() does.
+ *
+ * This does the same for the TOAST heap, if any. Indexes are not affected.
+ */
+void
+heap_register_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	RecordPendingSync(rel);
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		RecordPendingSync(toastrel);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e93262975d..6d62d6e34f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2021,6 +2021,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2250,6 +2253,9 @@ PrepareTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2575,6 +2581,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	smgrDoPendingSyncs(false);	/* abandone pending syncs */
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 0302507e6f..26dc3ddb1b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -28,6 +28,7 @@
 #include "catalog/storage_xlog.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
+#include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -62,6 +63,49 @@ typedef struct PendingRelDelete
 
 static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
 
+/*
+ * We also track relation files (RelFileNode values) that have been created
+ * in the same transaction, and that have been modified without WAL-logging
+ * the action (an optimization possible with wal_level=minimal). When we are
+ * about to skip WAL-logging, a PendingRelSync entry is created, and
+ * 'sync_above' is set to the current size of the relation. Any operations
+ * on blocks < sync_above need to be WAL-logged as usual, but for operations
+ * on higher blocks, WAL-logging is skipped.
+ *
+ * NB: after WAL-logging has been skipped for a block, we must not WAL-log
+ * any subsequent actions on the same block either. Replaying the WAL record
+ * of the subsequent action might fail otherwise, as the "before" state of
+ * the block might not match, as the earlier actions were not WAL-logged.
+ * Likewise, after we have WAL-logged an operation for a block, we must
+ * WAL-log any subsequent operations on the same page as well. Replaying
+ * a possible full-page-image from the earlier WAL record would otherwise
+ * revert the page to the old state, even if we sync the relation at end
+ * of transaction.
+ *
+ * If a relation is truncated (without creating a new relfilenode), and we
+ * emit a WAL record of the truncation, we can't skip WAL-logging for any
+ * of the truncated blocks anymore, as replaying the truncation record will
+ * destroy all the data inserted after that. But if we have already decided
+ * to skip WAL-logging changes to a relation, and the relation is truncated,
+ * we don't need to WAL-log the truncation either.
+ *
+ * This mechanism is currently only used by heaps. Indexes are always
+ * WAL-logged. Also, this only applies for wal_level=minimal; with higher
+ * WAL levels we need the WAL for PITR/replication anyway.
+ */
+typedef struct PendingRelSync
+{
+	RelFileNode relnode;		/* relation created in same xact */
+	BlockNumber sync_above;		/* WAL-logging skipped for blocks >=
+								 * sync_above */
+	BlockNumber truncated_to;	/* truncation WAL record was written */
+}	PendingRelSync;
+
+/* Relations that need to be fsync'd at commit */
+static HTAB *pendingSyncs = NULL;
+
+static PendingRelSync *getPendingSyncEntry(Relation rel, bool create);
+
 /*
  * RelationCreateStorage
  *		Create physical storage for a relation.
@@ -259,37 +303,117 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
 	 */
 	if (RelationNeedsWAL(rel))
 	{
-		/*
-		 * Make an XLOG entry reporting the file truncation.
-		 */
-		XLogRecPtr	lsn;
-		xl_smgr_truncate xlrec;
+		PendingRelSync *pending_sync;
 
-		xlrec.blkno = nblocks;
-		xlrec.rnode = rel->rd_node;
-		xlrec.flags = SMGR_TRUNCATE_ALL;
+		/* get pending sync entry, create if not yet */
+		pending_sync = getPendingSyncEntry(rel, true);
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+		if (pending_sync->sync_above == InvalidBlockNumber ||
+			pending_sync->sync_above < nblocks)
+		{
+			/*
+			 * This is the first time truncation of this relation in this
+			 * transaction or truncation that leaves pages that need at-commit
+			 * fsync.  Make an XLOG entry reporting the file truncation.
+			 */
+			XLogRecPtr		lsn;
+			xl_smgr_truncate xlrec;
 
-		lsn = XLogInsert(RM_SMGR_ID,
-						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+			xlrec.blkno = nblocks;
+			xlrec.rnode = rel->rd_node;
+			xlrec.flags = SMGR_TRUNCATE_ALL;
 
-		/*
-		 * Flush, because otherwise the truncation of the main relation might
-		 * hit the disk before the WAL record, and the truncation of the FSM
-		 * or visibility map. If we crashed during that window, we'd be left
-		 * with a truncated heap, but the FSM or visibility map would still
-		 * contain entries for the non-existent heap pages.
-		 */
-		if (fsm || vm)
-			XLogFlush(lsn);
+			XLogBeginInsert();
+			XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+
+			lsn = XLogInsert(RM_SMGR_ID,
+							 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+
+			elog(DEBUG2, "WAL-logged truncation of rel %u/%u/%u to %u blocks",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+				 nblocks);
+
+			/*
+			 * Flush, because otherwise the truncation of the main relation
+			 * might hit the disk before the WAL record, and the truncation of
+			 * the FSM or visibility map. If we crashed during that window,
+			 * we'd be left with a truncated heap, but the FSM or visibility
+			 * map would still contain entries for the non-existent heap
+			 * pages.
+			 */
+			if (fsm || vm)
+				XLogFlush(lsn);
+
+			rel->pending_sync->truncated_to = nblocks;
+		}
 	}
 
 	/* Do the real work */
 	smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
 }
 
+/*
+ * getPendingSyncEntry: get pendig sync entry.
+ *
+ * Returns pending sync entry for the relation. The entry tracks pending
+ * at-commit fsyncs for the relation.  Creates one if needed when create is
+ * true.
+ */
+static PendingRelSync *
+getPendingSyncEntry(Relation rel, bool create)
+{
+	PendingRelSync *pendsync_entry = NULL;
+	bool			found;
+
+	if (rel->pending_sync)
+		return rel->pending_sync;
+
+	/* we know we don't have pending sync entry */
+	if (!create && rel->no_pending_sync)
+		return NULL;
+
+	if (!pendingSyncs)
+	{
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		if (!create)
+			return NULL;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(RelFileNode);
+		ctl.entrysize = sizeof(PendingRelSync);
+		ctl.hash = tag_hash;
+		pendingSyncs = hash_create("pending relation sync table", 5,
+								   &ctl, HASH_ELEM | HASH_FUNCTION);
+	}
+
+	elog(DEBUG2, "getPendingSyncEntry: accessing hash for %d",
+		 rel->rd_node.relNode);
+	pendsync_entry = (PendingRelSync *)
+		hash_search(pendingSyncs, (void *) &rel->rd_node,
+					create ? HASH_ENTER: HASH_FIND,	&found);
+
+	if (!pendsync_entry)
+	{
+		rel->no_pending_sync = true;
+		return NULL;
+	}
+
+	/* new entry created */
+	if (!found)
+	{
+		pendsync_entry->truncated_to = InvalidBlockNumber;
+		pendsync_entry->sync_above = InvalidBlockNumber;
+	}
+
+	/* hold shortcut in Relation */
+	rel->no_pending_sync = false;
+	rel->pending_sync = pendsync_entry;
+
+	return pendsync_entry;
+}
+
 /*
  *	smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
  *
@@ -367,6 +491,24 @@ smgrDoPendingDeletes(bool isCommit)
 	}
 }
 
+/*
+ * RelationRemovePendingSync() -- remove pendingSync entry for a relation
+ */
+void
+RelationRemovePendingSync(Relation rel)
+{
+	bool found;
+
+	rel->pending_sync = NULL;
+	rel->no_pending_sync = true;
+	if (pendingSyncs)
+	{
+		elog(DEBUG2, "RelationRemovePendingSync: accessing hash");
+		hash_search(pendingSyncs, (void *) &rel->rd_node, HASH_REMOVE, &found);
+	}
+}
+
+
 /*
  * smgrGetPendingDeletes() -- Get a list of non-temp relations to be deleted.
  *
@@ -418,6 +560,139 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
 	return nrels;
 }
 
+
+/*
+ * Remember that the given relation needs to be sync'd at commit, because we
+ * are going to skip WAL-logging subsequent actions to it.
+ */
+void
+RecordPendingSync(Relation rel)
+{
+	BlockNumber nblocks;
+	PendingRelSync *pending_sync;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* get pending sync entry, create if not yet  */
+	pending_sync = getPendingSyncEntry(rel, true);
+
+	nblocks = RelationGetNumberOfBlocks(rel);
+
+	if (pending_sync->sync_above != InvalidBlockNumber)
+	{
+		elog(DEBUG2,
+			 "pending sync for rel %u/%u/%u was already registered at block %u (new %u)",
+			 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+			 rel->pending_sync->sync_above, nblocks);
+
+		return;
+	}
+
+	elog(DEBUG2,
+		 "registering new pending sync for rel %u/%u/%u at block %u",
+		 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+		 nblocks);
+	pending_sync->sync_above = nblocks;
+}
+
+/*
+ * Do changes to given heap page need to be WAL-logged?
+ *
+ * This takes into account any previous RecordPendingSync() requests.
+ *
+ * Note that it is required to check this before creating any WAL records for
+ * heap pages - it is not merely an optimization! WAL-logging a record, when
+ * we have already skipped a previous WAL record for the same page could lead
+ * to failure at WAL replay, as the "before" state expected by the record
+ * might not match what's on disk. Also, if the heap was truncated earlier, we
+ * must WAL-log any changes to the once-truncated blocks, because replaying
+ * the truncation record will destroy them.
+ */
+bool
+BufferNeedsWAL(Relation rel, Buffer buf)
+{
+	BlockNumber		blkno = InvalidBlockNumber;
+	PendingRelSync *pending_sync;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch exising pending sync entry */
+	pending_sync = getPendingSyncEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't have pending
+	 * sync
+	 */
+	if (!pending_sync)
+		return true;
+
+	Assert(BufferIsValid(buf));
+
+	blkno = BufferGetBlockNumber(buf);
+
+	/* we don't skip WAL-logging for pages that already done */
+	if (pending_sync->sync_above == InvalidBlockNumber ||
+		pending_sync->sync_above > blkno)
+	{
+		elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because sync_above is %u",
+			 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+			 blkno, rel->pending_sync->sync_above);
+		return true;
+	}
+
+	/*
+	 * We have emitted a truncation record for this block.
+	 */
+	if (pending_sync->truncated_to != InvalidBlockNumber &&
+		pending_sync->truncated_to <= blkno)
+	{
+		elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because it was truncated earlier in the same xact",
+			 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+			 blkno);
+		return true;
+	}
+
+	elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+		 rel->rd_node.spcNode, rel->rd_node.dbNode, rel->rd_node.relNode,
+		 blkno);
+
+	return false;
+}
+
+/*
+ * Sync to disk any relations that we skipped WAL-logging for earlier.
+ */
+void
+smgrDoPendingSyncs(bool isCommit)
+{
+	if (!pendingSyncs)
+		return;
+
+	if (isCommit)
+	{
+		HASH_SEQ_STATUS status;
+		PendingRelSync *pending;
+
+		hash_seq_init(&status, pendingSyncs);
+
+		while ((pending = hash_seq_search(&status)) != NULL)
+		{
+			if (pending->sync_above != InvalidBlockNumber)
+			{
+				FlushRelationBuffersWithoutRelCache(pending->relnode, false);
+				smgrimmedsync(smgropen(pending->relnode, InvalidBackendId), MAIN_FORKNUM);
+
+				elog(DEBUG2, "syncing rel %u/%u/%u", pending->relnode.spcNode,
+					 pending->relnode.dbNode, pending->relnode.relNode);
+			}
+		}
+	}
+
+	hash_destroy(pendingSyncs);
+	pendingSyncs = NULL;
+}
+
 /*
  *	PostPrepare_smgr -- Clean up after a successful PREPARE
  *
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a93b13c2fe..6190b3f605 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11412,11 +11412,12 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 
 	/*
 	 * Create and copy all forks of the relation, and schedule unlinking of
-	 * old physical files.
+	 * old physical files. Pending syncs for the old node is no longer needed.
 	 *
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
+	RelationRemovePendingSync(rel);
 	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
 
 	/* copy main fork */
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 273e2f385f..a9741f138c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr,
 			BufferAccessStrategy strategy,
 			bool *foundPtr);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln);
+static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal);
 static void AtProcExit_Buffers(int code, Datum arg);
 static void CheckForBufferLeaks(void);
 static int	rnode_comparator(const void *p1, const void *p2);
@@ -3153,20 +3154,41 @@ PrintPinnedBufs(void)
 void
 FlushRelationBuffers(Relation rel)
 {
-	int			i;
-	BufferDesc *bufHdr;
-
 	/* Open rel at the smgr level if not already done */
 	RelationOpenSmgr(rel);
 
-	if (RelationUsesLocalBuffers(rel))
+	FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel));
+}
+
+/*
+ * Like FlushRelationBuffers(), but the relation is specified by a
+ * RelFileNode
+ */
+void
+FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal)
+{
+	FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal);
+}
+
+/*
+ * Code shared between functions FlushRelationBuffers() and
+ * FlushRelationBuffersWithoutRelCache().
+ */
+static void
+FlushRelationBuffers_common(SMgrRelation smgr, bool islocal)
+{
+	RelFileNode rnode = smgr->smgr_rnode.node;
+	int			i;
+	BufferDesc *bufHdr;
+
+	if (islocal)
 	{
 		for (i = 0; i < NLocBuffer; i++)
 		{
 			uint32		buf_state;
 
 			bufHdr = GetLocalBufferDescriptor(i);
-			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+			if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 				((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
 				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 			{
@@ -3183,7 +3205,7 @@ FlushRelationBuffers(Relation rel)
 
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
-				smgrwrite(rel->rd_smgr,
+				smgrwrite(smgr,
 						  bufHdr->tag.forkNum,
 						  bufHdr->tag.blockNum,
 						  localpage,
@@ -3213,18 +3235,18 @@ FlushRelationBuffers(Relation rel)
 		 * As in DropRelFileNodeBuffers, an unlocked precheck should be safe
 		 * and saves some cycles.
 		 */
-		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+		if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
 			continue;
 
 		ReservePrivateRefCountEntry();
 
 		buf_state = LockBufHdr(bufHdr);
-		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+		if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 			(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 		{
 			PinBuffer_Locked(bufHdr);
 			LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
-			FlushBuffer(bufHdr, rel->rd_smgr);
+			FlushBuffer(bufHdr, smgr);
 			LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
 			UnpinBuffer(bufHdr, true);
 		}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 54a40ef00b..b5baa430db 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -75,6 +75,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
+#include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/array.h"
@@ -412,6 +413,10 @@ AllocateRelationDesc(Form_pg_class relp)
 	/* which we mark as a reference-counted tupdesc */
 	relation->rd_att->tdrefcount = 1;
 
+	/* We don't know if pending sync for this relation exists so far */
+	relation->pending_sync = NULL;
+	relation->no_pending_sync = false;
+
 	MemoryContextSwitchTo(oldcxt);
 
 	return relation;
@@ -1813,6 +1818,10 @@ formrdesc(const char *relationName, Oid relationReltype,
 		relation->rd_rel->relhasindex = true;
 	}
 
+	/* We don't know if pending sync for this relation exists so far */
+	relation->pending_sync = NULL;
+	relation->no_pending_sync = false;
+
 	/*
 	 * add new reldesc to relcache
 	 */
@@ -3207,6 +3216,10 @@ RelationBuildLocalRelation(const char *relname,
 	else
 		rel->rd_rel->relfilenode = relfilenode;
 
+	/* newly built relation has no pending sync */
+	rel->no_pending_sync = true;
+	rel->pending_sync = NULL;
+
 	RelationInitLockInfo(rel);	/* see lmgr.c */
 
 	RelationInitPhysicalAddr(rel);
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index ab0879138f..fab5052868 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -163,6 +163,7 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
+extern void heap_register_sync(Relation relation);
 extern void heap_sync(Relation relation);
 extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
 
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9f638be924..95d7898e25 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,13 +22,16 @@ extern void RelationCreateStorage(RelFileNode rnode, char relpersistence);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
-
+extern void RelationRemovePendingSync(Relation rel);
 /*
  * These functions used to be in storage/smgr/smgr.c, which explains the
  * naming
  */
 extern void smgrDoPendingDeletes(bool isCommit);
 extern int	smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
+extern void smgrDoPendingSyncs(bool isCommit);
+extern void RecordPendingSync(Relation rel);
+bool BufferNeedsWAL(Relation rel, Buffer buf);
 extern void AtSubCommit_smgr(void);
 extern void AtSubAbort_smgr(void);
 extern void PostPrepare_smgr(void);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index c5826f691d..8a9ea041dd 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
 extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
+extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode,
+									bool islocal);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
 					   ForkNumber forkNum, BlockNumber firstDelBlock);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 1d05465303..0f39f209d3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -185,6 +185,14 @@ typedef struct RelationData
 
 	/* use "struct" here to avoid needing to include pgstat.h: */
 	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+	/*
+	 * no_pending_sync is true if this relation is known not to have pending
+	 * syncs.  Elsewise searching for registered sync is required if
+	 * pending_sync is NULL.
+	 */
+	bool				   no_pending_sync;
+	struct PendingRelSync *pending_sync;
 } RelationData;
 
 
-- 
2.16.3


----Next_Part(Mon_Mar_04_12_24_48_2019_788)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v7-0004-Fix-WAL-skipping-feature.patch"



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH 4/8] Add infrastructure to WAL-logging skip feature
@ 2019-03-26 06:34  Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Kyotaro Horiguchi @ 2019-03-26 06:34 UTC (permalink / raw)

We used to optimize WAL-logging for truncation of in-transaction
crated tables in minimal mode by just signaling by
HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can
emit WAL records that results in corrupt state for certain series of
in-transaction operations. This patch provides infrastructure to track
pending at-commit fsyncs for a relation and in-transaction
truncations. heap_register_sync() should be used to start tracking
before batch operations like COPY and CLUSTER, and use
BufferNeedsWAL() instead of RelationNeedsWAL() at the places related
to WAL-logging about heap-modifying operations.
---
 src/backend/access/heap/heapam.c    |  31 +++
 src/backend/access/transam/xact.c   |  11 +
 src/backend/catalog/storage.c       | 418 ++++++++++++++++++++++++++++++++++--
 src/backend/commands/tablecmds.c    |   4 +-
 src/backend/storage/buffer/bufmgr.c |  39 +++-
 src/backend/utils/cache/relcache.c  |   3 +
 src/include/access/heapam.h         |   1 +
 src/include/catalog/storage.h       |   8 +
 src/include/storage/bufmgr.h        |   2 +
 src/include/utils/rel.h             |   8 +
 10 files changed, 493 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index c6e71dba6b..5a8627507f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -51,6 +51,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
+#include "catalog/storage.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
@@ -8800,3 +8801,33 @@ heap_mask(char *pagedata, BlockNumber blkno)
 		}
 	}
 }
+
+/*
+ *	heap_register_sync	- register a heap to be synced to disk at commit
+ *
+ * This can be used to skip WAL-logging changes on a relation file that has
+ * been created in the same transaction. This makes note of the current size of
+ * the relation, and ensures that when the relation is extended, any changes
+ * to the new blocks in the heap, in the same transaction, will not be
+ * WAL-logged. Instead, the heap contents are flushed to disk at commit,
+ * like heap_sync() does.
+ *
+ * This does the same for the TOAST heap, if any. Indexes are not affected.
+ */
+void
+heap_register_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	RecordWALSkipping(rel);
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		RecordWALSkipping(toastrel);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c3214d4f4d..ad7cb3bcb9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2022,6 +2022,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2254,6 +2257,9 @@ PrepareTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrDoPendingSyncs(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2579,6 +2585,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	smgrDoPendingSyncs(false);	/* abandon pending syncs */
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4097,6 +4104,8 @@ ReleaseSavepoint(const char *name)
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
 				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
+	smgrProcessWALRequirementInval(s->subTransactionId, true);
+
 	/*
 	 * Mark "commit pending" all subtransactions up to the target
 	 * subtransaction.  The actual commits will happen when control gets to
@@ -4206,6 +4215,8 @@ RollbackToSavepoint(const char *name)
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
 				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
+	smgrProcessWALRequirementInval(s->subTransactionId, false);
+
 	/*
 	 * Mark "abort pending" all subtransactions up to the target
 	 * subtransaction.  The actual aborts will happen when control gets to
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 0302507e6f..be37174ef2 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,7 +27,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "storage/freespace.h"
-#include "storage/smgr.h"
+#include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -62,6 +62,58 @@ typedef struct PendingRelDelete
 
 static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
 
+/*
+ * We also track relation files (RelFileNode values) that have been created
+ * in the same transaction, and that have been modified without WAL-logging
+ * the action (an optimization possible with wal_level=minimal). When we are
+ * about to skip WAL-logging, a RelWalRequirement entry is created, and
+ * 'skip_wal_min_blk' is set to the current size of the relation. Any operations
+ * on blocks < skip_wal_min_blk need to be WAL-logged as usual, but for
+ * operations on higher blocks, WAL-logging is skipped.
+
+ *
+ * NB: after WAL-logging has been skipped for a block, we must not WAL-log
+ * any subsequent actions on the same block either. Replaying the WAL record
+ * of the subsequent action might fail otherwise, as the "before" state of
+ * the block might not match, as the earlier actions were not WAL-logged.
+ * Likewise, after we have WAL-logged an operation for a block, we must
+ * WAL-log any subsequent operations on the same page as well. Replaying
+ * a possible full-page-image from the earlier WAL record would otherwise
+ * revert the page to the old state, even if we sync the relation at end
+ * of transaction.
+ *
+ * If a relation is truncated (without creating a new relfilenode), and we
+ * emit a WAL record of the truncation, we can't skip WAL-logging for any
+ * of the truncated blocks anymore, as replaying the truncation record will
+ * destroy all the data inserted after that. But if we have already decided
+ * to skip WAL-logging changes to a relation, and the relation is truncated,
+ * we don't need to WAL-log the truncation either.
+ *
+ * This mechanism is currently only used by heaps. Indexes are always
+ * WAL-logged. Also, this only applies for wal_level=minimal; with higher
+ * WAL levels we need the WAL for PITR/replication anyway.
+ */
+typedef struct RelWalRequirement
+{
+	RelFileNode relnode;			/* relation created in same xact */
+	bool		forks[MAX_FORKNUM + 1];	/* target forknums */
+	BlockNumber skip_wal_min_blk;	/* WAL-logging skipped for blocks >=
+									 * skip_wal_min_blk */
+	BlockNumber wal_log_min_blk; 	/* The minimum blk number that requires
+									 * WAL-logging even if skipped by the
+									 * above*/
+	SubTransactionId create_sxid;	/* subxid where this entry is created */
+	SubTransactionId invalidate_sxid; /* subxid where this entry is
+									   * invalidated */
+}	RelWalRequirement;
+
+/* Relations that need to be fsync'd at commit */
+static HTAB *walRequirements = NULL;
+
+static RelWalRequirement *getWalRequirementEntry(Relation rel, bool create);
+static RelWalRequirement *getWalRequirementEntryRNode(RelFileNode *node,
+													  bool create);
+
 /*
  * RelationCreateStorage
  *		Create physical storage for a relation.
@@ -259,37 +311,290 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
 	 */
 	if (RelationNeedsWAL(rel))
 	{
-		/*
-		 * Make an XLOG entry reporting the file truncation.
-		 */
-		XLogRecPtr	lsn;
-		xl_smgr_truncate xlrec;
+		RelWalRequirement *walreq;
 
-		xlrec.blkno = nblocks;
-		xlrec.rnode = rel->rd_node;
-		xlrec.flags = SMGR_TRUNCATE_ALL;
+		/* get pending sync entry, create if not yet */
+		walreq = getWalRequirementEntry(rel, true);
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+		if (walreq->skip_wal_min_blk == InvalidBlockNumber ||
+			walreq->skip_wal_min_blk < nblocks)
+		{
+			/*
+			 * This is the first time truncation of this relation in this
+			 * transaction or truncation that leaves pages that need at-commit
+			 * fsync.  Make an XLOG entry reporting the file truncation.
+			 */
+			XLogRecPtr		lsn;
+			xl_smgr_truncate xlrec;
 
-		lsn = XLogInsert(RM_SMGR_ID,
-						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+			xlrec.blkno = nblocks;
+			xlrec.rnode = rel->rd_node;
+			xlrec.flags = SMGR_TRUNCATE_ALL;
 
-		/*
-		 * Flush, because otherwise the truncation of the main relation might
-		 * hit the disk before the WAL record, and the truncation of the FSM
-		 * or visibility map. If we crashed during that window, we'd be left
-		 * with a truncated heap, but the FSM or visibility map would still
-		 * contain entries for the non-existent heap pages.
-		 */
-		if (fsm || vm)
-			XLogFlush(lsn);
+			XLogBeginInsert();
+			XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+
+			lsn = XLogInsert(RM_SMGR_ID,
+							 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+
+			/*
+			 * Flush, because otherwise the truncation of the main relation
+			 * might hit the disk before the WAL record, and the truncation of
+			 * the FSM or visibility map. If we crashed during that window,
+			 * we'd be left with a truncated heap, but the FSM or visibility
+			 * map would still contain entries for the non-existent heap
+			 * pages.
+			 */
+			if (fsm || vm)
+				XLogFlush(lsn);
+
+			/* no longer skip WAL-logging for the blocks */
+			rel->rd_walrequirement->wal_log_min_blk = nblocks;
+		}
 	}
 
 	/* Do the real work */
 	smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks);
 }
 
+/*
+ * Do changes to given heap page need to be WAL-logged?
+ *
+ * This takes into account any previous RecordPendingSync() requests.
+ *
+ * Note that it is required to check this before creating any WAL records for
+ * heap pages - it is not merely an optimization! WAL-logging a record, when
+ * we have already skipped a previous WAL record for the same page could lead
+ * to failure at WAL replay, as the "before" state expected by the record
+ * might not match what's on disk. Also, if the heap was truncated earlier, we
+ * must WAL-log any changes to the once-truncated blocks, because replaying
+ * the truncation record will destroy them.
+ */
+bool
+BufferNeedsWAL(Relation rel, Buffer buf)
+{
+	BlockNumber		blkno = InvalidBlockNumber;
+	RelWalRequirement *walreq;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch existing pending sync entry */
+	walreq = getWalRequirementEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't have special
+	 * WAL requirement
+	 */
+	if (!walreq)
+		return true;
+
+	Assert(BufferIsValid(buf));
+
+	blkno = BufferGetBlockNumber(buf);
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walreq->skip_wal_min_blk == InvalidBlockNumber ||
+		walreq->skip_wal_min_blk > blkno)
+		return true;
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walreq->wal_log_min_blk != InvalidBlockNumber &&
+		walreq->wal_log_min_blk <= blkno)
+		return true;
+
+	return false;
+}
+
+bool
+BlockNeedsWAL(Relation rel, BlockNumber blkno)
+{
+	RelWalRequirement *walreq;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch exising pending sync entry */
+	walreq = getWalRequirementEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't have special
+	 * WAL requirement
+	 */
+	if (!walreq)
+		return true;
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walreq->skip_wal_min_blk == InvalidBlockNumber ||
+		walreq->skip_wal_min_blk > blkno)
+		return true;
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walreq->wal_log_min_blk != InvalidBlockNumber &&
+		walreq->wal_log_min_blk <= blkno)
+		return true;
+
+	return false;
+}
+
+/*
+ * Remember that the given relation doesn't need WAL-logging for the blocks
+ * after the current block size and fo the blocks that are going to be synced
+ * at commit.
+ */
+void
+RecordWALSkipping(Relation rel)
+{
+	RelWalRequirement *walreq;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* get pending sync entry, create if not yet  */
+	walreq = getWalRequirementEntry(rel, true);
+
+	/*
+	 *  Record only the first registration.
+	 */
+	if (walreq->skip_wal_min_blk != InvalidBlockNumber)
+		return;
+
+	walreq->skip_wal_min_blk = RelationGetNumberOfBlocks(rel);
+}
+
+/*
+ * Record commit-time file sync. This shouldn't be used mixing with
+ * RecordWALSkipping.
+ */
+void
+RecordPendingSync(SMgrRelation rel, ForkNumber forknum)
+{
+	RelWalRequirement *walreq;
+
+	walreq = getWalRequirementEntryRNode(&rel->smgr_rnode.node, true);
+	walreq->forks[forknum] = true;
+	walreq->skip_wal_min_blk = 0;
+}
+
+/*
+ * RelationInvalidateWALRequirements() -- invalidate wal requirement entry
+ */
+void
+RelationInvalidateWALRequirements(Relation rel)
+{
+	RelWalRequirement *walreq;
+
+	/* we know we don't have one */
+	if (rel->rd_nowalrequirement)
+		return;
+
+	walreq = getWalRequirementEntry(rel, false);
+	
+	if (!walreq)
+		return;
+
+	/*
+	 * The state is reset at subtransaction commit/abort. No invalidation
+	 * request must not come for the same relation in the same subtransaction.
+	 */
+	Assert(walreq->invalidate_sxid == InvalidSubTransactionId);
+
+	walreq->invalidate_sxid = GetCurrentSubTransactionId();
+}
+
+/*
+ * getWalRequirementEntry: get WAL requirement entry.
+ *
+ * Returns WAL requirement entry for the relation. The entry tracks
+ * WAL-skipping blocks for the relation.  The WAL-skipped blocks need fsync at
+ * commit time.  Creates one if needed when create is true.
+ */
+static RelWalRequirement *
+getWalRequirementEntry(Relation rel, bool create)
+{
+	RelWalRequirement *walreq_entry = NULL;
+
+	if (rel->rd_walrequirement)
+		return rel->rd_walrequirement;
+
+	/* we know we don't have pending sync entry */
+	if (!create && rel->rd_nowalrequirement)
+		return NULL;
+
+	walreq_entry = getWalRequirementEntryRNode(&rel->rd_node, create);
+
+	if (!walreq_entry)
+	{
+		/* prevent further hash lookup */
+		rel->rd_nowalrequirement = true;
+		return NULL;
+	}
+
+	walreq_entry->forks[MAIN_FORKNUM] = true;
+
+	/* hold shortcut in Relation */
+	rel->rd_nowalrequirement = false;
+	rel->rd_walrequirement = walreq_entry;
+
+	return walreq_entry;
+}
+
+/*
+ * getWalRequirementEntryRNode: get WAL requirement entry by rnode
+ *
+ * Returns WAL requirement entry for the RelFileNode.
+ */
+static RelWalRequirement *
+getWalRequirementEntryRNode(RelFileNode *rnode, bool create)
+{
+	RelWalRequirement *walreq_entry = NULL;
+	bool			found;
+
+	if (!walRequirements)
+	{
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		if (!create)
+			return NULL;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(RelFileNode);
+		ctl.entrysize = sizeof(RelWalRequirement);
+		ctl.hash = tag_hash;
+		walRequirements = hash_create("pending relation sync table", 5,
+								   &ctl, HASH_ELEM | HASH_FUNCTION);
+	}
+
+	walreq_entry = (RelWalRequirement *)
+		hash_search(walRequirements, (void *) rnode,
+					create ? HASH_ENTER: HASH_FIND,	&found);
+
+	if (!walreq_entry)
+		return NULL;
+
+	/* new entry created */
+	if (!found)
+	{
+		memset(&walreq_entry->forks, 0, sizeof(sizeof(walreq_entry->forks)));
+		walreq_entry->wal_log_min_blk = InvalidBlockNumber;
+		walreq_entry->skip_wal_min_blk = InvalidBlockNumber;
+		walreq_entry->create_sxid = GetCurrentSubTransactionId();
+		walreq_entry->invalidate_sxid = InvalidSubTransactionId;
+	}
+
+	return walreq_entry;
+}
+
 /*
  *	smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
  *
@@ -418,6 +723,75 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
 	return nrels;
 }
 
+/*
+ * Sync to disk any relations that we have skipped WAL-logging earlier.
+ */
+void
+smgrDoPendingSyncs(bool isCommit)
+{
+	if (!walRequirements)
+		return;
+
+	if (isCommit)
+	{
+		HASH_SEQ_STATUS status;
+		RelWalRequirement *walreq;
+
+		hash_seq_init(&status, walRequirements);
+
+		while ((walreq = hash_seq_search(&status)) != NULL)
+		{
+			if (walreq->skip_wal_min_blk != InvalidBlockNumber &&
+				walreq->invalidate_sxid == InvalidSubTransactionId)
+			{
+				int f;
+
+				FlushRelationBuffersWithoutRelCache(walreq->relnode, false);
+
+				/* flush all requested forks  */
+				for (f = MAIN_FORKNUM ; f <= MAX_FORKNUM ; f++)
+				{
+					if (walreq->forks[f])
+						smgrimmedsync(smgropen(walreq->relnode,
+											   InvalidBackendId), f);
+				}
+			}
+		}
+	}
+
+	hash_destroy(walRequirements);
+	walRequirements = NULL;
+}
+
+/*
+ * Process pending invalidation of WAL requirements happened in the
+ * subtransaction
+ */
+void
+smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit)
+{
+	HASH_SEQ_STATUS status;
+	RelWalRequirement *walreq;
+
+	if (!walRequirements)
+		return;
+
+	/* We expect that we don't have walRequirements in almost all cases */
+	hash_seq_init(&status, walRequirements);
+
+	while ((walreq = hash_seq_search(&status)) != NULL)
+	{
+		/* remove useless entry */
+		if (isCommit ?
+			walreq->invalidate_sxid == sxid :
+			walreq->create_sxid == sxid)
+			hash_search(walRequirements, &walreq->relnode, HASH_REMOVE, NULL);
+		/* or cancel invalidation  */
+		else if (!isCommit && walreq->invalidate_sxid == sxid)
+			walreq->invalidate_sxid = InvalidSubTransactionId;
+	}
+}
+
 /*
  *	PostPrepare_smgr -- Clean up after a successful PREPARE
  *
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3183b2aaa1..c9a0e02168 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11587,11 +11587,13 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
 
 	/*
 	 * Create and copy all forks of the relation, and schedule unlinking of
-	 * old physical files.
+	 * old physical files. WAL requirements for the old node is no longer
+	 * needed.
 	 *
 	 * NOTE: any conflict in relfilenode value will be caught in
 	 * RelationCreateStorage().
 	 */
+	RelationInvalidateWALRequirements(rel);
 	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
 
 	/* copy main fork */
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 273e2f385f..f00826712a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr,
 			BufferAccessStrategy strategy,
 			bool *foundPtr);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln);
+static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal);
 static void AtProcExit_Buffers(int code, Datum arg);
 static void CheckForBufferLeaks(void);
 static int	rnode_comparator(const void *p1, const void *p2);
@@ -3153,20 +3154,40 @@ PrintPinnedBufs(void)
 void
 FlushRelationBuffers(Relation rel)
 {
-	int			i;
-	BufferDesc *bufHdr;
-
 	/* Open rel at the smgr level if not already done */
 	RelationOpenSmgr(rel);
 
-	if (RelationUsesLocalBuffers(rel))
+	FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel));
+}
+
+/*
+ * Like FlushRelationBuffers(), but the relation is specified by RelFileNode
+ */
+void
+FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal)
+{
+	FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal);
+}
+
+/*
+ * Code shared between functions FlushRelationBuffers() and
+ * FlushRelationBuffersWithoutRelCache().
+ */
+static void
+FlushRelationBuffers_common(SMgrRelation smgr, bool islocal)
+{
+	RelFileNode rnode = smgr->smgr_rnode.node;
+	int			i;
+	BufferDesc *bufHdr;
+
+	if (islocal)
 	{
 		for (i = 0; i < NLocBuffer; i++)
 		{
 			uint32		buf_state;
 
 			bufHdr = GetLocalBufferDescriptor(i);
-			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+			if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 				((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
 				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 			{
@@ -3183,7 +3204,7 @@ FlushRelationBuffers(Relation rel)
 
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
-				smgrwrite(rel->rd_smgr,
+				smgrwrite(smgr,
 						  bufHdr->tag.forkNum,
 						  bufHdr->tag.blockNum,
 						  localpage,
@@ -3213,18 +3234,18 @@ FlushRelationBuffers(Relation rel)
 		 * As in DropRelFileNodeBuffers, an unlocked precheck should be safe
 		 * and saves some cycles.
 		 */
-		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+		if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
 			continue;
 
 		ReservePrivateRefCountEntry();
 
 		buf_state = LockBufHdr(bufHdr);
-		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+		if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 			(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 		{
 			PinBuffer_Locked(bufHdr);
 			LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
-			FlushBuffer(bufHdr, rel->rd_smgr);
+			FlushBuffer(bufHdr, smgr);
 			LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
 			UnpinBuffer(bufHdr, true);
 		}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 84609e0725..95e834d45e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -75,6 +75,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
+#include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/array.h"
@@ -5625,6 +5626,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
 		MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
+		rel->rd_nowalrequirement = false;
+		rel->rd_walrequirement = NULL;
 
 		/*
 		 * Recompute lock and physical addressing info.  This is needed in
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 3773a4df85..3d4fb7f3c3 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -172,6 +172,7 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
+extern void heap_register_sync(Relation relation);
 extern void heap_sync(Relation relation);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 9f638be924..9034465001 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -16,12 +16,18 @@
 
 #include "storage/block.h"
 #include "storage/relfilenode.h"
+#include "storage/smgr.h"
 #include "utils/relcache.h"
 
 extern void RelationCreateStorage(RelFileNode rnode, char relpersistence);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
+extern bool BufferNeedsWAL(Relation rel, Buffer buf);
+extern bool BlockNeedsWAL(Relation rel, BlockNumber blkno);
+extern void RecordWALSkipping(Relation rel);
+extern void RecordPendingSync(SMgrRelation rel, ForkNumber forknum);
+extern void RelationInvalidateWALRequirements(Relation rel);
 
 /*
  * These functions used to be in storage/smgr/smgr.c, which explains the
@@ -29,6 +35,8 @@ extern void RelationTruncate(Relation rel, BlockNumber nblocks);
  */
 extern void smgrDoPendingDeletes(bool isCommit);
 extern int	smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
+extern void smgrDoPendingSyncs(bool isCommit);
+extern void smgrProcessWALRequirementInval(SubTransactionId sxid, bool isCommit);
 extern void AtSubCommit_smgr(void);
 extern void AtSubAbort_smgr(void);
 extern void PostPrepare_smgr(void);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index c5826f691d..8a9ea041dd 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
 extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
+extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode,
+									bool islocal);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
 					   ForkNumber forkNum, BlockNumber firstDelBlock);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 54028515a7..30f0d5bd83 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -198,6 +198,14 @@ typedef struct RelationData
 
 	/* use "struct" here to avoid needing to include pgstat.h: */
 	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+	/*
+	 * rd_nowalrequirement is true if this relation is known not to have
+	 * special WAL requirements.  Otherwise we need to ask smgr for an entry
+	 * if rd_walrequirement is NULL.
+	 */
+	bool						rd_nowalrequirement;
+	struct RelWalRequirement   *rd_walrequirement;
 } RelationData;
 
 
-- 
2.16.3


----Next_Part(Tue_Mar_26_16_35_07_2019_128)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v9-0005-Fix-WAL-skipping-feature.patch"



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH 5/7] Add infrastructure to WAL-logging skip feature
@ 2019-04-02 09:05  Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Kyotaro Horiguchi @ 2019-04-02 09:05 UTC (permalink / raw)

We used to optimize WAL-logging for truncation of in-transaction
crated tables in minimal mode by just signaling by
HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can
emit WAL records that results in corrupt state for certain series of
in-transaction operations. This patch provides infrastructure to track
pending at-commit fsyncs for a relation and in-transaction
truncations. table_relation_register_walskip() should be used to start
tracking before batch operations like COPY and CLUSTER, and use
BufferNeedsWAL() instead of RelationNeedsWAL() at the places related
to WAL-logging about heap-modifying operations, then remove
call to table_finish_bulk_insert() and the tableam intaface.
---
 src/backend/access/transam/xact.c   |  12 +-
 src/backend/catalog/storage.c       | 612 +++++++++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c    |   6 +-
 src/backend/storage/buffer/bufmgr.c |  39 ++-
 src/backend/utils/cache/relcache.c  |   3 +
 src/include/catalog/storage.h       |  17 +-
 src/include/storage/bufmgr.h        |   2 +
 src/include/utils/rel.h             |   7 +
 8 files changed, 631 insertions(+), 67 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e9ed92b70b..33a83dc784 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2102,6 +2102,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrFinishBulkInsert(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2334,6 +2337,9 @@ PrepareTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrFinishBulkInsert(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2659,6 +2665,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	smgrFinishBulkInsert(false);	/* abandon pending syncs */
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4792,8 +4799,7 @@ CommitSubTransaction(void)
 	AtEOSubXact_RelationCache(true, s->subTransactionId,
 							  s->parent->subTransactionId);
 	AtEOSubXact_Inval(true);
-	AtSubCommit_smgr();
-
+	AtSubCommit_smgr(s->subTransactionId, s->parent->subTransactionId);
 	/*
 	 * The only lock we actually release here is the subtransaction XID lock.
 	 */
@@ -4970,7 +4976,7 @@ AbortSubTransaction(void)
 		ResourceOwnerRelease(s->curTransactionOwner,
 							 RESOURCE_RELEASE_AFTER_LOCKS,
 							 false, false);
-		AtSubAbort_smgr();
+		AtSubAbort_smgr(s->subTransactionId, s->parent->subTransactionId);
 
 		AtEOXact_GUC(false, s->gucNestLevel);
 		AtEOSubXact_SPI(false, s->subTransactionId);
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 72242b2476..4cd112f86c 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -21,6 +21,7 @@
 
 #include "miscadmin.h"
 
+#include "access/tableam.h"
 #include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -29,10 +30,18 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "storage/freespace.h"
-#include "storage/smgr.h"
+#include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
+ /* #define STORAGEDEBUG */	/* turns DEBUG elogs on */
+
+#ifdef STORAGEDEBUG
+#define STORAGE_elog(...)				elog(__VA_ARGS__)
+#else
+#define STORAGE_elog(...)
+#endif
+
 /*
  * We keep a list of all relations (represented as RelFileNode values)
  * that have been created or deleted in the current transaction.  When
@@ -64,6 +73,61 @@ typedef struct PendingRelDelete
 
 static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
 
+/*
+ * We also track relation files (RelFileNode values) that have been created
+ * in the same transaction, and that have been modified without WAL-logging
+ * the action (an optimization possible with wal_level=minimal). When we are
+ * about to skip WAL-logging, a RelWalSkip entry is created, and
+ * 'skip_wal_min_blk' is set to the current size of the relation. Any
+ * operations on blocks < skip_wal_min_blk need to be WAL-logged as usual, but
+ * for operations on higher blocks, WAL-logging is skipped.
+
+ *
+ * NB: after WAL-logging has been skipped for a block, we must not WAL-log
+ * any subsequent actions on the same block either. Replaying the WAL record
+ * of the subsequent action might fail otherwise, as the "before" state of
+ * the block might not match, as the earlier actions were not WAL-logged.
+ * Likewise, after we have WAL-logged an operation for a block, we must
+ * WAL-log any subsequent operations on the same page as well. Replaying
+ * a possible full-page-image from the earlier WAL record would otherwise
+ * revert the page to the old state, even if we sync the relation at end
+ * of transaction.
+ *
+ * If a relation is truncated (without creating a new relfilenode), and we
+ * emit a WAL record of the truncation, we can't skip WAL-logging for any
+ * of the truncated blocks anymore, as replaying the truncation record will
+ * destroy all the data inserted after that. But if we have already decided
+ * to skip WAL-logging changes to a relation, and the relation is truncated,
+ * we don't need to WAL-log the truncation either.
+ *
+ * This mechanism is currently only used by heaps. Indexes are always
+ * WAL-logged. Also, this only applies for wal_level=minimal; with higher
+ * WAL levels we need the WAL for PITR/replication anyway.
+ */
+typedef struct RelWalSkip
+{
+	RelFileNode relnode;			/* relation created in same xact */
+	bool		forks[MAX_FORKNUM + 1];	/* target forknums */
+	BlockNumber skip_wal_min_blk;	/* WAL-logging skipped for blocks >=
+									 * skip_wal_min_blk */
+	BlockNumber wal_log_min_blk; 	/* The minimum blk number that requires
+									 * WAL-logging even if skipped by the
+									 * above*/
+	SubTransactionId create_sxid;	/* subxid where this entry is created */
+	SubTransactionId invalidate_sxid; /* subxid where this entry is
+									   * invalidated */
+	const TableAmRoutine *tableam;	/* Table access routine */
+}	RelWalSkip;
+
+/* Relations that need to be fsync'd at commit */
+static HTAB *walSkipHash = NULL;
+
+static RelWalSkip *getWalSkipEntry(Relation rel, bool create);
+static RelWalSkip *getWalSkipEntryRNode(RelFileNode *node,
+													  bool create);
+static void smgrProcessWALSkipInval(bool isCommit, SubTransactionId mySubid,
+						SubTransactionId parentSubid);
+
 /*
  * RelationCreateStorage
  *		Create physical storage for a relation.
@@ -261,31 +325,59 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
 	 */
 	if (RelationNeedsWAL(rel))
 	{
-		/*
-		 * Make an XLOG entry reporting the file truncation.
-		 */
-		XLogRecPtr	lsn;
-		xl_smgr_truncate xlrec;
+		RelWalSkip *walskip;
 
-		xlrec.blkno = nblocks;
-		xlrec.rnode = rel->rd_node;
-		xlrec.flags = SMGR_TRUNCATE_ALL;
-
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, sizeof(xlrec));
-
-		lsn = XLogInsert(RM_SMGR_ID,
-						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+		/* get pending sync entry, create if not yet */
+		walskip = getWalSkipEntry(rel, true);
 
 		/*
-		 * Flush, because otherwise the truncation of the main relation might
-		 * hit the disk before the WAL record, and the truncation of the FSM
-		 * or visibility map. If we crashed during that window, we'd be left
-		 * with a truncated heap, but the FSM or visibility map would still
-		 * contain entries for the non-existent heap pages.
+		 * walskip is null here if rel doesn't support WAL-logging skip,
+		 * otherwise check for WAL-skipping status.
 		 */
-		if (fsm || vm)
-			XLogFlush(lsn);
+		if (walskip == NULL ||
+			walskip->skip_wal_min_blk == InvalidBlockNumber ||
+			walskip->skip_wal_min_blk < nblocks)
+		{
+			/*
+			 * If WAL-skipping is enabled, this is the first time truncation
+			 * of this relation in this transaction or truncation that leaves
+			 * pages that need at-commit fsync.  Make an XLOG entry reporting
+			 * the file truncation.
+			 */
+			XLogRecPtr		lsn;
+			xl_smgr_truncate xlrec;
+
+			xlrec.blkno = nblocks;
+			xlrec.rnode = rel->rd_node;
+			xlrec.flags = SMGR_TRUNCATE_ALL;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+
+			lsn = XLogInsert(RM_SMGR_ID,
+							 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+
+			STORAGE_elog(DEBUG2,
+						 "WAL-logged truncation of rel %u/%u/%u to %u blocks",
+						 rel->rd_node.spcNode, rel->rd_node.dbNode,
+						 rel->rd_node.relNode, nblocks);
+			/*
+			 * Flush, because otherwise the truncation of the main relation
+			 * might hit the disk before the WAL record, and the truncation of
+			 * the FSM or visibility map. If we crashed during that window,
+			 * we'd be left with a truncated heap, but the FSM or visibility
+			 * map would still contain entries for the non-existent heap
+			 * pages.
+			 */
+			if (fsm || vm)
+				XLogFlush(lsn);
+
+			if (walskip)
+			{
+				/* no longer skip WAL-logging for the blocks */
+				walskip->wal_log_min_blk = nblocks;
+			}
+		}
 	}
 
 	/* Do the real work */
@@ -296,8 +388,7 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
  * Copy a fork's data, block by block.
  */
 void
-RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-					ForkNumber forkNum, char relpersistence)
+RelationCopyStorage(Relation srcrel, SMgrRelation dst, ForkNumber forkNum)
 {
 	PGAlignedBlock buf;
 	Page		page;
@@ -305,6 +396,8 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 	bool		copying_initfork;
 	BlockNumber nblocks;
 	BlockNumber blkno;
+	SMgrRelation src = srcrel->rd_smgr;
+	char 		relpersistence = srcrel->rd_rel->relpersistence;
 
 	page = (Page) buf.data;
 
@@ -316,12 +409,33 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 	copying_initfork = relpersistence == RELPERSISTENCE_UNLOGGED &&
 		forkNum == INIT_FORKNUM;
 
-	/*
-	 * We need to log the copied data in WAL iff WAL archiving/streaming is
-	 * enabled AND it's a permanent relation.
-	 */
-	use_wal = XLogIsNeeded() &&
-		(relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork);
+	if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork)
+	{
+		/*
+		 * We need to log the copied data in WAL iff WAL archiving/streaming
+		 * is enabled AND it's a permanent relation.
+		 */
+		if (XLogIsNeeded())
+			use_wal = true;
+
+		/*
+		 * If the rel is WAL-logged, must fsync before commit.  We use
+		 * heap_sync to ensure that the toast table gets fsync'd too.  (For a
+		 * temp or unlogged rel we don't care since the data will be gone
+		 * after a crash anyway.)
+		 *
+		 * It's obvious that we must do this when not WAL-logging the
+		 * copy. It's less obvious that we have to do it even if we did
+		 * WAL-log the copied pages. The reason is that since we're copying
+		 * outside shared buffers, a CHECKPOINT occurring during the copy has
+		 * no way to flush the previously written data to disk (indeed it
+		 * won't know the new rel even exists).  A crash later on would replay
+		 * WAL from the checkpoint, therefore it wouldn't replay our earlier
+		 * WAL entries. If we do not fsync those pages here, they might still
+		 * not be on disk when the crash occurs.
+		 */
+		RecordPendingSync(srcrel, dst, forkNum);
+	}
 
 	nblocks = smgrnblocks(src, forkNum);
 
@@ -358,24 +472,321 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 		 */
 		smgrextend(dst, forkNum, blkno, buf.data, true);
 	}
+}
+
+/*
+ * Do changes to given heap page need to be WAL-logged?
+ *
+ * This takes into account any previous RecordPendingSync() requests.
+ *
+ * Note that it is required to check this before creating any WAL records for
+ * heap pages - it is not merely an optimization! WAL-logging a record, when
+ * we have already skipped a previous WAL record for the same page could lead
+ * to failure at WAL replay, as the "before" state expected by the record
+ * might not match what's on disk. Also, if the heap was truncated earlier, we
+ * must WAL-log any changes to the once-truncated blocks, because replaying
+ * the truncation record will destroy them.
+ */
+bool
+BufferNeedsWAL(Relation rel, Buffer buf)
+{
+	BlockNumber		blkno = InvalidBlockNumber;
+	RelWalSkip *walskip;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch existing pending sync entry */
+	walskip = getWalSkipEntry(rel, false);
 
 	/*
-	 * If the rel is WAL-logged, must fsync before commit.  We use heap_sync
-	 * to ensure that the toast table gets fsync'd too.  (For a temp or
-	 * unlogged rel we don't care since the data will be gone after a crash
-	 * anyway.)
-	 *
-	 * It's obvious that we must do this when not WAL-logging the copy. It's
-	 * less obvious that we have to do it even if we did WAL-log the copied
-	 * pages. The reason is that since we're copying outside shared buffers, a
-	 * CHECKPOINT occurring during the copy has no way to flush the previously
-	 * written data to disk (indeed it won't know the new rel even exists).  A
-	 * crash later on would replay WAL from the checkpoint, therefore it
-	 * wouldn't replay our earlier WAL entries. If we do not fsync those pages
-	 * here, they might still not be on disk when the crash occurs.
+	 * no point in doing further work if we know that we don't skip
+	 * WAL-logging.
 	 */
-	if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork)
-		smgrimmedsync(dst, forkNum);
+	if (!walskip)
+	{
+		STORAGE_elog(DEBUG2,
+					 "not skipping WAL-logging for rel %u/%u/%u block %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, BufferGetBlockNumber(buf));
+		return true;
+	}
+
+	Assert(BufferIsValid(buf));
+
+	blkno = BufferGetBlockNumber(buf);
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walskip->skip_wal_min_blk == InvalidBlockNumber ||
+		walskip->skip_wal_min_blk > blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because skip_wal_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->skip_wal_min_blk);
+		return true;
+	}
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walskip->wal_log_min_blk != InvalidBlockNumber &&
+		walskip->wal_log_min_blk <= blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because wal_log_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->wal_log_min_blk);
+		return true;
+	}
+
+	STORAGE_elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, blkno);
+
+	return false;
+}
+
+bool
+BlockNeedsWAL(Relation rel, BlockNumber blkno)
+{
+	RelWalSkip *walskip;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch exising pending sync entry */
+	walskip = getWalSkipEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't skip
+	 * WAL-logging.
+	 */
+	if (!walskip)
+		return true;
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walskip->skip_wal_min_blk == InvalidBlockNumber ||
+		walskip->skip_wal_min_blk > blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because skip_wal_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->skip_wal_min_blk);
+		return true;
+	}
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walskip->wal_log_min_blk != InvalidBlockNumber &&
+		walskip->wal_log_min_blk <= blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because wal_log_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->wal_log_min_blk);
+
+		return true;
+	}
+
+	STORAGE_elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, blkno);
+
+	return false;
+}
+
+/*
+ * Remember that the given relation doesn't need WAL-logging for the blocks
+ * after the current block size and for the blocks that are going to be synced
+ * at commit.
+ */
+void
+RecordWALSkipping(Relation rel)
+{
+	RelWalSkip *walskip;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* get pending sync entry, create if not yet  */
+	walskip = getWalSkipEntry(rel, true);
+
+	if (walskip == NULL)
+		return;
+
+	/*
+	 *  Record only the first registration.
+	 */
+	if (walskip->skip_wal_min_blk != InvalidBlockNumber)
+	{
+		STORAGE_elog(DEBUG2, "WAL skipping for rel %u/%u/%u was already registered at block %u (new %u)",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, walskip->skip_wal_min_blk,
+					 RelationGetNumberOfBlocks(rel));
+		return;
+	}
+
+	STORAGE_elog(DEBUG2, "registering new WAL skipping rel %u/%u/%u at block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, RelationGetNumberOfBlocks(rel));
+
+	walskip->skip_wal_min_blk = RelationGetNumberOfBlocks(rel);
+}
+
+/*
+ * Record commit-time file sync. This shouldn't be used mixing with
+ * RecordWALSkipping.
+ */
+void
+RecordPendingSync(Relation rel, SMgrRelation targetsrel, ForkNumber forknum)
+{
+	RelWalSkip *walskip;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* check for support for this feature */
+	if (rel->rd_tableam == NULL ||
+		rel->rd_tableam->relation_register_walskip == NULL)
+		return;
+
+	walskip = getWalSkipEntryRNode(&targetsrel->smgr_rnode.node, true);
+	walskip->forks[forknum] = true;
+	walskip->skip_wal_min_blk = 0;
+	walskip->tableam = rel->rd_tableam;
+
+	STORAGE_elog(DEBUG2,
+				 "registering new pending sync for rel %u/%u/%u at block %u",
+				 walskip->relnode.spcNode, walskip->relnode.dbNode,
+				 walskip->relnode.relNode, 0);
+}
+
+/*
+ * RelationInvalidateWALSkip() -- invalidate WAL-skip entry
+ */
+void
+RelationInvalidateWALSkip(Relation rel)
+{
+	RelWalSkip *walskip;
+
+	/* we know we don't have one */
+	if (rel->rd_nowalskip)
+		return;
+
+	walskip = getWalSkipEntry(rel, false);
+
+	if (!walskip)
+		return;
+
+	/*
+	 * The state is reset at subtransaction commit/abort. No invalidation
+	 * request must not come for the same relation in the same subtransaction.
+	 */
+	Assert(walskip->invalidate_sxid == InvalidSubTransactionId);
+
+	walskip->invalidate_sxid = GetCurrentSubTransactionId();
+
+	STORAGE_elog(DEBUG2,
+				 "WAL skip of rel %u/%u/%u invalidated by sxid %d",
+				 walskip->relnode.spcNode, walskip->relnode.dbNode,
+				 walskip->relnode.relNode, walskip->invalidate_sxid);
+}
+
+/*
+ * getWalSkipEntry: get WAL skip entry.
+ *
+ * Returns WAL skip entry for the relation. The entry tracks WAL-skipping
+ * blocks for the relation.  The WAL-skipped blocks need fsync at commit time.
+ * Creates one if needed when create is true. If rel doesn't support this
+ * feature, returns true even if create is true.
+ */
+static inline RelWalSkip *
+getWalSkipEntry(Relation rel, bool create)
+{
+	RelWalSkip *walskip_entry = NULL;
+
+	if (rel->rd_walskip)
+		return rel->rd_walskip;
+
+	/* we know we don't have pending sync entry */
+	if (!create && rel->rd_nowalskip)
+		return NULL;
+
+	/* check for support for this feature */
+	if (rel->rd_tableam == NULL ||
+		rel->rd_tableam->relation_register_walskip == NULL)
+	{
+		rel->rd_nowalskip = true;
+		return NULL;
+	}
+
+	walskip_entry = getWalSkipEntryRNode(&rel->rd_node, create);
+
+	if (!walskip_entry)
+	{
+		/* prevent further hash lookup */
+		rel->rd_nowalskip = true;
+		return NULL;
+	}
+
+	walskip_entry->forks[MAIN_FORKNUM] = true;
+	walskip_entry->tableam = rel->rd_tableam;
+
+	/* hold shortcut in Relation */
+	rel->rd_nowalskip = false;
+	rel->rd_walskip = walskip_entry;
+
+	return walskip_entry;
+}
+
+/*
+ * getWalSkipEntryRNode: get WAL skip entry by rnode
+ *
+ * Returns a WAL skip entry for the RelFileNode.
+ */
+static RelWalSkip *
+getWalSkipEntryRNode(RelFileNode *rnode, bool create)
+{
+	RelWalSkip *walskip_entry = NULL;
+	bool			found;
+
+	if (!walSkipHash)
+	{
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		if (!create)
+			return NULL;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(RelFileNode);
+		ctl.entrysize = sizeof(RelWalSkip);
+		ctl.hash = tag_hash;
+		walSkipHash = hash_create("pending relation sync table", 5,
+								   &ctl, HASH_ELEM | HASH_FUNCTION);
+	}
+
+	walskip_entry = (RelWalSkip *)
+		hash_search(walSkipHash, (void *) rnode,
+					create ? HASH_ENTER: HASH_FIND,	&found);
+
+	if (!walskip_entry)
+		return NULL;
+
+	/* new entry created */
+	if (!found)
+	{
+		memset(&walskip_entry->forks, 0, sizeof(walskip_entry->forks));
+		walskip_entry->wal_log_min_blk = InvalidBlockNumber;
+		walskip_entry->skip_wal_min_blk = InvalidBlockNumber;
+		walskip_entry->create_sxid = GetCurrentSubTransactionId();
+		walskip_entry->invalidate_sxid = InvalidSubTransactionId;
+		walskip_entry->tableam = NULL;
+	}
+
+	return walskip_entry;
 }
 
 /*
@@ -506,6 +917,107 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
 	return nrels;
 }
 
+/*
+ * Finish bulk insert of files.
+ */
+void
+smgrFinishBulkInsert(bool isCommit)
+{
+	if (!walSkipHash)
+		return;
+
+	if (isCommit)
+	{
+		HASH_SEQ_STATUS status;
+		RelWalSkip *walskip;
+
+		hash_seq_init(&status, walSkipHash);
+
+		while ((walskip = hash_seq_search(&status)) != NULL)
+		{
+			/*
+			 * On commit, process valid entreis. Rollback doesn't need sync on
+			 * all changes during the transaction.
+			 */
+			if (walskip->skip_wal_min_blk != InvalidBlockNumber &&
+				walskip->invalidate_sxid == InvalidSubTransactionId)
+			{
+				int f;
+
+				FlushRelationBuffersWithoutRelCache(walskip->relnode, false);
+
+				/*
+				 * We mustn't create an entry when the table AM doesn't
+				 * support WAL-skipping.
+				 */
+				Assert (walskip->tableam->finish_bulk_insert);
+
+				/* flush all requested forks  */
+				for (f = MAIN_FORKNUM ; f <= MAX_FORKNUM ; f++)
+				{
+					if (walskip->forks[f])
+					{
+						walskip->tableam->finish_bulk_insert(walskip->relnode, f);
+						STORAGE_elog(DEBUG2, "finishing bulk insert to rel %u/%u/%u fork %d",
+									 walskip->relnode.spcNode,
+									 walskip->relnode.dbNode,
+									 walskip->relnode.relNode, f);
+					}
+				}
+			}
+		}
+	}
+
+	hash_destroy(walSkipHash);
+	walSkipHash = NULL;
+}
+
+/*
+ * Process pending invalidation of WAL skip happened in the subtransaction
+ */
+void
+smgrProcessWALSkipInval(bool isCommit, SubTransactionId mySubid,
+						SubTransactionId parentSubid)
+{
+	HASH_SEQ_STATUS status;
+	RelWalSkip *walskip;
+
+	if (!walSkipHash)
+		return;
+
+	/* We expect that we don't have walSkipHash in almost all cases */
+	hash_seq_init(&status, walSkipHash);
+
+	while ((walskip = hash_seq_search(&status)) != NULL)
+	{
+		if (walskip->create_sxid == mySubid)
+		{
+			/*
+			 * The entry was created in this subxact. Remove it on abort, or
+			 * on commit after invalidation.
+			 */
+			if (!isCommit || walskip->invalidate_sxid == mySubid)
+				hash_search(walSkipHash, &walskip->relnode,
+							HASH_REMOVE, NULL);
+			/* Treat committing valid entry as creation by the parent. */
+			else if (walskip->invalidate_sxid == InvalidSubTransactionId)
+				walskip->create_sxid = parentSubid;
+		}
+		else if (walskip->invalidate_sxid == mySubid)
+		{
+			/*
+			 * This entry was created elsewhere then invalidated by this
+			 * subxact. Treat commit as invalidation by the parent. Otherwise
+			 * cancel invalidation.
+			 */
+			if (isCommit)
+				walskip->invalidate_sxid = parentSubid;
+			else
+				walskip->invalidate_sxid = InvalidSubTransactionId;
+		}
+	}
+}
+
 /*
  *	PostPrepare_smgr -- Clean up after a successful PREPARE
  *
@@ -535,7 +1047,7 @@ PostPrepare_smgr(void)
  * Reassign all items in the pending-deletes list to the parent transaction.
  */
 void
-AtSubCommit_smgr(void)
+AtSubCommit_smgr(SubTransactionId mySubid, SubTransactionId parentSubid)
 {
 	int			nestLevel = GetCurrentTransactionNestLevel();
 	PendingRelDelete *pending;
@@ -545,6 +1057,9 @@ AtSubCommit_smgr(void)
 		if (pending->nestLevel >= nestLevel)
 			pending->nestLevel = nestLevel - 1;
 	}
+
+	/* Remove invalidated WAL skip in this subtransaction */
+	smgrProcessWALSkipInval(true, mySubid, parentSubid);
 }
 
 /*
@@ -555,9 +1070,12 @@ AtSubCommit_smgr(void)
  * subtransaction will not commit.
  */
 void
-AtSubAbort_smgr(void)
+AtSubAbort_smgr(SubTransactionId mySubid, SubTransactionId parentSubid)
 {
 	smgrDoPendingDeletes(false);
+
+	/* Remove invalidated WAL skip in this subtransaction */
+	smgrProcessWALSkipInval(false, mySubid, parentSubid);
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 654179297c..8908b77d98 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11983,8 +11983,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
 
 	/* copy main fork */
-	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
-						rel->rd_rel->relpersistence);
+	RelationCopyStorage(rel, dstrel, MAIN_FORKNUM);
 
 	/* copy those extra forks that exist */
 	for (ForkNumber forkNum = MAIN_FORKNUM + 1;
@@ -12002,8 +12001,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 				(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(&newrnode, forkNum);
-			RelationCopyStorage(rel->rd_smgr, dstrel, forkNum,
-								rel->rd_rel->relpersistence);
+			RelationCopyStorage(rel, dstrel, forkNum);
 		}
 	}
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 273e2f385f..f00826712a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr,
 			BufferAccessStrategy strategy,
 			bool *foundPtr);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln);
+static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal);
 static void AtProcExit_Buffers(int code, Datum arg);
 static void CheckForBufferLeaks(void);
 static int	rnode_comparator(const void *p1, const void *p2);
@@ -3153,20 +3154,40 @@ PrintPinnedBufs(void)
 void
 FlushRelationBuffers(Relation rel)
 {
-	int			i;
-	BufferDesc *bufHdr;
-
 	/* Open rel at the smgr level if not already done */
 	RelationOpenSmgr(rel);
 
-	if (RelationUsesLocalBuffers(rel))
+	FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel));
+}
+
+/*
+ * Like FlushRelationBuffers(), but the relation is specified by RelFileNode
+ */
+void
+FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal)
+{
+	FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal);
+}
+
+/*
+ * Code shared between functions FlushRelationBuffers() and
+ * FlushRelationBuffersWithoutRelCache().
+ */
+static void
+FlushRelationBuffers_common(SMgrRelation smgr, bool islocal)
+{
+	RelFileNode rnode = smgr->smgr_rnode.node;
+	int			i;
+	BufferDesc *bufHdr;
+
+	if (islocal)
 	{
 		for (i = 0; i < NLocBuffer; i++)
 		{
 			uint32		buf_state;
 
 			bufHdr = GetLocalBufferDescriptor(i);
-			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+			if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 				((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
 				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 			{
@@ -3183,7 +3204,7 @@ FlushRelationBuffers(Relation rel)
 
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
-				smgrwrite(rel->rd_smgr,
+				smgrwrite(smgr,
 						  bufHdr->tag.forkNum,
 						  bufHdr->tag.blockNum,
 						  localpage,
@@ -3213,18 +3234,18 @@ FlushRelationBuffers(Relation rel)
 		 * As in DropRelFileNodeBuffers, an unlocked precheck should be safe
 		 * and saves some cycles.
 		 */
-		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+		if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
 			continue;
 
 		ReservePrivateRefCountEntry();
 
 		buf_state = LockBufHdr(bufHdr);
-		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+		if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 			(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 		{
 			PinBuffer_Locked(bufHdr);
 			LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
-			FlushBuffer(bufHdr, rel->rd_smgr);
+			FlushBuffer(bufHdr, smgr);
 			LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
 			UnpinBuffer(bufHdr, true);
 		}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 64f3c2e887..f06d55a8fe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -75,6 +75,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
+#include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/array.h"
@@ -5644,6 +5645,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
 		MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
+		rel->rd_nowalskip = false;
+		rel->rd_walskip = NULL;
 
 		/*
 		 * Recompute lock and physical addressing info.  This is needed in
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 882dc65c89..83fee7dbfe 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -23,8 +23,14 @@ extern void RelationCreateStorage(RelFileNode rnode, char relpersistence);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
-extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-								ForkNumber forkNum, char relpersistence);
+extern void RelationCopyStorage(Relation srcrel, SMgrRelation dst,
+								ForkNumber forkNum);
+extern bool BufferNeedsWAL(Relation rel, Buffer buf);
+extern bool BlockNeedsWAL(Relation rel, BlockNumber blkno);
+extern void RecordWALSkipping(Relation rel);
+extern void RecordPendingSync(Relation rel, SMgrRelation srel,
+							  ForkNumber forknum);
+extern void RelationInvalidateWALSkip(Relation rel);
 
 /*
  * These functions used to be in storage/smgr/smgr.c, which explains the
@@ -32,8 +38,11 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
  */
 extern void smgrDoPendingDeletes(bool isCommit);
 extern int	smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
-extern void AtSubCommit_smgr(void);
-extern void AtSubAbort_smgr(void);
+extern void smgrFinishBulkInsert(bool isCommit);
+extern void AtSubCommit_smgr(SubTransactionId mySubid,
+							 SubTransactionId parentSubid);
+extern void AtSubAbort_smgr(SubTransactionId mySubid,
+							 SubTransactionId parentSubid);
 extern void PostPrepare_smgr(void);
 
 #endif							/* STORAGE_H */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index c5826f691d..8a9ea041dd 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
 extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
+extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode,
+									bool islocal);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
 					   ForkNumber forkNum, BlockNumber firstDelBlock);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 54028515a7..b2b46322b2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -198,6 +198,13 @@ typedef struct RelationData
 
 	/* use "struct" here to avoid needing to include pgstat.h: */
 	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+	/*
+	 * rd_nowalskip is true if this relation is known not to skip WAL.
+	 * Otherwise we need to ask smgr for an entry if rd_walskip is NULL.
+	 */
+	bool				rd_nowalskip;
+	struct RelWalSkip   *rd_walskip;
 } RelationData;
 
 
-- 
2.16.3


----Next_Part(Tue_Apr_02_19_54_06_2019_801)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v10-0006-Fix-WAL-skipping-feature.patch"



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH 5/7] Add infrastructure to WAL-logging skip feature
@ 2019-04-02 09:05  Kyotaro Horiguchi <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Kyotaro Horiguchi @ 2019-04-02 09:05 UTC (permalink / raw)

We used to optimize WAL-logging for truncation of in-transaction
crated tables in minimal mode by just signaling by
HEAP_INSERT_SKIP_WAL option on heap operations. This mechanism can
emit WAL records that results in corrupt state for certain series of
in-transaction operations. This patch provides infrastructure to track
pending at-commit fsyncs for a relation and in-transaction
truncations. table_relation_register_walskip() should be used to start
tracking before batch operations like COPY and CLUSTER, and use
BufferNeedsWAL() instead of RelationNeedsWAL() at the places related
to WAL-logging about heap-modifying operations, then remove
call to table_finish_bulk_insert() and the tableam intaface.
---
 src/backend/access/transam/xact.c   |  12 +-
 src/backend/catalog/storage.c       | 612 +++++++++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c    |   6 +-
 src/backend/storage/buffer/bufmgr.c |  39 ++-
 src/backend/utils/cache/relcache.c  |   3 +
 src/include/catalog/storage.h       |  17 +-
 src/include/storage/bufmgr.h        |   2 +
 src/include/utils/rel.h             |   7 +
 8 files changed, 631 insertions(+), 67 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index bd5024ef00..a2c689f414 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2111,6 +2111,9 @@ CommitTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrFinishBulkInsert(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2343,6 +2346,9 @@ PrepareTransaction(void)
 	/* close large objects before lower-level cleanup */
 	AtEOXact_LargeObject(true);
 
+	/* Flush updates to relations that we didn't WAL-logged */
+	smgrFinishBulkInsert(true);
+
 	/*
 	 * Mark serializable transaction as complete for predicate locking
 	 * purposes.  This should be done as late as we can put it and still allow
@@ -2668,6 +2674,7 @@ AbortTransaction(void)
 	AtAbort_Notify();
 	AtEOXact_RelationMap(false, is_parallel_worker);
 	AtAbort_Twophase();
+	smgrFinishBulkInsert(false);	/* abandon pending syncs */
 
 	/*
 	 * Advertise the fact that we aborted in pg_xact (assuming that we got as
@@ -4801,8 +4808,7 @@ CommitSubTransaction(void)
 	AtEOSubXact_RelationCache(true, s->subTransactionId,
 							  s->parent->subTransactionId);
 	AtEOSubXact_Inval(true);
-	AtSubCommit_smgr();
-
+	AtSubCommit_smgr(s->subTransactionId, s->parent->subTransactionId);
 	/*
 	 * The only lock we actually release here is the subtransaction XID lock.
 	 */
@@ -4979,7 +4985,7 @@ AbortSubTransaction(void)
 		ResourceOwnerRelease(s->curTransactionOwner,
 							 RESOURCE_RELEASE_AFTER_LOCKS,
 							 false, false);
-		AtSubAbort_smgr();
+		AtSubAbort_smgr(s->subTransactionId, s->parent->subTransactionId);
 
 		AtEOXact_GUC(false, s->gucNestLevel);
 		AtEOSubXact_SPI(false, s->subTransactionId);
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 72242b2476..4cd112f86c 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -21,6 +21,7 @@
 
 #include "miscadmin.h"
 
+#include "access/tableam.h"
 #include "access/visibilitymap.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -29,10 +30,18 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "storage/freespace.h"
-#include "storage/smgr.h"
+#include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
+ /* #define STORAGEDEBUG */	/* turns DEBUG elogs on */
+
+#ifdef STORAGEDEBUG
+#define STORAGE_elog(...)				elog(__VA_ARGS__)
+#else
+#define STORAGE_elog(...)
+#endif
+
 /*
  * We keep a list of all relations (represented as RelFileNode values)
  * that have been created or deleted in the current transaction.  When
@@ -64,6 +73,61 @@ typedef struct PendingRelDelete
 
 static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
 
+/*
+ * We also track relation files (RelFileNode values) that have been created
+ * in the same transaction, and that have been modified without WAL-logging
+ * the action (an optimization possible with wal_level=minimal). When we are
+ * about to skip WAL-logging, a RelWalSkip entry is created, and
+ * 'skip_wal_min_blk' is set to the current size of the relation. Any
+ * operations on blocks < skip_wal_min_blk need to be WAL-logged as usual, but
+ * for operations on higher blocks, WAL-logging is skipped.
+
+ *
+ * NB: after WAL-logging has been skipped for a block, we must not WAL-log
+ * any subsequent actions on the same block either. Replaying the WAL record
+ * of the subsequent action might fail otherwise, as the "before" state of
+ * the block might not match, as the earlier actions were not WAL-logged.
+ * Likewise, after we have WAL-logged an operation for a block, we must
+ * WAL-log any subsequent operations on the same page as well. Replaying
+ * a possible full-page-image from the earlier WAL record would otherwise
+ * revert the page to the old state, even if we sync the relation at end
+ * of transaction.
+ *
+ * If a relation is truncated (without creating a new relfilenode), and we
+ * emit a WAL record of the truncation, we can't skip WAL-logging for any
+ * of the truncated blocks anymore, as replaying the truncation record will
+ * destroy all the data inserted after that. But if we have already decided
+ * to skip WAL-logging changes to a relation, and the relation is truncated,
+ * we don't need to WAL-log the truncation either.
+ *
+ * This mechanism is currently only used by heaps. Indexes are always
+ * WAL-logged. Also, this only applies for wal_level=minimal; with higher
+ * WAL levels we need the WAL for PITR/replication anyway.
+ */
+typedef struct RelWalSkip
+{
+	RelFileNode relnode;			/* relation created in same xact */
+	bool		forks[MAX_FORKNUM + 1];	/* target forknums */
+	BlockNumber skip_wal_min_blk;	/* WAL-logging skipped for blocks >=
+									 * skip_wal_min_blk */
+	BlockNumber wal_log_min_blk; 	/* The minimum blk number that requires
+									 * WAL-logging even if skipped by the
+									 * above*/
+	SubTransactionId create_sxid;	/* subxid where this entry is created */
+	SubTransactionId invalidate_sxid; /* subxid where this entry is
+									   * invalidated */
+	const TableAmRoutine *tableam;	/* Table access routine */
+}	RelWalSkip;
+
+/* Relations that need to be fsync'd at commit */
+static HTAB *walSkipHash = NULL;
+
+static RelWalSkip *getWalSkipEntry(Relation rel, bool create);
+static RelWalSkip *getWalSkipEntryRNode(RelFileNode *node,
+													  bool create);
+static void smgrProcessWALSkipInval(bool isCommit, SubTransactionId mySubid,
+						SubTransactionId parentSubid);
+
 /*
  * RelationCreateStorage
  *		Create physical storage for a relation.
@@ -261,31 +325,59 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
 	 */
 	if (RelationNeedsWAL(rel))
 	{
-		/*
-		 * Make an XLOG entry reporting the file truncation.
-		 */
-		XLogRecPtr	lsn;
-		xl_smgr_truncate xlrec;
+		RelWalSkip *walskip;
 
-		xlrec.blkno = nblocks;
-		xlrec.rnode = rel->rd_node;
-		xlrec.flags = SMGR_TRUNCATE_ALL;
-
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, sizeof(xlrec));
-
-		lsn = XLogInsert(RM_SMGR_ID,
-						 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+		/* get pending sync entry, create if not yet */
+		walskip = getWalSkipEntry(rel, true);
 
 		/*
-		 * Flush, because otherwise the truncation of the main relation might
-		 * hit the disk before the WAL record, and the truncation of the FSM
-		 * or visibility map. If we crashed during that window, we'd be left
-		 * with a truncated heap, but the FSM or visibility map would still
-		 * contain entries for the non-existent heap pages.
+		 * walskip is null here if rel doesn't support WAL-logging skip,
+		 * otherwise check for WAL-skipping status.
 		 */
-		if (fsm || vm)
-			XLogFlush(lsn);
+		if (walskip == NULL ||
+			walskip->skip_wal_min_blk == InvalidBlockNumber ||
+			walskip->skip_wal_min_blk < nblocks)
+		{
+			/*
+			 * If WAL-skipping is enabled, this is the first time truncation
+			 * of this relation in this transaction or truncation that leaves
+			 * pages that need at-commit fsync.  Make an XLOG entry reporting
+			 * the file truncation.
+			 */
+			XLogRecPtr		lsn;
+			xl_smgr_truncate xlrec;
+
+			xlrec.blkno = nblocks;
+			xlrec.rnode = rel->rd_node;
+			xlrec.flags = SMGR_TRUNCATE_ALL;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) &xlrec, sizeof(xlrec));
+
+			lsn = XLogInsert(RM_SMGR_ID,
+							 XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE);
+
+			STORAGE_elog(DEBUG2,
+						 "WAL-logged truncation of rel %u/%u/%u to %u blocks",
+						 rel->rd_node.spcNode, rel->rd_node.dbNode,
+						 rel->rd_node.relNode, nblocks);
+			/*
+			 * Flush, because otherwise the truncation of the main relation
+			 * might hit the disk before the WAL record, and the truncation of
+			 * the FSM or visibility map. If we crashed during that window,
+			 * we'd be left with a truncated heap, but the FSM or visibility
+			 * map would still contain entries for the non-existent heap
+			 * pages.
+			 */
+			if (fsm || vm)
+				XLogFlush(lsn);
+
+			if (walskip)
+			{
+				/* no longer skip WAL-logging for the blocks */
+				walskip->wal_log_min_blk = nblocks;
+			}
+		}
 	}
 
 	/* Do the real work */
@@ -296,8 +388,7 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
  * Copy a fork's data, block by block.
  */
 void
-RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-					ForkNumber forkNum, char relpersistence)
+RelationCopyStorage(Relation srcrel, SMgrRelation dst, ForkNumber forkNum)
 {
 	PGAlignedBlock buf;
 	Page		page;
@@ -305,6 +396,8 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 	bool		copying_initfork;
 	BlockNumber nblocks;
 	BlockNumber blkno;
+	SMgrRelation src = srcrel->rd_smgr;
+	char 		relpersistence = srcrel->rd_rel->relpersistence;
 
 	page = (Page) buf.data;
 
@@ -316,12 +409,33 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 	copying_initfork = relpersistence == RELPERSISTENCE_UNLOGGED &&
 		forkNum == INIT_FORKNUM;
 
-	/*
-	 * We need to log the copied data in WAL iff WAL archiving/streaming is
-	 * enabled AND it's a permanent relation.
-	 */
-	use_wal = XLogIsNeeded() &&
-		(relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork);
+	if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork)
+	{
+		/*
+		 * We need to log the copied data in WAL iff WAL archiving/streaming
+		 * is enabled AND it's a permanent relation.
+		 */
+		if (XLogIsNeeded())
+			use_wal = true;
+
+		/*
+		 * If the rel is WAL-logged, must fsync before commit.  We use
+		 * heap_sync to ensure that the toast table gets fsync'd too.  (For a
+		 * temp or unlogged rel we don't care since the data will be gone
+		 * after a crash anyway.)
+		 *
+		 * It's obvious that we must do this when not WAL-logging the
+		 * copy. It's less obvious that we have to do it even if we did
+		 * WAL-log the copied pages. The reason is that since we're copying
+		 * outside shared buffers, a CHECKPOINT occurring during the copy has
+		 * no way to flush the previously written data to disk (indeed it
+		 * won't know the new rel even exists).  A crash later on would replay
+		 * WAL from the checkpoint, therefore it wouldn't replay our earlier
+		 * WAL entries. If we do not fsync those pages here, they might still
+		 * not be on disk when the crash occurs.
+		 */
+		RecordPendingSync(srcrel, dst, forkNum);
+	}
 
 	nblocks = smgrnblocks(src, forkNum);
 
@@ -358,24 +472,321 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
 		 */
 		smgrextend(dst, forkNum, blkno, buf.data, true);
 	}
+}
+
+/*
+ * Do changes to given heap page need to be WAL-logged?
+ *
+ * This takes into account any previous RecordPendingSync() requests.
+ *
+ * Note that it is required to check this before creating any WAL records for
+ * heap pages - it is not merely an optimization! WAL-logging a record, when
+ * we have already skipped a previous WAL record for the same page could lead
+ * to failure at WAL replay, as the "before" state expected by the record
+ * might not match what's on disk. Also, if the heap was truncated earlier, we
+ * must WAL-log any changes to the once-truncated blocks, because replaying
+ * the truncation record will destroy them.
+ */
+bool
+BufferNeedsWAL(Relation rel, Buffer buf)
+{
+	BlockNumber		blkno = InvalidBlockNumber;
+	RelWalSkip *walskip;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch existing pending sync entry */
+	walskip = getWalSkipEntry(rel, false);
 
 	/*
-	 * If the rel is WAL-logged, must fsync before commit.  We use heap_sync
-	 * to ensure that the toast table gets fsync'd too.  (For a temp or
-	 * unlogged rel we don't care since the data will be gone after a crash
-	 * anyway.)
-	 *
-	 * It's obvious that we must do this when not WAL-logging the copy. It's
-	 * less obvious that we have to do it even if we did WAL-log the copied
-	 * pages. The reason is that since we're copying outside shared buffers, a
-	 * CHECKPOINT occurring during the copy has no way to flush the previously
-	 * written data to disk (indeed it won't know the new rel even exists).  A
-	 * crash later on would replay WAL from the checkpoint, therefore it
-	 * wouldn't replay our earlier WAL entries. If we do not fsync those pages
-	 * here, they might still not be on disk when the crash occurs.
+	 * no point in doing further work if we know that we don't skip
+	 * WAL-logging.
 	 */
-	if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork)
-		smgrimmedsync(dst, forkNum);
+	if (!walskip)
+	{
+		STORAGE_elog(DEBUG2,
+					 "not skipping WAL-logging for rel %u/%u/%u block %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, BufferGetBlockNumber(buf));
+		return true;
+	}
+
+	Assert(BufferIsValid(buf));
+
+	blkno = BufferGetBlockNumber(buf);
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walskip->skip_wal_min_blk == InvalidBlockNumber ||
+		walskip->skip_wal_min_blk > blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because skip_wal_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->skip_wal_min_blk);
+		return true;
+	}
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walskip->wal_log_min_blk != InvalidBlockNumber &&
+		walskip->wal_log_min_blk <= blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because wal_log_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->wal_log_min_blk);
+		return true;
+	}
+
+	STORAGE_elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, blkno);
+
+	return false;
+}
+
+bool
+BlockNeedsWAL(Relation rel, BlockNumber blkno)
+{
+	RelWalSkip *walskip;
+
+	if (!RelationNeedsWAL(rel))
+		return false;
+
+	/* fetch exising pending sync entry */
+	walskip = getWalSkipEntry(rel, false);
+
+	/*
+	 * no point in doing further work if we know that we don't skip
+	 * WAL-logging.
+	 */
+	if (!walskip)
+		return true;
+
+	/*
+	 * We don't skip WAL-logging for pages that once done.
+	 */
+	if (walskip->skip_wal_min_blk == InvalidBlockNumber ||
+		walskip->skip_wal_min_blk > blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because skip_wal_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->skip_wal_min_blk);
+		return true;
+	}
+
+	/*
+	 * we don't skip WAL-logging for blocks that have got WAL-logged
+	 * truncation
+	 */
+	if (walskip->wal_log_min_blk != InvalidBlockNumber &&
+		walskip->wal_log_min_blk <= blkno)
+	{
+		STORAGE_elog(DEBUG2, "not skipping WAL-logging for rel %u/%u/%u block %u, because wal_log_min_blk is %u",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, blkno, walskip->wal_log_min_blk);
+
+		return true;
+	}
+
+	STORAGE_elog(DEBUG2, "skipping WAL-logging for rel %u/%u/%u block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, blkno);
+
+	return false;
+}
+
+/*
+ * Remember that the given relation doesn't need WAL-logging for the blocks
+ * after the current block size and for the blocks that are going to be synced
+ * at commit.
+ */
+void
+RecordWALSkipping(Relation rel)
+{
+	RelWalSkip *walskip;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* get pending sync entry, create if not yet  */
+	walskip = getWalSkipEntry(rel, true);
+
+	if (walskip == NULL)
+		return;
+
+	/*
+	 *  Record only the first registration.
+	 */
+	if (walskip->skip_wal_min_blk != InvalidBlockNumber)
+	{
+		STORAGE_elog(DEBUG2, "WAL skipping for rel %u/%u/%u was already registered at block %u (new %u)",
+					 rel->rd_node.spcNode, rel->rd_node.dbNode,
+					 rel->rd_node.relNode, walskip->skip_wal_min_blk,
+					 RelationGetNumberOfBlocks(rel));
+		return;
+	}
+
+	STORAGE_elog(DEBUG2, "registering new WAL skipping rel %u/%u/%u at block %u",
+				 rel->rd_node.spcNode, rel->rd_node.dbNode,
+				 rel->rd_node.relNode, RelationGetNumberOfBlocks(rel));
+
+	walskip->skip_wal_min_blk = RelationGetNumberOfBlocks(rel);
+}
+
+/*
+ * Record commit-time file sync. This shouldn't be used mixing with
+ * RecordWALSkipping.
+ */
+void
+RecordPendingSync(Relation rel, SMgrRelation targetsrel, ForkNumber forknum)
+{
+	RelWalSkip *walskip;
+
+	Assert(RelationNeedsWAL(rel));
+
+	/* check for support for this feature */
+	if (rel->rd_tableam == NULL ||
+		rel->rd_tableam->relation_register_walskip == NULL)
+		return;
+
+	walskip = getWalSkipEntryRNode(&targetsrel->smgr_rnode.node, true);
+	walskip->forks[forknum] = true;
+	walskip->skip_wal_min_blk = 0;
+	walskip->tableam = rel->rd_tableam;
+
+	STORAGE_elog(DEBUG2,
+				 "registering new pending sync for rel %u/%u/%u at block %u",
+				 walskip->relnode.spcNode, walskip->relnode.dbNode,
+				 walskip->relnode.relNode, 0);
+}
+
+/*
+ * RelationInvalidateWALSkip() -- invalidate WAL-skip entry
+ */
+void
+RelationInvalidateWALSkip(Relation rel)
+{
+	RelWalSkip *walskip;
+
+	/* we know we don't have one */
+	if (rel->rd_nowalskip)
+		return;
+
+	walskip = getWalSkipEntry(rel, false);
+
+	if (!walskip)
+		return;
+
+	/*
+	 * The state is reset at subtransaction commit/abort. No invalidation
+	 * request must not come for the same relation in the same subtransaction.
+	 */
+	Assert(walskip->invalidate_sxid == InvalidSubTransactionId);
+
+	walskip->invalidate_sxid = GetCurrentSubTransactionId();
+
+	STORAGE_elog(DEBUG2,
+				 "WAL skip of rel %u/%u/%u invalidated by sxid %d",
+				 walskip->relnode.spcNode, walskip->relnode.dbNode,
+				 walskip->relnode.relNode, walskip->invalidate_sxid);
+}
+
+/*
+ * getWalSkipEntry: get WAL skip entry.
+ *
+ * Returns WAL skip entry for the relation. The entry tracks WAL-skipping
+ * blocks for the relation.  The WAL-skipped blocks need fsync at commit time.
+ * Creates one if needed when create is true. If rel doesn't support this
+ * feature, returns true even if create is true.
+ */
+static inline RelWalSkip *
+getWalSkipEntry(Relation rel, bool create)
+{
+	RelWalSkip *walskip_entry = NULL;
+
+	if (rel->rd_walskip)
+		return rel->rd_walskip;
+
+	/* we know we don't have pending sync entry */
+	if (!create && rel->rd_nowalskip)
+		return NULL;
+
+	/* check for support for this feature */
+	if (rel->rd_tableam == NULL ||
+		rel->rd_tableam->relation_register_walskip == NULL)
+	{
+		rel->rd_nowalskip = true;
+		return NULL;
+	}
+
+	walskip_entry = getWalSkipEntryRNode(&rel->rd_node, create);
+
+	if (!walskip_entry)
+	{
+		/* prevent further hash lookup */
+		rel->rd_nowalskip = true;
+		return NULL;
+	}
+
+	walskip_entry->forks[MAIN_FORKNUM] = true;
+	walskip_entry->tableam = rel->rd_tableam;
+
+	/* hold shortcut in Relation */
+	rel->rd_nowalskip = false;
+	rel->rd_walskip = walskip_entry;
+
+	return walskip_entry;
+}
+
+/*
+ * getWalSkipEntryRNode: get WAL skip entry by rnode
+ *
+ * Returns a WAL skip entry for the RelFileNode.
+ */
+static RelWalSkip *
+getWalSkipEntryRNode(RelFileNode *rnode, bool create)
+{
+	RelWalSkip *walskip_entry = NULL;
+	bool			found;
+
+	if (!walSkipHash)
+	{
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		if (!create)
+			return NULL;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(RelFileNode);
+		ctl.entrysize = sizeof(RelWalSkip);
+		ctl.hash = tag_hash;
+		walSkipHash = hash_create("pending relation sync table", 5,
+								   &ctl, HASH_ELEM | HASH_FUNCTION);
+	}
+
+	walskip_entry = (RelWalSkip *)
+		hash_search(walSkipHash, (void *) rnode,
+					create ? HASH_ENTER: HASH_FIND,	&found);
+
+	if (!walskip_entry)
+		return NULL;
+
+	/* new entry created */
+	if (!found)
+	{
+		memset(&walskip_entry->forks, 0, sizeof(walskip_entry->forks));
+		walskip_entry->wal_log_min_blk = InvalidBlockNumber;
+		walskip_entry->skip_wal_min_blk = InvalidBlockNumber;
+		walskip_entry->create_sxid = GetCurrentSubTransactionId();
+		walskip_entry->invalidate_sxid = InvalidSubTransactionId;
+		walskip_entry->tableam = NULL;
+	}
+
+	return walskip_entry;
 }
 
 /*
@@ -506,6 +917,107 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
 	return nrels;
 }
 
+/*
+ * Finish bulk insert of files.
+ */
+void
+smgrFinishBulkInsert(bool isCommit)
+{
+	if (!walSkipHash)
+		return;
+
+	if (isCommit)
+	{
+		HASH_SEQ_STATUS status;
+		RelWalSkip *walskip;
+
+		hash_seq_init(&status, walSkipHash);
+
+		while ((walskip = hash_seq_search(&status)) != NULL)
+		{
+			/*
+			 * On commit, process valid entreis. Rollback doesn't need sync on
+			 * all changes during the transaction.
+			 */
+			if (walskip->skip_wal_min_blk != InvalidBlockNumber &&
+				walskip->invalidate_sxid == InvalidSubTransactionId)
+			{
+				int f;
+
+				FlushRelationBuffersWithoutRelCache(walskip->relnode, false);
+
+				/*
+				 * We mustn't create an entry when the table AM doesn't
+				 * support WAL-skipping.
+				 */
+				Assert (walskip->tableam->finish_bulk_insert);
+
+				/* flush all requested forks  */
+				for (f = MAIN_FORKNUM ; f <= MAX_FORKNUM ; f++)
+				{
+					if (walskip->forks[f])
+					{
+						walskip->tableam->finish_bulk_insert(walskip->relnode, f);
+						STORAGE_elog(DEBUG2, "finishing bulk insert to rel %u/%u/%u fork %d",
+									 walskip->relnode.spcNode,
+									 walskip->relnode.dbNode,
+									 walskip->relnode.relNode, f);
+					}
+				}
+			}
+		}
+	}
+
+	hash_destroy(walSkipHash);
+	walSkipHash = NULL;
+}
+
+/*
+ * Process pending invalidation of WAL skip happened in the subtransaction
+ */
+void
+smgrProcessWALSkipInval(bool isCommit, SubTransactionId mySubid,
+						SubTransactionId parentSubid)
+{
+	HASH_SEQ_STATUS status;
+	RelWalSkip *walskip;
+
+	if (!walSkipHash)
+		return;
+
+	/* We expect that we don't have walSkipHash in almost all cases */
+	hash_seq_init(&status, walSkipHash);
+
+	while ((walskip = hash_seq_search(&status)) != NULL)
+	{
+		if (walskip->create_sxid == mySubid)
+		{
+			/*
+			 * The entry was created in this subxact. Remove it on abort, or
+			 * on commit after invalidation.
+			 */
+			if (!isCommit || walskip->invalidate_sxid == mySubid)
+				hash_search(walSkipHash, &walskip->relnode,
+							HASH_REMOVE, NULL);
+			/* Treat committing valid entry as creation by the parent. */
+			else if (walskip->invalidate_sxid == InvalidSubTransactionId)
+				walskip->create_sxid = parentSubid;
+		}
+		else if (walskip->invalidate_sxid == mySubid)
+		{
+			/*
+			 * This entry was created elsewhere then invalidated by this
+			 * subxact. Treat commit as invalidation by the parent. Otherwise
+			 * cancel invalidation.
+			 */
+			if (isCommit)
+				walskip->invalidate_sxid = parentSubid;
+			else
+				walskip->invalidate_sxid = InvalidSubTransactionId;
+		}
+	}
+}
+
 /*
  *	PostPrepare_smgr -- Clean up after a successful PREPARE
  *
@@ -535,7 +1047,7 @@ PostPrepare_smgr(void)
  * Reassign all items in the pending-deletes list to the parent transaction.
  */
 void
-AtSubCommit_smgr(void)
+AtSubCommit_smgr(SubTransactionId mySubid, SubTransactionId parentSubid)
 {
 	int			nestLevel = GetCurrentTransactionNestLevel();
 	PendingRelDelete *pending;
@@ -545,6 +1057,9 @@ AtSubCommit_smgr(void)
 		if (pending->nestLevel >= nestLevel)
 			pending->nestLevel = nestLevel - 1;
 	}
+
+	/* Remove invalidated WAL skip in this subtransaction */
+	smgrProcessWALSkipInval(true, mySubid, parentSubid);
 }
 
 /*
@@ -555,9 +1070,12 @@ AtSubCommit_smgr(void)
  * subtransaction will not commit.
  */
 void
-AtSubAbort_smgr(void)
+AtSubAbort_smgr(SubTransactionId mySubid, SubTransactionId parentSubid)
 {
 	smgrDoPendingDeletes(false);
+
+	/* Remove invalidated WAL skip in this subtransaction */
+	smgrProcessWALSkipInval(false, mySubid, parentSubid);
 }
 
 void
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e842f9152b..013eb203f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12452,8 +12452,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 	RelationCreateStorage(newrnode, rel->rd_rel->relpersistence);
 
 	/* copy main fork */
-	RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM,
-						rel->rd_rel->relpersistence);
+	RelationCopyStorage(rel, dstrel, MAIN_FORKNUM);
 
 	/* copy those extra forks that exist */
 	for (ForkNumber forkNum = MAIN_FORKNUM + 1;
@@ -12471,8 +12470,7 @@ index_copy_data(Relation rel, RelFileNode newrnode)
 				(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
 				 forkNum == INIT_FORKNUM))
 				log_smgrcreate(&newrnode, forkNum);
-			RelationCopyStorage(rel->rd_smgr, dstrel, forkNum,
-								rel->rd_rel->relpersistence);
+			RelationCopyStorage(rel, dstrel, forkNum);
 		}
 	}
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 887023fc8a..0c6598d9af 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -451,6 +451,7 @@ static BufferDesc *BufferAlloc(SMgrRelation smgr,
 			BufferAccessStrategy strategy,
 			bool *foundPtr);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln);
+static void FlushRelationBuffers_common(SMgrRelation smgr, bool islocal);
 static void AtProcExit_Buffers(int code, Datum arg);
 static void CheckForBufferLeaks(void);
 static int	rnode_comparator(const void *p1, const void *p2);
@@ -3153,20 +3154,40 @@ PrintPinnedBufs(void)
 void
 FlushRelationBuffers(Relation rel)
 {
-	int			i;
-	BufferDesc *bufHdr;
-
 	/* Open rel at the smgr level if not already done */
 	RelationOpenSmgr(rel);
 
-	if (RelationUsesLocalBuffers(rel))
+	FlushRelationBuffers_common(rel->rd_smgr, RelationUsesLocalBuffers(rel));
+}
+
+/*
+ * Like FlushRelationBuffers(), but the relation is specified by RelFileNode
+ */
+void
+FlushRelationBuffersWithoutRelCache(RelFileNode rnode, bool islocal)
+{
+	FlushRelationBuffers_common(smgropen(rnode, InvalidBackendId), islocal);
+}
+
+/*
+ * Code shared between functions FlushRelationBuffers() and
+ * FlushRelationBuffersWithoutRelCache().
+ */
+static void
+FlushRelationBuffers_common(SMgrRelation smgr, bool islocal)
+{
+	RelFileNode rnode = smgr->smgr_rnode.node;
+	int			i;
+	BufferDesc *bufHdr;
+
+	if (islocal)
 	{
 		for (i = 0; i < NLocBuffer; i++)
 		{
 			uint32		buf_state;
 
 			bufHdr = GetLocalBufferDescriptor(i);
-			if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+			if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 				((buf_state = pg_atomic_read_u32(&bufHdr->state)) &
 				 (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 			{
@@ -3183,7 +3204,7 @@ FlushRelationBuffers(Relation rel)
 
 				PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
 
-				smgrwrite(rel->rd_smgr,
+				smgrwrite(smgr,
 						  bufHdr->tag.forkNum,
 						  bufHdr->tag.blockNum,
 						  localpage,
@@ -3213,18 +3234,18 @@ FlushRelationBuffers(Relation rel)
 		 * As in DropRelFileNodeBuffers, an unlocked precheck should be safe
 		 * and saves some cycles.
 		 */
-		if (!RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node))
+		if (!RelFileNodeEquals(bufHdr->tag.rnode, rnode))
 			continue;
 
 		ReservePrivateRefCountEntry();
 
 		buf_state = LockBufHdr(bufHdr);
-		if (RelFileNodeEquals(bufHdr->tag.rnode, rel->rd_node) &&
+		if (RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
 			(buf_state & (BM_VALID | BM_DIRTY)) == (BM_VALID | BM_DIRTY))
 		{
 			PinBuffer_Locked(bufHdr);
 			LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);
-			FlushBuffer(bufHdr, rel->rd_smgr);
+			FlushBuffer(bufHdr, smgr);
 			LWLockRelease(BufferDescriptorGetContentLock(bufHdr));
 			UnpinBuffer(bufHdr, true);
 		}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 64f3c2e887..f06d55a8fe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -75,6 +75,7 @@
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rowsecurity.h"
+#include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
 #include "utils/array.h"
@@ -5644,6 +5645,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
 		MemSet(&rel->pgstat_info, 0, sizeof(rel->pgstat_info));
+		rel->rd_nowalskip = false;
+		rel->rd_walskip = NULL;
 
 		/*
 		 * Recompute lock and physical addressing info.  This is needed in
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 882dc65c89..83fee7dbfe 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -23,8 +23,14 @@ extern void RelationCreateStorage(RelFileNode rnode, char relpersistence);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
-extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
-								ForkNumber forkNum, char relpersistence);
+extern void RelationCopyStorage(Relation srcrel, SMgrRelation dst,
+								ForkNumber forkNum);
+extern bool BufferNeedsWAL(Relation rel, Buffer buf);
+extern bool BlockNeedsWAL(Relation rel, BlockNumber blkno);
+extern void RecordWALSkipping(Relation rel);
+extern void RecordPendingSync(Relation rel, SMgrRelation srel,
+							  ForkNumber forknum);
+extern void RelationInvalidateWALSkip(Relation rel);
 
 /*
  * These functions used to be in storage/smgr/smgr.c, which explains the
@@ -32,8 +38,11 @@ extern void RelationCopyStorage(SMgrRelation src, SMgrRelation dst,
  */
 extern void smgrDoPendingDeletes(bool isCommit);
 extern int	smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr);
-extern void AtSubCommit_smgr(void);
-extern void AtSubAbort_smgr(void);
+extern void smgrFinishBulkInsert(bool isCommit);
+extern void AtSubCommit_smgr(SubTransactionId mySubid,
+							 SubTransactionId parentSubid);
+extern void AtSubAbort_smgr(SubTransactionId mySubid,
+							 SubTransactionId parentSubid);
 extern void PostPrepare_smgr(void);
 
 #endif							/* STORAGE_H */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index c5826f691d..8a9ea041dd 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -189,6 +189,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
 								ForkNumber forkNum);
 extern void FlushOneBuffer(Buffer buffer);
 extern void FlushRelationBuffers(Relation rel);
+extern void FlushRelationBuffersWithoutRelCache(RelFileNode rnode,
+									bool islocal);
 extern void FlushDatabaseBuffers(Oid dbid);
 extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,
 					   ForkNumber forkNum, BlockNumber firstDelBlock);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 89a7fbf73a..0adc2aba06 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -198,6 +198,13 @@ typedef struct RelationData
 
 	/* use "struct" here to avoid needing to include pgstat.h: */
 	struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
+
+	/*
+	 * rd_nowalskip is true if this relation is known not to skip WAL.
+	 * Otherwise we need to ask smgr for an entry if rd_walskip is NULL.
+	 */
+	bool				rd_nowalskip;
+	struct RelWalSkip   *rd_walskip;
 } RelationData;
 
 
-- 
2.16.3


----Next_Part(Fri_Apr_05_12_55_20_2019_986)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v11-0006-Fix-WAL-skipping-feature.patch"



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-03-07 00:28  Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2023-03-07 00:28 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: pgsql-hackers

Hi,

On 2023-03-03 15:09:18 -0500, Tom Lane wrote:
> Andres Freund <[email protected]> writes:
> > On 2023-03-02 13:00:31 -0800, Andres Freund wrote:
> >> I'm not opposed to EXPR_PARAM_SET, to be clear. I'll send an updated
> >> version later. I was just thinking about the correctness in the current
> >> world.
>
> > Attached.
>
> I've looked through this, and it looks basically OK so I marked it RfC.

Thanks!


> I do have a few nitpicks that you might or might not choose to adopt:
>
> It'd be good to have a header comment for ExecInitExprRec documenting
> the arguments, particularly that resv/resnull are where to put the
> subplan's eventual result.

Did you mean ExecInitSubPlanExpr()?


> You could avoid having to assume ExprState's resvalue/resnull being
> safe to use by instead using the target resv/resnull.  This would
> require putting those into the EEOP_PARAM_SET step so that
> ExecEvalParamSet knows where to fetch from, so maybe it's not an
> improvement, but perhaps worth considering.

I think that'd be a bit worse - we'd have more pointers that can't be handled
in a generic way in JIT.


> I think that ExecEvalParamSet should either set prm->execPlan to NULL,
> or maybe better Assert that it is already NULL.

Agreed.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-03-07 00:51  Tom Lane <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2023-03-07 00:51 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: pgsql-hackers

Andres Freund <[email protected]> writes:
> On 2023-03-03 15:09:18 -0500, Tom Lane wrote:
>> It'd be good to have a header comment for ExecInitExprRec documenting
>> the arguments, particularly that resv/resnull are where to put the
>> subplan's eventual result.

> Did you mean ExecInitSubPlanExpr()?

Right, copy-and-pasteo, sorry.

>> You could avoid having to assume ExprState's resvalue/resnull being
>> safe to use by instead using the target resv/resnull.  This would
>> require putting those into the EEOP_PARAM_SET step so that
>> ExecEvalParamSet knows where to fetch from, so maybe it's not an
>> improvement, but perhaps worth considering.

> I think that'd be a bit worse - we'd have more pointers that can't be handled
> in a generic way in JIT.

OK.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH v4 1/1] Adjust extra lines generated by psql to be valid SQL comments.
@ 2023-07-26 21:35  Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Nathan Bossart @ 2023-07-26 21:35 UTC (permalink / raw)

psql's --echo-hidden, --log-file, and --single-step options
generate extra lines to clearly separate queries from other output.
Presently, these extra lines are not valid SQL comments, which
makes them a hazard for anyone trying to copy/paste these decorated
queries into a client.  This commit replaces the starting and
ending asterisks in these extra lines with forward slashes so that
they are valid SQL comments that can be copy/pasted without
incident.

Author: Kirk Wolak
Reviewed-by: Pavel Stehule, Laurenz Albe, Tom Lane, �lvaro Herrera, Andrey Borodin
Discussion: https://postgr.es/m/CACLU5mTFJRJYtbvmZ26txGgmXWQo0hkGhH2o3hEquUPmSbGtBw%40mail.gmail.com
---
 src/bin/psql/command.c |  8 ++++----
 src/bin/psql/common.c  | 16 ++++++++--------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 6733f008fd..1300869d79 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -5395,16 +5395,16 @@ echo_hidden_command(const char *query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
-		printf(_("********* QUERY **********\n"
+		printf(_("/******** QUERY *********/\n"
 				 "%s\n"
-				 "**************************\n\n"), query);
+				 "/************************/\n\n"), query);
 		fflush(stdout);
 		if (pset.logfile)
 		{
 			fprintf(pset.logfile,
-					_("********* QUERY **********\n"
+					_("/******** QUERY *********/\n"
 					  "%s\n"
-					  "**************************\n\n"), query);
+					  "/************************/\n\n"), query);
 			fflush(pset.logfile);
 		}
 
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 5973df2e39..10ad1f2538 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -589,16 +589,16 @@ PSQLexec(const char *query)
 
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
-		printf(_("********* QUERY **********\n"
+		printf(_("/******** QUERY *********/\n"
 				 "%s\n"
-				 "**************************\n\n"), query);
+				 "/************************/\n\n"), query);
 		fflush(stdout);
 		if (pset.logfile)
 		{
 			fprintf(pset.logfile,
-					_("********* QUERY **********\n"
+					_("/******** QUERY *********/\n"
 					  "%s\n"
-					  "**************************\n\n"), query);
+					  "/************************/\n\n"), query);
 			fflush(pset.logfile);
 		}
 
@@ -1060,9 +1060,9 @@ SendQuery(const char *query)
 		char		buf[3];
 
 		fflush(stderr);
-		printf(_("***(Single step mode: verify command)*******************************************\n"
+		printf(_("/**(Single step mode: verify command)******************************************/\n"
 				 "%s\n"
-				 "***(press return to proceed or enter x and return to cancel)********************\n"),
+				 "/**(press return to proceed or enter x and return to cancel)*******************/\n"),
 			   query);
 		fflush(stdout);
 		if (fgets(buf, sizeof(buf), stdin) != NULL)
@@ -1080,9 +1080,9 @@ SendQuery(const char *query)
 	if (pset.logfile)
 	{
 		fprintf(pset.logfile,
-				_("********* QUERY **********\n"
+				_("/******** QUERY *********/\n"
 				  "%s\n"
-				  "**************************\n\n"), query);
+				  "/************************/\n\n"), query);
 		fflush(pset.logfile);
 	}
 
-- 
2.25.1


--UlVJffcvxoiEqYs2--





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-10-01 18:41  Peter Eisentraut <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Peter Eisentraut @ 2023-10-01 18:41 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; Andres Freund <[email protected]>; +Cc: pgsql-hackers

Is this patch still being worked on?

On 07.03.23 01:51, Tom Lane wrote:
> Andres Freund <[email protected]> writes:
>> On 2023-03-03 15:09:18 -0500, Tom Lane wrote:
>>> It'd be good to have a header comment for ExecInitExprRec documenting
>>> the arguments, particularly that resv/resnull are where to put the
>>> subplan's eventual result.
> 
>> Did you mean ExecInitSubPlanExpr()?
> 
> Right, copy-and-pasteo, sorry.
> 
>>> You could avoid having to assume ExprState's resvalue/resnull being
>>> safe to use by instead using the target resv/resnull.  This would
>>> require putting those into the EEOP_PARAM_SET step so that
>>> ExecEvalParamSet knows where to fetch from, so maybe it's not an
>>> improvement, but perhaps worth considering.
> 
>> I think that'd be a bit worse - we'd have more pointers that can't be handled
>> in a generic way in JIT.
> 
> OK.
> 
> 			regards, tom lane
> 
> 







^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-10-01 18:53  Tom Lane <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2023-10-01 18:53 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Andres Freund <[email protected]>; pgsql-hackers

Peter Eisentraut <[email protected]> writes:
> Is this patch still being worked on?

I thought Andres simply hadn't gotten back to it yet.
It still seems like a worthwhile improvement.

			regards, tom lane






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-10-10 03:00  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Andres Freund @ 2023-10-10 03:00 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers

Hi,

On 2023-10-01 14:53:23 -0400, Tom Lane wrote:
> Peter Eisentraut <[email protected]> writes:
> > Is this patch still being worked on?
> 
> I thought Andres simply hadn't gotten back to it yet.
> It still seems like a worthwhile improvement.

Indeed - I do plan to commit it. I haven't quite shifted into v17 mode yet...

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-10-23 20:14  Alena Rybakina <[email protected]>
  parent: Andres Freund <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Alena Rybakina @ 2023-10-23 20:14 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; Tom Lane <[email protected]>

Hi!

I looked through your patch and noticed that it was not applied to the 
current version of the master. I rebased it and attached a version. I 
didn't see any problems and, honestly, no big changes were needed, all 
regression tests were passed.

I think it's better to add a test, but to be honest, I haven't been able 
to come up with something yet.

-- 
Regards,
Alena Rybakina


Attachments:

  [text/x-patch] v2-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch (12.9K, ../../[email protected]/3-v2-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch)
  download | inline diff:
From f7a8ca7f3263fa5f82056f90231cf937133622c9 Mon Sep 17 00:00:00 2001
From: Andres Freund <[email protected]>
Date: Mon, 23 Oct 2023 22:54:04 +0300
Subject: [PATCH] WIP: Evaluate arguments of correlated SubPlans in the
 referencing ExprState

---
 src/backend/executor/execExpr.c       | 93 +++++++++++++++++----------
 src/backend/executor/execExprInterp.c | 23 +++++++
 src/backend/executor/execProcnode.c   |  5 ++
 src/backend/executor/nodeSubplan.c    | 30 ++++-----
 src/backend/jit/llvm/llvmjit_expr.c   |  6 ++
 src/backend/jit/llvm/llvmjit_types.c  |  1 +
 src/include/executor/execExpr.h       |  6 +-
 src/include/nodes/execnodes.h         |  1 -
 8 files changed, 113 insertions(+), 52 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2c62b0c9c84..14cd56a2db3 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -88,6 +88,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 								  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
 								  int transno, int setno, int setoff, bool ishash,
 								  bool nullcheck);
+static void ExecInitSubPlanExpr(SubPlan *subplan,
+								ExprState *state,
+								Datum *resv, bool *resnull);
 
 
 /*
@@ -1389,7 +1392,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_SubPlan:
 			{
 				SubPlan    *subplan = (SubPlan *) node;
-				SubPlanState *sstate;
 
 				/*
 				 * Real execution of a MULTIEXPR SubPlan has already been
@@ -1406,19 +1408,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					break;
 				}
 
-				if (!state->parent)
-					elog(ERROR, "SubPlan found with no parent plan");
-
-				sstate = ExecInitSubPlan(subplan, state->parent);
-
-				/* add SubPlanState nodes to state->parent->subPlan */
-				state->parent->subPlan = lappend(state->parent->subPlan,
-												 sstate);
-
-				scratch.opcode = EEOP_SUBPLAN;
-				scratch.d.subplan.sstate = sstate;
-
-				ExprEvalPushStep(state, &scratch);
+				ExecInitSubPlanExpr(subplan, state, resv, resnull);
 				break;
 			}
 
@@ -2750,29 +2740,12 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
 	foreach(lc, info->multiexpr_subplans)
 	{
 		SubPlan    *subplan = (SubPlan *) lfirst(lc);
-		SubPlanState *sstate;
 
 		Assert(subplan->subLinkType == MULTIEXPR_SUBLINK);
 
-		/* This should match what ExecInitExprRec does for other SubPlans: */
-
-		if (!state->parent)
-			elog(ERROR, "SubPlan found with no parent plan");
-
-		sstate = ExecInitSubPlan(subplan, state->parent);
-
-		/* add SubPlanState nodes to state->parent->subPlan */
-		state->parent->subPlan = lappend(state->parent->subPlan,
-										 sstate);
-
-		scratch.opcode = EEOP_SUBPLAN;
-		scratch.d.subplan.sstate = sstate;
-
 		/* The result can be ignored, but we better put it somewhere */
-		scratch.resvalue = &state->resvalue;
-		scratch.resnull = &state->resnull;
-
-		ExprEvalPushStep(state, &scratch);
+		ExecInitSubPlanExpr(subplan, state,
+							&state->resvalue, &state->resnull);
 	}
 }
 
@@ -4178,3 +4151,57 @@ ExecBuildParamSetEqual(TupleDesc desc,
 
 	return state;
 }
+
+static void
+ExecInitSubPlanExpr(SubPlan *subplan,
+					ExprState *state,
+					Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch = {0};
+	SubPlanState *sstate;
+	ListCell   *pvar;
+	ListCell   *l;
+
+	if (!state->parent)
+		elog(ERROR, "SubPlan found with no parent plan");
+
+	/*
+	 * Generate steps to evaluate input arguments for the subplan.
+	 *
+	 * We evaluate the argument expressions into ExprState's resvalue/resnull,
+	 * and then use PARAM_SET to update the parameter. We do that, instead of
+	 * evaluating directly into the param, to avoid depending on the pointer
+	 * value remaining stable / being included in the generated expression. No
+	 * danger of conflicts with other uses of resvalue/resnull as storing and
+	 * using the value always is in subsequent steps.
+	 *
+	 * Any calculation we have to do can be done in the parent econtext, since
+	 * the Param values don't need to have per-query lifetime.
+	 */
+	forboth(l, subplan->parParam, pvar, subplan->args)
+	{
+		int			paramid = lfirst_int(l);
+
+		ExecInitExprRec(lfirst(pvar), state,
+						&state->resvalue, &state->resnull);
+
+		scratch.opcode = EEOP_PARAM_SET;
+		scratch.d.param.paramid = paramid;
+		/* type isn't needed, but an old value could be confusing */
+		scratch.d.param.paramtype = InvalidOid;
+		ExprEvalPushStep(state, &scratch);
+	}
+
+	sstate = ExecInitSubPlan(subplan, state->parent);
+
+	/* add SubPlanState nodes to state->parent->subPlan */
+	state->parent->subPlan = lappend(state->parent->subPlan,
+									 sstate);
+
+	scratch.opcode = EEOP_SUBPLAN;
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+	scratch.d.subplan.sstate = sstate;
+
+	ExprEvalPushStep(state, &scratch);
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 24c2b60c62a..e3981df3de8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -449,6 +449,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_SET,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1087,6 +1088,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_SET)
+		{
+			/* out of line, unlikely to matter performancewise */
+			ExecEvalParamSet(state, op, econtext);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
@@ -2510,6 +2518,21 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Set value of a param (currently always PARAM_EXEC) from
+ * state->res{value,null}.
+ */
+void
+ExecEvalParamSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamExecData *prm;
+
+	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
+	prm->value = state->resvalue;
+	prm->isnull = state->resnull;
+}
+
+
 /*
  * Evaluate a SQLValueFunction expression.
  */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index b4b5c562c0f..13ac3e600e6 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -393,6 +393,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 	/*
 	 * Initialize any initPlans present in this node.  The planner put them in
 	 * a separate list for us.
+	 *
+	 * The defining characteristic of initplans is that they don't have
+	 * arguments, so we don't need to evaluate them (in contrast to
+	 * ExecInitSubPlanExpr()).
 	 */
 	subps = NIL;
 	foreach(l, node->initPlan)
@@ -401,6 +405,7 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 		SubPlanState *sstate;
 
 		Assert(IsA(subplan, SubPlan));
+		Assert(subplan->args == NIL);
 		sstate = ExecInitSubPlan(subplan, result);
 		subps = lappend(subps, sstate);
 	}
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index c136f75ac24..3458ac007cd 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -107,7 +107,7 @@ ExecHashSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 
 	/* Shouldn't have any direct correlation Vars */
-	if (subplan->parParam != NIL || node->args != NIL)
+	if (subplan->parParam != NIL || subplan->args != NIL)
 		elog(ERROR, "hashed subplan with direct correlation not supported");
 
 	/*
@@ -231,7 +231,6 @@ ExecScanSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 	Datum		result;
 	bool		found = false;	/* true if got at least one subplan tuple */
-	ListCell   *pvar;
 	ListCell   *l;
 	ArrayBuildStateAny *astate = NULL;
 
@@ -248,26 +247,20 @@ ExecScanSubPlan(SubPlanState *node,
 	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 
 	/*
-	 * Set Params of this plan from parent plan correlation values. (Any
-	 * calculation we have to do is done in the parent econtext, since the
-	 * Param values don't need to have per-query lifetime.)
+	 * We rely on the caller to evaluate plan correlation values, if
+	 * necessary. However we still need to record the fact that the values
+	 * (might have) changed, otherwise the ExecReScan() below won't know that
+	 * nodes need to be rescanned.
 	 */
-	Assert(list_length(subplan->parParam) == list_length(node->args));
-
-	forboth(l, subplan->parParam, pvar, node->args)
+	Assert(list_length(subplan->parParam) == list_length(subplan->args));
+	foreach(l, subplan->parParam)
 	{
 		int			paramid = lfirst_int(l);
-		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-		prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
-											   econtext,
-											   &(prm->isnull));
 		planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
 	}
 
-	/*
-	 * Now that we've set up its parameters, we can reset the subplan.
-	 */
+	/* with that done, we can reset the subplan */
 	ExecReScan(planstate);
 
 	/*
@@ -817,6 +810,10 @@ slotNoNulls(TupleTableSlot *slot)
  * as well as regular SubPlans.  Note that we don't link the SubPlan into
  * the parent's subPlan list, because that shouldn't happen for InitPlans.
  * Instead, ExecInitExpr() does that one part.
+ *
+ * We also rely on ExecInitExpr(), more precisely ExecInitSubPlanExpr(), to
+ * evaluate input parameters, as that allows them to be evaluated as part of
+ * the expression referencing the SubPlan.
  * ----------------------------------------------------------------
  */
 SubPlanState *
@@ -844,7 +841,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 
 	/* Initialize subexpressions */
 	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
-	sstate->args = ExecInitExprList(subplan->args, parent);
 
 	/*
 	 * initialize my state
@@ -1107,7 +1103,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		elog(ERROR, "ANY/ALL subselect unsupported as initplan");
 	if (subLinkType == CTE_SUBLINK)
 		elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
-	if (subplan->parParam || node->args)
+	if (subplan->parParam || subplan->args)
 		elog(ERROR, "correlated subplans should not be executed via ExecSetParamPlan");
 
 	/*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index a3a0876bfff..0b2d4d9057c 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1145,6 +1145,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_PARAM_SET:
+				build_EvalXFunc(b, mod, "ExecEvalParamSet",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_SBSREF_SUBSCRIPTS:
 				{
 					int			jumpdone = op->d.sbsref_subscript.jumpdone;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 791902ff1f3..78bf99c1cbc 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -159,6 +159,7 @@ void	   *referenced_functions[] =
 	ExecEvalNextValueExpr,
 	ExecEvalParamExec,
 	ExecEvalParamExtern,
+	ExecEvalParamSet,
 	ExecEvalRow,
 	ExecEvalRowNotNull,
 	ExecEvalRowNull,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 048573c2bcb..91b67454eb4 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -159,6 +159,8 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	/* set PARAM_EXEC value */
+	EEOP_PARAM_SET,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -378,7 +380,7 @@ typedef struct ExprEvalStep
 			ExprEvalRowtypeCache rowcache;
 		}			nulltest_row;
 
-		/* for EEOP_PARAM_EXEC/EXTERN */
+		/* for EEOP_PARAM_EXEC/EXTERN and EEOP_PARAM_SET */
 		struct
 		{
 			int			paramid;	/* numeric ID for parameter */
@@ -776,6 +778,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
+extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
+							 ExprContext *econtext);
 extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba287..88a128a5514 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -952,7 +952,6 @@ typedef struct SubPlanState
 	struct PlanState *planstate;	/* subselect plan's state tree */
 	struct PlanState *parent;	/* parent plan node's state tree */
 	ExprState  *testexpr;		/* state of combining expression */
-	List	   *args;			/* states of argument expression(s) */
 	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
 	Datum		curArray;		/* most recent array from ARRAY() subplan */
 	/* these are used when hashing the subselect's output: */
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2023-11-22 08:37  John Naylor <[email protected]>
  parent: Andres Freund <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: John Naylor @ 2023-11-22 08:37 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

On Tue, Oct 10, 2023 at 10:00 AM Andres Freund <[email protected]> wrote:
>
> Hi,
>
> On 2023-10-01 14:53:23 -0400, Tom Lane wrote:
> > Peter Eisentraut <[email protected]> writes:
> > > Is this patch still being worked on?
> >
> > I thought Andres simply hadn't gotten back to it yet.
> > It still seems like a worthwhile improvement.
>
> Indeed - I do plan to commit it. I haven't quite shifted into v17 mode yet...

Any shift yet? ;-)






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-01-21 23:30  Peter Smith <[email protected]>
  parent: John Naylor <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Peter Smith @ 2024-01-21 23:30 UTC (permalink / raw)
  To: John Naylor <[email protected]>; +Cc: Andres Freund <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

2024-01 Commitfest.

Hi, This patch has a CF status of "Ready for Committer", but it is
currently failing some CFbot tests [1]. Please have a look and post an
updated version..

======
[1] https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/46/4209

Kind Regards,
Peter Smith.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-01-22 20:47  Andres Freund <[email protected]>
  parent: Peter Smith <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Andres Freund @ 2024-01-22 20:47 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: John Naylor <[email protected]>; Tom Lane <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

Hi,

On 2024-01-22 10:30:22 +1100, Peter Smith wrote:
> 2024-01 Commitfest.
> 
> Hi, This patch has a CF status of "Ready for Committer", but it is
> currently failing some CFbot tests [1]. Please have a look and post an
> updated version..

I think this failure is independent of this patch - by coincidence I just
sent an email about the issue
https://www.postgresql.org/message-id/20240122204117.swton324xcoodnyi%40awork3.anarazel.de
a few minutes ago.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-01-26 02:37  vignesh C <[email protected]>
  parent: Alena Rybakina <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: vignesh C @ 2024-01-26 02:37 UTC (permalink / raw)
  To: Alena Rybakina <[email protected]>; +Cc: Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers; Tom Lane <[email protected]>

On Tue, 24 Oct 2023 at 01:47, Alena Rybakina <[email protected]> wrote:
>
> Hi!
>
> I looked through your patch and noticed that it was not applied to the current version of the master. I rebased it and attached a version. I didn't see any problems and, honestly, no big changes were needed, all regression tests were passed.
>
> I think it's better to add a test, but to be honest, I haven't been able to come up with something yet.

The patch does not apply anymore as in CFBot at [1]:

=== Applying patches on top of PostgreSQL commit ID
7014c9a4bba2d1b67d60687afb5b2091c1d07f73 ===
=== applying patch
./v2-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch
....
patching file src/include/executor/execExpr.h
Hunk #1 succeeded at 160 (offset 1 line).
Hunk #2 succeeded at 382 (offset 2 lines).
Hunk #3 FAILED at 778.
1 out of 3 hunks FAILED -- saving rejects to file
src/include/executor/execExpr.h.rej
patching file src/include/nodes/execnodes.h
Hunk #1 succeeded at 959 (offset 7 lines).

Please have a look and post an updated version.

[1] - http://cfbot.cputube.org/patch_46_4209.log

Regards,
Vignesh





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-01-28 09:07  Alena Rybakina <[email protected]>
  parent: vignesh C <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Alena Rybakina @ 2024-01-28 09:07 UTC (permalink / raw)
  To: vignesh C <[email protected]>; +Cc: Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers; Tom Lane <[email protected]>

On 26.01.2024 05:37, vignesh C wrote:
> On Tue, 24 Oct 2023 at 01:47, Alena Rybakina <[email protected]> wrote:
>> Hi!
>>
>> I looked through your patch and noticed that it was not applied to the current version of the master. I rebased it and attached a version. I didn't see any problems and, honestly, no big changes were needed, all regression tests were passed.
>>
>> I think it's better to add a test, but to be honest, I haven't been able to come up with something yet.
> The patch does not apply anymore as in CFBot at [1]:
>
> === Applying patches on top of PostgreSQL commit ID
> 7014c9a4bba2d1b67d60687afb5b2091c1d07f73 ===
> === applying patch
> ./v2-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch
> ....
> patching file src/include/executor/execExpr.h
> Hunk #1 succeeded at 160 (offset 1 line).
> Hunk #2 succeeded at 382 (offset 2 lines).
> Hunk #3 FAILED at 778.
> 1 out of 3 hunks FAILED -- saving rejects to file
> src/include/executor/execExpr.h.rej
> patching file src/include/nodes/execnodes.h
> Hunk #1 succeeded at 959 (offset 7 lines).
>
> Please have a look and post an updated version.
>
> [1] - http://cfbot.cputube.org/patch_46_4209.log
>
> Regards,
> Vignesh

Thank you!

I fixed it. The code remains the same.

-- 
Regards,
Alena Rybakina
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company


Attachments:

  [text/x-patch] v3-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch (12.9K, ../../[email protected]/2-v3-0001-WIP-Evaluate-arguments-of-correlated-SubPlans-in-the.patch)
  download | inline diff:
From bf40b14c0cb63f47280299fd3f76a1711db6aada Mon Sep 17 00:00:00 2001
From: Alena Rybakina <[email protected]>
Date: Sun, 28 Jan 2024 11:58:44 +0300
Subject: [PATCH] WIP: Evaluate arguments of correlated SubPlans in the
 referencing ExprState.

---
 src/backend/executor/execExpr.c       | 93 +++++++++++++++++----------
 src/backend/executor/execExprInterp.c | 22 +++++++
 src/backend/executor/execProcnode.c   |  5 ++
 src/backend/executor/nodeSubplan.c    | 30 ++++-----
 src/backend/jit/llvm/llvmjit_expr.c   |  6 ++
 src/backend/jit/llvm/llvmjit_types.c  |  1 +
 src/include/executor/execExpr.h       |  6 +-
 src/include/nodes/execnodes.h         |  1 -
 8 files changed, 112 insertions(+), 52 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 3181b1136a2..d2e539e7b28 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -88,6 +88,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 								  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
 								  int transno, int setno, int setoff, bool ishash,
 								  bool nullcheck);
+static void ExecInitSubPlanExpr(SubPlan *subplan,
+								ExprState *state,
+								Datum *resv, bool *resnull);
 
 
 /*
@@ -1386,7 +1389,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_SubPlan:
 			{
 				SubPlan    *subplan = (SubPlan *) node;
-				SubPlanState *sstate;
 
 				/*
 				 * Real execution of a MULTIEXPR SubPlan has already been
@@ -1403,19 +1405,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					break;
 				}
 
-				if (!state->parent)
-					elog(ERROR, "SubPlan found with no parent plan");
-
-				sstate = ExecInitSubPlan(subplan, state->parent);
-
-				/* add SubPlanState nodes to state->parent->subPlan */
-				state->parent->subPlan = lappend(state->parent->subPlan,
-												 sstate);
-
-				scratch.opcode = EEOP_SUBPLAN;
-				scratch.d.subplan.sstate = sstate;
-
-				ExprEvalPushStep(state, &scratch);
+				ExecInitSubPlanExpr(subplan, state, resv, resnull);
 				break;
 			}
 
@@ -2752,29 +2742,12 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
 	foreach(lc, info->multiexpr_subplans)
 	{
 		SubPlan    *subplan = (SubPlan *) lfirst(lc);
-		SubPlanState *sstate;
 
 		Assert(subplan->subLinkType == MULTIEXPR_SUBLINK);
 
-		/* This should match what ExecInitExprRec does for other SubPlans: */
-
-		if (!state->parent)
-			elog(ERROR, "SubPlan found with no parent plan");
-
-		sstate = ExecInitSubPlan(subplan, state->parent);
-
-		/* add SubPlanState nodes to state->parent->subPlan */
-		state->parent->subPlan = lappend(state->parent->subPlan,
-										 sstate);
-
-		scratch.opcode = EEOP_SUBPLAN;
-		scratch.d.subplan.sstate = sstate;
-
 		/* The result can be ignored, but we better put it somewhere */
-		scratch.resvalue = &state->resvalue;
-		scratch.resnull = &state->resnull;
-
-		ExprEvalPushStep(state, &scratch);
+		ExecInitSubPlanExpr(subplan, state,
+							&state->resvalue, &state->resnull);
 	}
 }
 
@@ -4181,3 +4154,57 @@ ExecBuildParamSetEqual(TupleDesc desc,
 
 	return state;
 }
+
+static void
+ExecInitSubPlanExpr(SubPlan *subplan,
+					ExprState *state,
+					Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch = {0};
+	SubPlanState *sstate;
+	ListCell   *pvar;
+	ListCell   *l;
+
+	if (!state->parent)
+		elog(ERROR, "SubPlan found with no parent plan");
+
+	/*
+	 * Generate steps to evaluate input arguments for the subplan.
+	 *
+	 * We evaluate the argument expressions into ExprState's resvalue/resnull,
+	 * and then use PARAM_SET to update the parameter. We do that, instead of
+	 * evaluating directly into the param, to avoid depending on the pointer
+	 * value remaining stable / being included in the generated expression. No
+	 * danger of conflicts with other uses of resvalue/resnull as storing and
+	 * using the value always is in subsequent steps.
+	 *
+	 * Any calculation we have to do can be done in the parent econtext, since
+	 * the Param values don't need to have per-query lifetime.
+	 */
+	forboth(l, subplan->parParam, pvar, subplan->args)
+	{
+		int			paramid = lfirst_int(l);
+
+		ExecInitExprRec(lfirst(pvar), state,
+						&state->resvalue, &state->resnull);
+
+		scratch.opcode = EEOP_PARAM_SET;
+		scratch.d.param.paramid = paramid;
+		/* type isn't needed, but an old value could be confusing */
+		scratch.d.param.paramtype = InvalidOid;
+		ExprEvalPushStep(state, &scratch);
+	}
+
+	sstate = ExecInitSubPlan(subplan, state->parent);
+
+	/* add SubPlanState nodes to state->parent->subPlan */
+	state->parent->subPlan = lappend(state->parent->subPlan,
+									 sstate);
+
+	scratch.opcode = EEOP_SUBPLAN;
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+	scratch.d.subplan.sstate = sstate;
+
+	ExprEvalPushStep(state, &scratch);
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 3f20f1dd314..edab4ed357b 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -450,6 +450,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_SET,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1089,6 +1090,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_SET)
+		{
+			/* out of line, unlikely to matter performancewise */
+			ExecEvalParamSet(state, op, econtext);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
@@ -2521,6 +2529,20 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Set value of a param (currently always PARAM_EXEC) from
+ * state->res{value,null}.
+ */
+void
+ExecEvalParamSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamExecData *prm;
+
+	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
+	prm->value = state->resvalue;
+	prm->isnull = state->resnull;
+}
+
 /*
  * Evaluate a CoerceViaIO node in soft-error mode.
  *
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 6e48062f56a..34f28dfecea 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -393,6 +393,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 	/*
 	 * Initialize any initPlans present in this node.  The planner put them in
 	 * a separate list for us.
+	 *
+	 * The defining characteristic of initplans is that they don't have
+	 * arguments, so we don't need to evaluate them (in contrast to
+	 * ExecInitSubPlanExpr()).
 	 */
 	subps = NIL;
 	foreach(l, node->initPlan)
@@ -401,6 +405,7 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 		SubPlanState *sstate;
 
 		Assert(IsA(subplan, SubPlan));
+		Assert(subplan->args == NIL);
 		sstate = ExecInitSubPlan(subplan, result);
 		subps = lappend(subps, sstate);
 	}
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 9697b1f396d..2335a828f4f 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -107,7 +107,7 @@ ExecHashSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 
 	/* Shouldn't have any direct correlation Vars */
-	if (subplan->parParam != NIL || node->args != NIL)
+	if (subplan->parParam != NIL || subplan->args != NIL)
 		elog(ERROR, "hashed subplan with direct correlation not supported");
 
 	/*
@@ -231,7 +231,6 @@ ExecScanSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 	Datum		result;
 	bool		found = false;	/* true if got at least one subplan tuple */
-	ListCell   *pvar;
 	ListCell   *l;
 	ArrayBuildStateAny *astate = NULL;
 
@@ -248,26 +247,20 @@ ExecScanSubPlan(SubPlanState *node,
 	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 
 	/*
-	 * Set Params of this plan from parent plan correlation values. (Any
-	 * calculation we have to do is done in the parent econtext, since the
-	 * Param values don't need to have per-query lifetime.)
+	 * We rely on the caller to evaluate plan correlation values, if
+	 * necessary. However we still need to record the fact that the values
+	 * (might have) changed, otherwise the ExecReScan() below won't know that
+	 * nodes need to be rescanned.
 	 */
-	Assert(list_length(subplan->parParam) == list_length(node->args));
-
-	forboth(l, subplan->parParam, pvar, node->args)
+	Assert(list_length(subplan->parParam) == list_length(subplan->args));
+	foreach(l, subplan->parParam)
 	{
 		int			paramid = lfirst_int(l);
-		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-		prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
-											   econtext,
-											   &(prm->isnull));
 		planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
 	}
 
-	/*
-	 * Now that we've set up its parameters, we can reset the subplan.
-	 */
+	/* with that done, we can reset the subplan */
 	ExecReScan(planstate);
 
 	/*
@@ -817,6 +810,10 @@ slotNoNulls(TupleTableSlot *slot)
  * as well as regular SubPlans.  Note that we don't link the SubPlan into
  * the parent's subPlan list, because that shouldn't happen for InitPlans.
  * Instead, ExecInitExpr() does that one part.
+ *
+ * We also rely on ExecInitExpr(), more precisely ExecInitSubPlanExpr(), to
+ * evaluate input parameters, as that allows them to be evaluated as part of
+ * the expression referencing the SubPlan.
  * ----------------------------------------------------------------
  */
 SubPlanState *
@@ -844,7 +841,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 
 	/* Initialize subexpressions */
 	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
-	sstate->args = ExecInitExprList(subplan->args, parent);
 
 	/*
 	 * initialize my state
@@ -1107,7 +1103,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		elog(ERROR, "ANY/ALL subselect unsupported as initplan");
 	if (subLinkType == CTE_SUBLINK)
 		elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
-	if (subplan->parParam || node->args)
+	if (subplan->parParam || subplan->args)
 		elog(ERROR, "correlated subplans should not be executed via ExecSetParamPlan");
 
 	/*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 0c448422e20..88eee19c50e 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1145,6 +1145,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_PARAM_SET:
+				build_EvalXFunc(b, mod, "ExecEvalParamSet",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_SBSREF_SUBSCRIPTS:
 				{
 					int			jumpdone = op->d.sbsref_subscript.jumpdone;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 47c9daf4023..9fc525cbe45 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -159,6 +159,7 @@ void	   *referenced_functions[] =
 	ExecEvalNextValueExpr,
 	ExecEvalParamExec,
 	ExecEvalParamExtern,
+	ExecEvalParamSet,
 	ExecEvalRow,
 	ExecEvalRowNotNull,
 	ExecEvalRowNull,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index a28ddcdd771..09c48a7ba92 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -160,6 +160,8 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	/* set PARAM_EXEC value */
+	EEOP_PARAM_SET,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -380,7 +382,7 @@ typedef struct ExprEvalStep
 			ExprEvalRowtypeCache rowcache;
 		}			nulltest_row;
 
-		/* for EEOP_PARAM_EXEC/EXTERN */
+		/* for EEOP_PARAM_EXEC/EXTERN and EEOP_PARAM_SET */
 		struct
 		{
 			int			paramid;	/* numeric ID for parameter */
@@ -777,6 +779,8 @@ extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 										 ExprContext *econtext);
 extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
+extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
+							 ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
 extern void ExecEvalCoerceViaIOSafe(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 444a5f0fd57..ce93ed451a6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -959,7 +959,6 @@ typedef struct SubPlanState
 	struct PlanState *planstate;	/* subselect plan's state tree */
 	struct PlanState *parent;	/* parent plan node's state tree */
 	ExprState  *testexpr;		/* state of combining expression */
-	List	   *args;			/* states of argument expression(s) */
 	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
 	Datum		curArray;		/* most recent array from ARRAY() subplan */
 	/* these are used when hashing the subselect's output: */
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-07-18 20:01  Tom Lane <[email protected]>
  parent: Alena Rybakina <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Tom Lane @ 2024-07-18 20:01 UTC (permalink / raw)
  To: Alena Rybakina <[email protected]>; +Cc: vignesh C <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

Alena Rybakina <[email protected]> writes:
> I fixed it. The code remains the same.

I see the cfbot is again complaining that this patch doesn't apply.

In hopes of pushing this over the finish line, I fixed up the (minor)
patch conflict and also addressed the cosmetic complaints I had
upthread [1].  I think the attached v4 is committable.  If Andres is
too busy, I can push it, but really it's his patch ...

(BTW, I see no need for additional test cases.  Coverage checks show
that all this code is reached during the core regression tests.)

			regards, tom lane

[1] https://www.postgresql.org/message-id/2618533.1677874158%40sss.pgh.pa.us



Attachments:

  [text/x-diff] v4-0001-Evaluate-arguments-of-correlated-SubPlans-in-the.patch (12.9K, ../../[email protected]/2-v4-0001-Evaluate-arguments-of-correlated-SubPlans-in-the.patch)
  download | inline diff:
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index ccd4863778..9a13825161 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -69,6 +69,9 @@ static void ExecInitExprRec(Expr *node, ExprState *state,
 static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
 						 Oid funcid, Oid inputcollid,
 						 ExprState *state);
+static void ExecInitSubPlanExpr(SubPlan *subplan,
+								ExprState *state,
+								Datum *resv, bool *resnull);
 static void ExecCreateExprSetupSteps(ExprState *state, Node *node);
 static void ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info);
 static bool expr_setup_walker(Node *node, ExprSetupInfo *info);
@@ -1405,7 +1408,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 		case T_SubPlan:
 			{
 				SubPlan    *subplan = (SubPlan *) node;
-				SubPlanState *sstate;
 
 				/*
 				 * Real execution of a MULTIEXPR SubPlan has already been
@@ -1422,19 +1424,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					break;
 				}
 
-				if (!state->parent)
-					elog(ERROR, "SubPlan found with no parent plan");
-
-				sstate = ExecInitSubPlan(subplan, state->parent);
-
-				/* add SubPlanState nodes to state->parent->subPlan */
-				state->parent->subPlan = lappend(state->parent->subPlan,
-												 sstate);
-
-				scratch.opcode = EEOP_SUBPLAN;
-				scratch.d.subplan.sstate = sstate;
-
-				ExprEvalPushStep(state, &scratch);
+				ExecInitSubPlanExpr(subplan, state, resv, resnull);
 				break;
 			}
 
@@ -2714,6 +2704,70 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	}
 }
 
+/*
+ * Append the steps necessary for the evaluation of a SubPlan node to
+ * ExprState->steps.
+ *
+ * subplan - SubPlan expression to evaluate
+ * state - ExprState to whose ->steps to append the necessary operations
+ * resv / resnull - where to store the result of the node into
+ */
+static void
+ExecInitSubPlanExpr(SubPlan *subplan,
+					ExprState *state,
+					Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch = {0};
+	SubPlanState *sstate;
+	ListCell   *pvar;
+	ListCell   *l;
+
+	if (!state->parent)
+		elog(ERROR, "SubPlan found with no parent plan");
+
+	/*
+	 * Generate steps to evaluate input arguments for the subplan.
+	 *
+	 * We evaluate the argument expressions into ExprState's resvalue/resnull,
+	 * and then use PARAM_SET to update the parameter. We do that, instead of
+	 * evaluating directly into the param, to avoid depending on the pointer
+	 * value remaining stable / being included in the generated expression. No
+	 * danger of conflicts with other uses of resvalue/resnull as storing and
+	 * using the value always is in subsequent steps.
+	 *
+	 * Any calculation we have to do can be done in the parent econtext, since
+	 * the Param values don't need to have per-query lifetime.
+	 */
+	Assert(list_length(subplan->parParam) == list_length(subplan->args));
+	forboth(l, subplan->parParam, pvar, subplan->args)
+	{
+		int			paramid = lfirst_int(l);
+		Expr	   *arg = (Expr *) lfirst(pvar);
+
+		ExecInitExprRec(arg, state,
+						&state->resvalue, &state->resnull);
+
+		scratch.opcode = EEOP_PARAM_SET;
+		scratch.d.param.paramid = paramid;
+		/* paramtype's not actually used, but we might as well fill it */
+		scratch.d.param.paramtype = exprType((Node *) arg);
+		ExprEvalPushStep(state, &scratch);
+	}
+
+	sstate = ExecInitSubPlan(subplan, state->parent);
+
+	/* add SubPlanState nodes to state->parent->subPlan */
+	state->parent->subPlan = lappend(state->parent->subPlan,
+									 sstate);
+
+	scratch.opcode = EEOP_SUBPLAN;
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+	scratch.d.subplan.sstate = sstate;
+
+	ExprEvalPushStep(state, &scratch);
+}
+
 /*
  * Add expression steps performing setup that's needed before any of the
  * main execution of the expression.
@@ -2788,29 +2842,12 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
 	foreach(lc, info->multiexpr_subplans)
 	{
 		SubPlan    *subplan = (SubPlan *) lfirst(lc);
-		SubPlanState *sstate;
 
 		Assert(subplan->subLinkType == MULTIEXPR_SUBLINK);
 
-		/* This should match what ExecInitExprRec does for other SubPlans: */
-
-		if (!state->parent)
-			elog(ERROR, "SubPlan found with no parent plan");
-
-		sstate = ExecInitSubPlan(subplan, state->parent);
-
-		/* add SubPlanState nodes to state->parent->subPlan */
-		state->parent->subPlan = lappend(state->parent->subPlan,
-										 sstate);
-
-		scratch.opcode = EEOP_SUBPLAN;
-		scratch.d.subplan.sstate = sstate;
-
 		/* The result can be ignored, but we better put it somewhere */
-		scratch.resvalue = &state->resvalue;
-		scratch.resnull = &state->resnull;
-
-		ExprEvalPushStep(state, &scratch);
+		ExecInitSubPlanExpr(subplan, state,
+							&state->resvalue, &state->resnull);
 	}
 }
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d8735286c4..acdd918cbb 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -450,6 +450,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_PARAM_EXEC,
 		&&CASE_EEOP_PARAM_EXTERN,
 		&&CASE_EEOP_PARAM_CALLBACK,
+		&&CASE_EEOP_PARAM_SET,
 		&&CASE_EEOP_CASE_TESTVAL,
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
@@ -1093,6 +1094,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_PARAM_SET)
+		{
+			/* out of line, unlikely to matter performancewise */
+			ExecEvalParamSet(state, op, econtext);
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_CASE_TESTVAL)
 		{
 			/*
@@ -2555,6 +2563,24 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 errmsg("no value found for parameter %d", paramId)));
 }
 
+/*
+ * Set value of a param (currently always PARAM_EXEC) from
+ * state->res{value,null}.
+ */
+void
+ExecEvalParamSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamExecData *prm;
+
+	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
+
+	/* Shouldn't have a pending evaluation anymore */
+	Assert(prm->execPlan == NULL);
+
+	prm->value = state->resvalue;
+	prm->isnull = state->resnull;
+}
+
 /*
  * Evaluate a CoerceViaIO node in soft-error mode.
  *
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 6e48062f56..34f28dfece 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -393,6 +393,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 	/*
 	 * Initialize any initPlans present in this node.  The planner put them in
 	 * a separate list for us.
+	 *
+	 * The defining characteristic of initplans is that they don't have
+	 * arguments, so we don't need to evaluate them (in contrast to
+	 * ExecInitSubPlanExpr()).
 	 */
 	subps = NIL;
 	foreach(l, node->initPlan)
@@ -401,6 +405,7 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 		SubPlanState *sstate;
 
 		Assert(IsA(subplan, SubPlan));
+		Assert(subplan->args == NIL);
 		sstate = ExecInitSubPlan(subplan, result);
 		subps = lappend(subps, sstate);
 	}
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 9697b1f396..a96cdd01e1 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -107,7 +107,7 @@ ExecHashSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 
 	/* Shouldn't have any direct correlation Vars */
-	if (subplan->parParam != NIL || node->args != NIL)
+	if (subplan->parParam != NIL || subplan->args != NIL)
 		elog(ERROR, "hashed subplan with direct correlation not supported");
 
 	/*
@@ -231,7 +231,6 @@ ExecScanSubPlan(SubPlanState *node,
 	TupleTableSlot *slot;
 	Datum		result;
 	bool		found = false;	/* true if got at least one subplan tuple */
-	ListCell   *pvar;
 	ListCell   *l;
 	ArrayBuildStateAny *astate = NULL;
 
@@ -248,26 +247,19 @@ ExecScanSubPlan(SubPlanState *node,
 	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 
 	/*
-	 * Set Params of this plan from parent plan correlation values. (Any
-	 * calculation we have to do is done in the parent econtext, since the
-	 * Param values don't need to have per-query lifetime.)
+	 * We rely on the caller to evaluate plan correlation values, if
+	 * necessary. However we still need to record the fact that the values
+	 * (might have) changed, otherwise the ExecReScan() below won't know that
+	 * nodes need to be rescanned.
 	 */
-	Assert(list_length(subplan->parParam) == list_length(node->args));
-
-	forboth(l, subplan->parParam, pvar, node->args)
+	foreach(l, subplan->parParam)
 	{
 		int			paramid = lfirst_int(l);
-		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-		prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
-											   econtext,
-											   &(prm->isnull));
 		planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
 	}
 
-	/*
-	 * Now that we've set up its parameters, we can reset the subplan.
-	 */
+	/* with that done, we can reset the subplan */
 	ExecReScan(planstate);
 
 	/*
@@ -817,6 +809,10 @@ slotNoNulls(TupleTableSlot *slot)
  * as well as regular SubPlans.  Note that we don't link the SubPlan into
  * the parent's subPlan list, because that shouldn't happen for InitPlans.
  * Instead, ExecInitExpr() does that one part.
+ *
+ * We also rely on ExecInitExpr(), more precisely ExecInitSubPlanExpr(), to
+ * evaluate input parameters, as that allows them to be evaluated as part of
+ * the expression referencing the SubPlan.
  * ----------------------------------------------------------------
  */
 SubPlanState *
@@ -844,7 +840,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 
 	/* Initialize subexpressions */
 	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
-	sstate->args = ExecInitExprList(subplan->args, parent);
 
 	/*
 	 * initialize my state
@@ -1107,7 +1102,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		elog(ERROR, "ANY/ALL subselect unsupported as initplan");
 	if (subLinkType == CTE_SUBLINK)
 		elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
-	if (subplan->parParam || node->args)
+	if (subplan->parParam || subplan->args)
 		elog(ERROR, "correlated subplans should not be executed via ExecSetParamPlan");
 
 	/*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index cbd9ed7cc4..27f94f9007 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1145,6 +1145,12 @@ llvm_compile_expr(ExprState *state)
 					break;
 				}
 
+			case EEOP_PARAM_SET:
+				build_EvalXFunc(b, mod, "ExecEvalParamSet",
+								v_state, op, v_econtext);
+				LLVMBuildBr(b, opblocks[opno + 1]);
+				break;
+
 			case EEOP_SBSREF_SUBSCRIPTS:
 				{
 					int			jumpdone = op->d.sbsref_subscript.jumpdone;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index f93c383fd5..4a9e077014 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -160,6 +160,7 @@ void	   *referenced_functions[] =
 	ExecEvalNextValueExpr,
 	ExecEvalParamExec,
 	ExecEvalParamExtern,
+	ExecEvalParamSet,
 	ExecEvalRow,
 	ExecEvalRowNotNull,
 	ExecEvalRowNull,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 55337d4916..9f520a867a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -160,6 +160,8 @@ typedef enum ExprEvalOp
 	EEOP_PARAM_EXEC,
 	EEOP_PARAM_EXTERN,
 	EEOP_PARAM_CALLBACK,
+	/* set PARAM_EXEC value */
+	EEOP_PARAM_SET,
 
 	/* return CaseTestExpr value */
 	EEOP_CASE_TESTVAL,
@@ -384,7 +386,7 @@ typedef struct ExprEvalStep
 			ExprEvalRowtypeCache rowcache;
 		}			nulltest_row;
 
-		/* for EEOP_PARAM_EXEC/EXTERN */
+		/* for EEOP_PARAM_EXEC/EXTERN and EEOP_PARAM_SET */
 		struct
 		{
 			int			paramid;	/* numeric ID for parameter */
@@ -796,6 +798,8 @@ extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 										 ExprContext *econtext);
 extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 							  ExprContext *econtext);
+extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op,
+							 ExprContext *econtext);
 extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
 								ExprContext *econtext);
 extern void ExecEvalCoerceViaIOSafe(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cac684d9b3..c3670f7158 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -961,7 +961,6 @@ typedef struct SubPlanState
 	struct PlanState *planstate;	/* subselect plan's state tree */
 	struct PlanState *parent;	/* parent plan node's state tree */
 	ExprState  *testexpr;		/* state of combining expression */
-	List	   *args;			/* states of argument expression(s) */
 	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
 	Datum		curArray;		/* most recent array from ARRAY() subplan */
 	/* these are used when hashing the subselect's output: */


^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-07-19 19:19  Alena Rybakina <[email protected]>
  parent: Tom Lane <[email protected]>
  1 sibling, 0 replies; 40+ messages in thread

From: Alena Rybakina @ 2024-07-19 19:19 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: vignesh C <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

On 18.07.2024 23:01, Tom Lane wrote:
> Alena Rybakina <[email protected]> writes:
>> I fixed it. The code remains the same.
> I see the cfbot is again complaining that this patch doesn't apply.
>
> In hopes of pushing this over the finish line, I fixed up the (minor)
> patch conflict and also addressed the cosmetic complaints I had
> upthread [1].  I think the attached v4 is committable.  If Andres is
> too busy, I can push it, but really it's his patch ...
>
> (BTW, I see no need for additional test cases.  Coverage checks show
> that all this code is reached during the core regression tests.)
>
> 			regards, tom lane
>
> [1] https://www.postgresql.org/message-id/2618533.1677874158%40sss.pgh.pa.us
>
Thank you for your contribution! I looked at the patch again and I agree 
that it is ready to be pushed.







^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-07-20 04:17  Andres Freund <[email protected]>
  parent: Tom Lane <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Andres Freund @ 2024-07-20 04:17 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Alena Rybakina <[email protected]>; vignesh C <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

On 2024-07-18 16:01:19 -0400, Tom Lane wrote:
> Alena Rybakina <[email protected]> writes:
> > I fixed it. The code remains the same.
> 
> I see the cfbot is again complaining that this patch doesn't apply.
> 
> In hopes of pushing this over the finish line, I fixed up the (minor)
> patch conflict and also addressed the cosmetic complaints I had
> upthread [1].  I think the attached v4 is committable.  If Andres is
> too busy, I can push it, but really it's his patch ...

Thanks for the rebase - I'll try to get it pushed in the next few days!






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Evaluate arguments of correlated SubPlans in the referencing ExprState
@ 2024-08-01 03:24  Andres Freund <[email protected]>
  parent: Andres Freund <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Andres Freund @ 2024-08-01 03:24 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Alena Rybakina <[email protected]>; vignesh C <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers

Hi,

On 2024-07-19 21:17:12 -0700, Andres Freund wrote:
> On 2024-07-18 16:01:19 -0400, Tom Lane wrote:
> > Alena Rybakina <[email protected]> writes:
> > > I fixed it. The code remains the same.
> > 
> > I see the cfbot is again complaining that this patch doesn't apply.
> > 
> > In hopes of pushing this over the finish line, I fixed up the (minor)
> > patch conflict and also addressed the cosmetic complaints I had
> > upthread [1].  I think the attached v4 is committable.  If Andres is
> > too busy, I can push it, but really it's his patch ...
> 
> Thanks for the rebase - I'll try to get it pushed in the next few days!

And finally done. No code changes. I did spend some more time evaluating the
resource usage benefits actually do exist (see mail upthread).

Thanks for the reviews, rebasing and the reminders!

Greetings,

Andres






^ permalink  raw  reply  [nested|flat] 40+ messages in thread


end of thread, other threads:[~2024-08-01 03:24 UTC | newest]

Thread overview: 40+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-09-14 13:12 Re: [PATCH] Tab completion for ALTER DATABASE … SET TABLESPACE Arthur Zakirov <[email protected]>
2018-09-20 21:22 ` Tom Lane <[email protected]>
2018-09-20 21:29   ` Thomas Munro <[email protected]>
2018-09-20 22:38     ` Tom Lane <[email protected]>
2018-09-20 22:46       ` Andres Freund <[email protected]>
2018-09-20 23:03         ` Tom Lane <[email protected]>
2018-09-20 23:19           ` Andres Freund <[email protected]>
2018-09-21 05:52             ` Andres Freund <[email protected]>
2018-09-21 08:23               ` Thomas Munro <[email protected]>
2018-09-21 20:20                 ` Tom Lane <[email protected]>
2018-09-21 20:51                   ` Andres Freund <[email protected]>
2018-09-21 21:15                     ` Thomas Munro <[email protected]>
2018-09-21 21:46                       ` Andres Freund <[email protected]>
2018-09-21 21:56                         ` Thomas Munro <[email protected]>
2018-09-22 01:57                           ` Andres Freund <[email protected]>
2018-09-21 22:00                         ` Tom Lane <[email protected]>
2018-09-21 22:53                           ` Andres Freund <[email protected]>
2018-09-21 23:27                             ` Tom Lane <[email protected]>
2018-09-22 01:52                               ` Andres Freund <[email protected]>
2018-10-11 07:00 [PATCH 4/7] Add infrastructure to WAL-logging skip feature Kyotaro Horiguchi <[email protected]>
2018-10-11 07:00 [PATCH 3/4] Add infrastructure to WAL-logging skip feature Kyotaro Horiguchi <[email protected]>
2019-03-26 06:34 [PATCH 4/8] Add infrastructure to WAL-logging skip feature Kyotaro Horiguchi <[email protected]>
2019-04-02 09:05 [PATCH 5/7] Add infrastructure to WAL-logging skip feature Kyotaro Horiguchi <[email protected]>
2019-04-02 09:05 [PATCH 5/7] Add infrastructure to WAL-logging skip feature Kyotaro Horiguchi <[email protected]>
2023-03-07 00:28 Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Andres Freund <[email protected]>
2023-03-07 00:51 ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Tom Lane <[email protected]>
2023-10-01 18:41   ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Peter Eisentraut <[email protected]>
2023-10-01 18:53     ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Tom Lane <[email protected]>
2023-10-10 03:00       ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Andres Freund <[email protected]>
2023-10-23 20:14         ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Alena Rybakina <[email protected]>
2024-01-26 02:37           ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState vignesh C <[email protected]>
2024-01-28 09:07             ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Alena Rybakina <[email protected]>
2024-07-18 20:01               ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Tom Lane <[email protected]>
2024-07-19 19:19                 ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Alena Rybakina <[email protected]>
2024-07-20 04:17                 ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Andres Freund <[email protected]>
2024-08-01 03:24                   ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Andres Freund <[email protected]>
2023-11-22 08:37         ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState John Naylor <[email protected]>
2024-01-21 23:30           ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Peter Smith <[email protected]>
2024-01-22 20:47             ` Re: Evaluate arguments of correlated SubPlans in the referencing ExprState Andres Freund <[email protected]>
2023-07-26 21:35 [PATCH v4 1/1] Adjust extra lines generated by psql to be valid SQL comments. Nathan Bossart <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox