From c5c942cd2595d78ff3d97eb5378aac722e806ac9 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Wed, 29 Jul 2026 16:53:28 +1000 Subject: [PATCH v2] psql - tab-completion of pub/sub options. For commands: CREATE PUBLICATION ... WITH (options) ALTER PUBLICATION ... SET (options) CREATE SUBSCRIPTION ... WITH (options) ALTER SUBSCRIPTION ... SET (options) Now: - handles multiple options of the WITH/SET comma-separated lists - enum options show their allowed values Author: Peter Smith Reviewed-by: Hayato Kuroda (Fujitsu) Discussion: https://www.postgresql.org/message-id/flat/OS9PR01MB121493EC5F804CBF8BC5E47F7F5CB2%40OS9PR01MB12149.jpnprd01.prod.outlook.com#3e9996f9509f977031fbb94c910600aa --- src/bin/psql/tab-complete.in.c | 170 +++++++++++++++++++++++++++++---- 1 file changed, 150 insertions(+), 20 deletions(-) diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 1cacc8c3ea2..6d30f882572 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2341,9 +2341,24 @@ match_previous_words(int pattern_id, COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas " AND nspname NOT LIKE E'pg\\\\_%%'", "CURRENT_SCHEMA"); - /* ALTER PUBLICATION SET ( */ - else if (Matches("ALTER", "PUBLICATION", MatchAny, MatchAnyN, "SET", "(")) - COMPLETE_WITH("publish", "publish_generated_columns", "publish_via_partition_root"); + /* ALTER PUBLICATION SET ( */ + else if (HeadMatches("ALTER", "PUBLICATION", MatchAny, "SET", "(*") && + !HeadMatches("ALTER", "PUBLICATION", MatchAny, "SET", "(*)")) + { + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + COMPLETE_WITH("publish =", "publish_generated_columns =", + "publish_via_partition_root"); + + /* Complete "ALTER PUBLICATION SET ( =" */ + + else if (TailMatches("publish")) + COMPLETE_WITH("="); + + else if (TailMatches("publish_generated_columns")) + COMPLETE_WITH("="); + else if (TailMatches("publish_generated_columns", "=")) + COMPLETE_WITH("none", "stored"); + } /* ALTER SUBSCRIPTION */ else if (Matches("ALTER", "SUBSCRIPTION", MatchAny)) COMPLETE_WITH("CONNECTION", "ENABLE", "DISABLE", "OWNER TO", @@ -2361,13 +2376,49 @@ match_previous_words(int pattern_id, else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, "SET")) COMPLETE_WITH("(", "PUBLICATION"); /* ALTER SUBSCRIPTION SET ( */ - else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "SET", "(")) - COMPLETE_WITH("binary", "conflict_log_destination", "disable_on_error", - "failover", "max_retention_duration", "origin", + else if (HeadMatches("ALTER", "SUBSCRIPTION", MatchAny, "SET", "(*") && + !HeadMatches("ALTER", "SUBSCRIPTION", MatchAny, "SET", "(*)")) + { + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + COMPLETE_WITH("binary", "conflict_log_destination =", + "disable_on_error", "failover", + "max_retention_duration =", "origin =", "password_required", "retain_dead_tuples", - "run_as_owner", "slot_name", "streaming", - "synchronous_commit", "two_phase", - "wal_receiver_timeout"); + "run_as_owner", "slot_name =", "streaming =", + "synchronous_commit =", "two_phase", + "wal_receiver_timeout ="); + + /* ALTER SUBSCRIPTION SET ( = */ + + else if (TailMatches("conflict_log_destination")) + COMPLETE_WITH("="); + else if (TailMatches("conflict_log_destination", "=")) + COMPLETE_WITH("all", "log", "table"); + + else if (TailMatches("max_retention_duration")) + COMPLETE_WITH("="); + + else if (TailMatches("origin")) + COMPLETE_WITH("="); + else if (TailMatches("origin", "=")) + COMPLETE_WITH("any", "none"); + + else if (TailMatches("slot_name")) + COMPLETE_WITH("="); + + else if (TailMatches("streaming")) + COMPLETE_WITH("="); + else if (TailMatches("streaming", "=")) + COMPLETE_WITH("off", "on", "parallel"); + + else if (TailMatches("synchronous_commit")) + COMPLETE_WITH("="); + else if (TailMatches("synchronous_commit", "=")) + COMPLETE_WITH("local", "off", "on", "remote_apply", "remote_write"); + + else if (TailMatches("wal_receiver_timeout")) + COMPLETE_WITH("="); + } /* ALTER SUBSCRIPTION SKIP ( */ else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "SKIP", "(")) COMPLETE_WITH("lsn"); @@ -3776,9 +3827,37 @@ match_previous_words(int pattern_id, "CURRENT_SCHEMA"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ','))) COMPLETE_WITH("WITH ("); - /* Complete "CREATE PUBLICATION [...] WITH" */ - else if (Matches("CREATE", "PUBLICATION", MatchAnyN, "WITH", "(")) - COMPLETE_WITH("publish", "publish_generated_columns", "publish_via_partition_root"); + /* Complete "CREATE PUBLICATION ... WITH ( " */ + else if (HeadMatches("CREATE", "PUBLICATION")) + { + bool seen_with = false; + + for (int i = 0; i < previous_words_count; i++) + { + if (pg_strcasecmp(previous_words[i], "WITH") == 0) + { + seen_with = true; + break; + } + } + + if (seen_with) + { + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + COMPLETE_WITH("publish =", "publish_generated_columns =", + "publish_via_partition_root"); + + /* Complete "CREATE PUBLICATION ... WITH ( =" */ + + else if (TailMatches("publish")) + COMPLETE_WITH("="); + + else if (TailMatches("publish_generated_columns")) + COMPLETE_WITH("="); + else if (TailMatches("publish_generated_columns", "=")) + COMPLETE_WITH("none", "stored"); + } + } /* CREATE RULE */ /* Complete "CREATE [ OR REPLACE ] RULE " with "AS ON" */ @@ -3945,15 +4024,66 @@ match_previous_words(int pattern_id, } else if (Matches("CREATE", "SUBSCRIPTION", MatchAnyN, "PUBLICATION", MatchAny)) COMPLETE_WITH("WITH ("); + else if (Matches("CREATE", "SUBSCRIPTION", MatchAnyN, "WITH")) + COMPLETE_WITH("("); /* Complete "CREATE SUBSCRIPTION ... WITH ( " */ - else if (Matches("CREATE", "SUBSCRIPTION", MatchAnyN, "WITH", "(")) - COMPLETE_WITH("binary", "conflict_log_destination", "connect", "copy_data", - "create_slot", "disable_on_error", "enabled", "failover", - "max_retention_duration", "origin", - "password_required", "retain_dead_tuples", - "run_as_owner", "slot_name", "streaming", - "synchronous_commit", "two_phase", - "wal_receiver_timeout"); + else if (HeadMatches("CREATE", "SUBSCRIPTION")) + { + bool seen_with = false; + + for (int i = 0; i < previous_words_count; i++) + { + if (pg_strcasecmp(previous_words[i], "WITH") == 0) + { + seen_with = true; + break; + } + } + + if (seen_with) + { + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + COMPLETE_WITH("binary", "conflict_log_destination =", + "connect", "copy_data", "create_slot", + "disable_on_error", "enabled", "failover", + "max_retention_duration =", "origin =", + "password_required", "retain_dead_tuples", + "run_as_owner", "slot_name =", "streaming =", + "synchronous_commit =", "two_phase", + "wal_receiver_timeout ="); + + /* Complete "CREATE SUBSCRIPTION ... WITH ( =" */ + + else if (TailMatches("conflict_log_destination")) + COMPLETE_WITH("="); + else if (TailMatches("conflict_log_destination", "=")) + COMPLETE_WITH("all", "log", "table"); + + else if (TailMatches("max_rentention_duration")) + COMPLETE_WITH("="); + + else if (TailMatches("origin")) + COMPLETE_WITH("="); + else if (TailMatches("origin", "=")) + COMPLETE_WITH("any", "none"); + + else if (TailMatches("slot_name")) + COMPLETE_WITH("="); + + else if (TailMatches("streaming")) + COMPLETE_WITH("="); + else if (TailMatches("streaming", "=")) + COMPLETE_WITH("off", "on", "parallel"); + + else if (TailMatches("synchronous_commit")) + COMPLETE_WITH("="); + else if (TailMatches("synchronous_commit", "=")) + COMPLETE_WITH("local", "off", "on", "remote_apply", "remote_write"); + + else if (TailMatches("wal_receiver_timeout")) + COMPLETE_WITH("="); + } + } /* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */ -- 2.47.3