psql: Improve tab completion for COPY option lists.
authorMasahiko Sawada <[email protected]>
Mon, 22 Dec 2025 22:28:12 +0000 (14:28 -0800)
committerMasahiko Sawada <[email protected]>
Mon, 22 Dec 2025 22:28:12 +0000 (14:28 -0800)
Previously, only the first option in a parenthesized option list was
suggested by tab completion. This commit enhances tab completion for
both COPY TO and COPY FROM commands to suggest options after each
comma.

Also add completion for HEADER and FREEZE option value candidates.

Author: Yugo Nagata <[email protected]>
Reviewed-by: Masahiko Sawada <[email protected]>
Discussion: https://postgr.es/m/20250605100835.b396f9d656df1018f65a4556@sraoss.co.jp

src/bin/psql/tab-complete.in.c

index b1ff6f6cd949ae1ad64d357537edc6b626900f30..ab2712216b5e50c45b012aff33cbb27aa488d9d2 100644 (file)
@@ -3378,30 +3378,50 @@ match_previous_words(int pattern_id,
             Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny))
        COMPLETE_WITH("WITH (", "WHERE");
 
-   /* Complete COPY <sth> FROM [PROGRAM] filename WITH ( */
-   else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(") ||
-            Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "("))
-       COMPLETE_WITH(Copy_from_options);
-
-   /* Complete COPY <sth> TO [PROGRAM] filename WITH ( */
-   else if (Matches("COPY|\\copy", MatchAny, "TO", MatchAnyExcept("PROGRAM"), "WITH", "(") ||
-            Matches("COPY|\\copy", MatchAny, "TO", "PROGRAM", MatchAny, "WITH", "("))
-       COMPLETE_WITH(Copy_to_options);
-
-   /* Complete COPY <sth> FROM|TO [PROGRAM] <sth> WITH (FORMAT */
-   else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(", "FORMAT") ||
-            Matches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(", "FORMAT"))
-       COMPLETE_WITH("binary", "csv", "text");
-
-   /* Complete COPY <sth> FROM [PROGRAM] filename WITH (ON_ERROR */
-   else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(", "ON_ERROR") ||
-            Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "(", "ON_ERROR"))
-       COMPLETE_WITH("stop", "ignore");
-
-   /* Complete COPY <sth> FROM [PROGRAM] filename WITH (LOG_VERBOSITY */
-   else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(", "LOG_VERBOSITY") ||
-            Matches("COPY|\\copy", MatchAny, "FROM", "PROGRAM", MatchAny, "WITH", "(", "LOG_VERBOSITY"))
-       COMPLETE_WITH("silent", "default", "verbose");
+   /* Complete COPY <sth> FROM|TO [PROGRAM] filename WITH ( */
+   else if (HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(*") ||
+            HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(*"))
+   {
+       if (!HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(*)") &&
+           !HeadMatches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(*)"))
+       {
+           /*
+            * This fires if we're in an unfinished parenthesized option list.
+            * get_previous_words treats a completed parenthesized option list
+            * as one word, so the above tests are correct.
+            */
+
+           if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+           {
+               if (HeadMatches("COPY|\\copy", MatchAny, "FROM"))
+                   COMPLETE_WITH(Copy_from_options);
+               else
+                   COMPLETE_WITH(Copy_to_options);
+           }
+
+           /* Complete COPY <sth> FROM|TO filename WITH (FORMAT */
+           else if (TailMatches("FORMAT"))
+               COMPLETE_WITH("binary", "csv", "text");
+
+           /* Complete COPY <sth> FROM|TO filename WITH (FREEZE */
+           else if (TailMatches("FREEZE"))
+               COMPLETE_WITH("true", "false");
+
+           /* Complete COPY <sth> FROM|TO filename WITH (HEADER */
+           else if (TailMatches("HEADER"))
+               COMPLETE_WITH("true", "false", "MATCH");
+
+           /* Complete COPY <sth> FROM filename WITH (ON_ERROR */
+           else if (TailMatches("ON_ERROR"))
+               COMPLETE_WITH("stop", "ignore");
+
+           /* Complete COPY <sth> FROM filename WITH (LOG_VERBOSITY */
+           else if (TailMatches("LOG_VERBOSITY"))
+               COMPLETE_WITH("silent", "default", "verbose");
+       }
+
+       /* A completed parenthesized option list should be caught below */
+   }
 
    /* Complete COPY <sth> FROM [PROGRAM] <sth> WITH (<options>) */
    else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", MatchAny) ||