From d96b4b4d3e999609608a52cb4bcf7ca7ff593920 Mon Sep 17 00:00:00 2001 From: Ishjaap Singh Date: Wed, 4 Nov 2026 20:17:11 +0000 Subject: [PATCH v1] psql: Don't offer partitioned tables after CREATE INDEX CONCURRENTLY Tab completion after CREATE INDEX CONCURRENTLY [name] ON offered the same relations as plain CREATE INDEX, including partitioned tables. CREATE INDEX CONCURRENTLY is not supported on partitioned tables, so choosing one always fails: CREATE INDEX CONCURRENTLY ON parted (a); ERROR: cannot create index on partitioned table "parted" concurrently Use a separate list without partitioned tables after CONCURRENTLY. Plain tables, materialized views and leaf partitions are still offered. --- src/bin/psql/t/010_tab_completion.pl | 41 ++++++++++++++++++++++++++++ src/bin/psql/tab-complete.in.c | 24 ++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl index 1d2e5f5b92a..1a1075a60aa 100644 --- a/src/bin/psql/t/010_tab_completion.pl +++ b/src/bin/psql/t/010_tab_completion.pl @@ -347,6 +347,47 @@ check_completion( clear_query(); +# CREATE INDEX CONCURRENTLY doesn't support partitioned tables, so they +# must not be offered after it. The two tables share a prefix: if only the +# plain one is offered, "cic_p" completes to it. +$node->safe_psql('postgres', + "CREATE TABLE cic_part (f1 int) PARTITION BY RANGE (f1);\n" + . "CREATE TABLE cic_plain (f1 int);\n"); + +# "index" is a valid index name, and must not make the command look like a +# plain CREATE INDEX. +foreach my $cmd ( + 'CREATE INDEX CONCURRENTLY ON', + 'CREATE INDEX CONCURRENTLY cic_idx ON', + 'CREATE INDEX CONCURRENTLY index ON', + 'CREATE UNIQUE INDEX CONCURRENTLY ON', + 'CREATE UNIQUE INDEX CONCURRENTLY index ON') +{ + check_completion("$cmd cic_p\t", qr/cic_plain /, + "$cmd does not offer partitioned tables"); + clear_query(); + + # A prefix that matches only the partitioned table must not complete. + # The X typed after the tab shows that the word was left alone. + check_completion("$cmd cic_pa\tX", + qr/cic_pa\a?X/, "$cmd does not complete a partitioned table"); + clear_query(); +} + +# Without CONCURRENTLY, partitioned tables are fine. +foreach my $cmd ( + 'CREATE INDEX ON', + 'CREATE INDEX cic_idx ON', + 'CREATE INDEX index ON', + 'CREATE UNIQUE INDEX ON') +{ + check_completion( + "$cmd cic_p\t\t", + qr/cic_part +cic_plain/, + "$cmd offers partitioned tables"); + clear_query(); +} + # check VersionedQuery infrastructure check_completion( "DROP PUBLIC\t \t\t", diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 96de78cd087..334f0372b9b 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -930,6 +930,17 @@ static const SchemaQuery Query_for_list_of_indexables = { .result = "c.relname", }; +/* Relations supporting CREATE INDEX CONCURRENTLY */ +static const SchemaQuery Query_for_list_of_concurrent_indexables = { + .catname = "pg_catalog.pg_class c", + .selcondition = + "c.relkind IN (" CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_MATVIEW) ")", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "c.relname", +}; + /* * Relations supporting VACUUM are currently same as those supporting * indexing. @@ -3548,10 +3559,17 @@ match_previous_words(int pattern_id, /* * Complete ... INDEX|CONCURRENTLY [] ON with a list of relations - * that indexes can be created on + * that indexes can be created on. CREATE INDEX CONCURRENTLY doesn't + * support partitioned tables, so leave those out after CONCURRENTLY. The + * plain rule must not match any CONCURRENTLY form (not even one with an + * index named "index"), or it would offer partitioned tables anyway + * whenever the CONCURRENTLY rule finds no match. */ - else if (TailMatches("INDEX|CONCURRENTLY", MatchAny, "ON") || - TailMatches("INDEX|CONCURRENTLY", "ON")) + else if (TailMatches("CONCURRENTLY", MatchAny, "ON") || + TailMatches("CONCURRENTLY", "ON")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_concurrent_indexables); + else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAnyExcept("CONCURRENTLY"), "ON") || + TailMatches("CREATE|UNIQUE", "INDEX", "ON")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables); /* -- 2.43.0