From c23641753c8c65cf838eaa75d570064412c6ab27 Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi Date: Fri, 5 Feb 2016 16:50:35 +0900 Subject: [PATCH 1/4] Suggest IF (NOT) EXISTS for tab-completion of psql This patch lets psql to suggest "IF (NOT) EXISTS". Addition to that, since this patch introduces some mechanism for syntactical robustness, it allows psql completion to omit some optional part on matching. --- src/bin/psql/tab-complete.c | 637 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 522 insertions(+), 115 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 019f75a..d957a35 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -656,6 +656,10 @@ static const SchemaQuery Query_for_list_of_matviews = { " FROM pg_catalog.pg_roles "\ " WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'" +#define Query_for_list_of_rules \ +"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules "\ +" WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'" + #define Query_for_list_of_grant_roles \ " SELECT pg_catalog.quote_ident(rolname) "\ " FROM pg_catalog.pg_roles "\ @@ -763,6 +767,11 @@ static const SchemaQuery Query_for_list_of_matviews = { "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\ " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'" +#define Query_for_list_of_triggers \ +"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger "\ +" WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND "\ +" NOT tgisinternal" + #define Query_for_list_of_fdws \ " SELECT pg_catalog.quote_ident(fdwname) "\ " FROM pg_catalog.pg_foreign_data_wrapper "\ @@ -914,7 +923,7 @@ static const pgsql_thing_t words_after_create[] = { {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW}, {"POLICY", NULL, NULL}, {"ROLE", Query_for_list_of_roles}, - {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, + {"RULE", Query_for_list_of_rules}, {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, &Query_for_list_of_sequences}, {"SERVER", Query_for_list_of_servers}, @@ -923,7 +932,7 @@ static const pgsql_thing_t words_after_create[] = { {"TEMP", NULL, NULL, THING_NO_DROP}, /* for CREATE TEMP TABLE ... */ {"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW}, {"TEXT SEARCH", NULL, NULL}, - {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"}, + {"TRIGGER", Query_for_list_of_triggers}, {"TYPE", NULL, &Query_for_list_of_datatypes}, {"UNIQUE", NULL, NULL, THING_NO_DROP}, /* for CREATE UNIQUE INDEX ... */ {"UNLOGGED", NULL, NULL, THING_NO_DROP}, /* for CREATE UNLOGGED TABLE @@ -952,6 +961,7 @@ static char **complete_from_variables(const char *text, const char *prefix, const char *suffix, bool need_value); static char *complete_from_files(const char *text, int state); +static int find_last_index_of(char *w, char **previous_words, int len); static char *pg_strdup_keyword_case(const char *s, const char *ref); static char *escape_string(const char *text); static PGresult *exec_query(const char *query); @@ -960,6 +970,7 @@ static char **get_previous_words(int point, char **buffer, int *nwords); static char *get_guctype(const char *varname); +static const pgsql_thing_t *find_thing_entry(char *word); #ifdef NOT_USED static char *quote_file_name(char *text, int match_type, char *quote_pointer); static char *dequote_file_name(char *text, char quote_char); @@ -1115,6 +1126,9 @@ psql_completion(const char *text, int start, int end) /* The number of words found on the input line. */ int previous_words_count; + /* The number of prefixing words to be ignored */ + int head_shift = 0; + /* * For compactness, we use these macros to reference previous_words[]. * Caution: do not access a previous_words[] entry without having checked @@ -1132,31 +1146,73 @@ psql_completion(const char *text, int start, int end) #define prev8_wd (previous_words[7]) #define prev9_wd (previous_words[8]) + /* Move the position of the beginning word for matching macros. */ +#define HEADSHIFT(n) \ + ((head_shift += n) || true) + + /* Return the number of stored words counting head shift */ +#define WORD_COUNT() (previous_words_count - head_shift) + + /* Return the true index in previous_words for index from the beginning */ +#define HEAD_INDEX(n) \ + (previous_words_count - head_shift - (n)) + + /* + * remove n words from current shifted position, see MidMatchAndRevmove2 + * for the reason for the return value + */ +#define COLLAPSE(n) \ + ((memmove(previous_words + HEAD_INDEX(n), previous_words + HEAD_INDEX(0), \ + sizeof(char *) * head_shift), \ + previous_words_count -= (n)) && false) + + /* + * Find the position the specified word occurs last and shift to there. + * This is used to ignore the words before there. + */ +#define SHIFT_TO_LAST1(p1) \ + (HEADSHIFT(find_last_index_of(p1, previous_words, previous_words_count))|| \ + true) + + /* + * Remove the specified words if they match from the sth word in + * previous_words. This is a bit tricky, COLLAPSE is skipped when + * HeadMatches failed but the last HEADSHIFT anyway should be done. + */ +#define MidMatchAndRemove1(s, p1) \ + ((HEADSHIFT(s) && HeadMatches1(p1) && COLLAPSE(1)) || HEADSHIFT(-s)) + +#define MidMatchAndRemove2(s, p1, p2) \ + ((HEADSHIFT(s) && HeadMatches2(p1, p2) && COLLAPSE(2)) || HEADSHIFT(-s)) + +#define MidMatchAndRemove3(s, p1, p2, p3) \ + ((HEADSHIFT(s) && HeadMatches3(p1, p2, p3) && COLLAPSE(3)) || HEADSHIFT(-s)) + /* Macros for matching the last N words before point, case-insensitively. */ #define TailMatches1(p1) \ - (previous_words_count >= 1 && \ + (WORD_COUNT() >= 1 && \ word_matches(p1, prev_wd)) #define TailMatches2(p2, p1) \ - (previous_words_count >= 2 && \ + (WORD_COUNT() >= 2 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd)) #define TailMatches3(p3, p2, p1) \ - (previous_words_count >= 3 && \ + (WORD_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_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_COUNT() >= 5 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd) && \ word_matches(p3, prev3_wd) && \ @@ -1164,7 +1220,7 @@ psql_completion(const char *text, int start, int end) word_matches(p5, prev5_wd)) #define TailMatches6(p6, p5, p4, p3, p2, p1) \ - (previous_words_count >= 6 && \ + (WORD_COUNT() >= 6 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd) && \ word_matches(p3, prev3_wd) && \ @@ -1173,7 +1229,7 @@ psql_completion(const char *text, int start, int end) word_matches(p6, prev6_wd)) #define TailMatches7(p7, p6, p5, p4, p3, p2, p1) \ - (previous_words_count >= 7 && \ + (WORD_COUNT() >= 7 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd) && \ word_matches(p3, prev3_wd) && \ @@ -1183,7 +1239,7 @@ psql_completion(const char *text, int start, int end) word_matches(p7, prev7_wd)) #define TailMatches8(p8, p7, p6, p5, p4, p3, p2, p1) \ - (previous_words_count >= 8 && \ + (WORD_COUNT() >= 8 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd) && \ word_matches(p3, prev3_wd) && \ @@ -1194,7 +1250,7 @@ psql_completion(const char *text, int start, int end) word_matches(p8, prev8_wd)) #define TailMatches9(p9, p8, p7, p6, p5, p4, p3, p2, p1) \ - (previous_words_count >= 9 && \ + (WORD_COUNT() >= 9 && \ word_matches(p1, prev_wd) && \ word_matches(p2, prev2_wd) && \ word_matches(p3, prev3_wd) && \ @@ -1207,43 +1263,43 @@ psql_completion(const char *text, int start, int end) /* Macros for matching the last N words before point, case-sensitively. */ #define TailMatchesCS1(p1) \ - (previous_words_count >= 1 && \ + (WORD_COUNT() >= 1 && \ word_matches_cs(p1, prev_wd)) #define TailMatchesCS2(p2, p1) \ - (previous_words_count >= 2 && \ + (WORD_COUNT() >= 2 && \ word_matches_cs(p1, prev_wd) && \ word_matches_cs(p2, prev2_wd)) /* - * Macros for matching N words beginning at the start of the line, + * Macros for matching N words exactly to the line, * case-insensitively. */ #define Matches1(p1) \ - (previous_words_count == 1 && \ + (WORD_COUNT() == 1 && \ TailMatches1(p1)) #define Matches2(p1, p2) \ - (previous_words_count == 2 && \ + (WORD_COUNT() == 2 && \ TailMatches2(p1, p2)) #define Matches3(p1, p2, p3) \ - (previous_words_count == 3 && \ + (WORD_COUNT() == 3 && \ TailMatches3(p1, p2, p3)) #define Matches4(p1, p2, p3, p4) \ - (previous_words_count == 4 && \ + (WORD_COUNT() == 4 && \ TailMatches4(p1, p2, p3, p4)) #define Matches5(p1, p2, p3, p4, p5) \ - (previous_words_count == 5 && \ + (WORD_COUNT() == 5 && \ TailMatches5(p1, p2, p3, p4, p5)) #define Matches6(p1, p2, p3, p4, p5, p6) \ - (previous_words_count == 6 && \ + (WORD_COUNT() == 6 && \ TailMatches6(p1, p2, p3, p4, p5, p6)) #define Matches7(p1, p2, p3, p4, p5, p6, p7) \ - (previous_words_count == 7 && \ + (WORD_COUNT() == 7 && \ TailMatches7(p1, p2, p3, p4, p5, p6, p7)) #define Matches8(p1, p2, p3, p4, p5, p6, p7, p8) \ - (previous_words_count == 8 && \ + (WORD_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 && \ + (WORD_COUNT() == 9 && \ TailMatches9(p1, p2, p3, p4, p5, p6, p7, p8, p9)) /* @@ -1251,19 +1307,53 @@ psql_completion(const char *text, int start, int end) * what is after them, case-insensitively. */ #define HeadMatches1(p1) \ - (previous_words_count >= 1 && \ - word_matches(p1, previous_words[previous_words_count - 1])) + (HEAD_INDEX(1) >=0 && \ + word_matches(p1, previous_words[HEAD_INDEX(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])) + (HEAD_INDEX(2) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(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])) + (HEAD_INDEX(3) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(2)]) && \ + word_matches(p3, previous_words[HEAD_INDEX(3)])) + +#define HeadMatches4(p1, p2, p3, p4) \ + (HEAD_INDEX(4) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(2)]) && \ + word_matches(p3, previous_words[HEAD_INDEX(3)]) && \ + word_matches(p4, previous_words[HEAD_INDEX(4)])) + +#define HeadMatches5(p1, p2, p3, p4, p5) \ + (HEAD_INDEX(5) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(2)]) && \ + word_matches(p3, previous_words[HEAD_INDEX(3)]) && \ + word_matches(p4, previous_words[HEAD_INDEX(4)]) && \ + word_matches(p5, previous_words[HEAD_INDEX(5)])) + +#define HeadMatches6(p1, p2, p3, p4, p5, p6) \ + (HEAD_INDEX(6) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(2)]) && \ + word_matches(p3, previous_words[HEAD_INDEX(3)]) && \ + word_matches(p4, previous_words[HEAD_INDEX(4)]) && \ + word_matches(p5, previous_words[HEAD_INDEX(5)]) && \ + word_matches(p6, previous_words[HEAD_INDEX(6)])) + +#define HeadMatches7(p1, p2, p3, p4, p5, p6, p7) \ + (HEAD_INDEX(7) >= 0 && \ + word_matches(p1, previous_words[HEAD_INDEX(1)]) && \ + word_matches(p2, previous_words[HEAD_INDEX(2)]) && \ + word_matches(p3, previous_words[HEAD_INDEX(3)]) && \ + word_matches(p4, previous_words[HEAD_INDEX(4)]) && \ + word_matches(p5, previous_words[HEAD_INDEX(5)]) && \ + word_matches(p6, previous_words[HEAD_INDEX(6)]) && \ + word_matches(p7, previous_words[HEAD_INDEX(7)])) /* Known command-starting keywords. */ static const char *const sql_commands[] = { @@ -1336,9 +1426,16 @@ psql_completion(const char *text, int start, int end) else if (previous_words_count == 0) COMPLETE_WITH_LIST(sql_commands); + /* + * If this is in CREATE SCHEMA, seek to the last CREATE and regard it as + * current command to complete. + */ + else if (HeadMatches2("CREATE", "SCHEMA") && + SHIFT_TO_LAST1("CREATE") && + false) {} /* FALL THROUGH */ /* CREATE */ /* complete with something you can create */ - else if (TailMatches1("CREATE")) + else if (Matches1("CREATE")) matches = completion_matches(text, create_command_generator); /* DROP, but not DROP embedded in other commands */ @@ -1351,7 +1448,13 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE */ else if (Matches2("ALTER", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, + "UNION SELECT 'IF EXISTS'" "UNION SELECT 'ALL IN TABLESPACE'"); + /* ALTER TABLE after removing optional words IF EXISTS*/ + else if (HeadMatches2("ALTER", "TABLE") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches2("ALTER", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* ALTER something */ else if (Matches1("ALTER")) @@ -1443,6 +1546,17 @@ psql_completion(const char *text, int start, int end) else if (Matches5("ALTER", "FOREIGN", "DATA", "WRAPPER", MatchAny)) COMPLETE_WITH_LIST5("HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", "RENAME TO"); + /* ALTER FOREIGN TABLE */ + else if (Matches3("ALTER|DROP", "FOREIGN", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, + " UNION SELECT 'IF EXISTS'"); + + /* ALTER|DROP FOREIGN TABLE after removing optinal words IF EXISTS */ + else if (HeadMatches3("ALTER|DROP", "FOREIGN", "TABLE") && + MidMatchAndRemove2(3, "IF", "EXISTS") && + Matches3("ALTER|DROP", "FOREIGN", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL); + /* ALTER FOREIGN TABLE */ else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny)) { @@ -1454,10 +1568,21 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE); } + /* ALTER FOREIGN TABLE xxx RENAME */ + else if (Matches5("ALTER", "FOREIGN", "TABLE", MatchAny, "RENAME")) + COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'"); + /* ALTER INDEX */ else if (Matches2("ALTER", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, + "UNION SELECT 'IF EXISTS'" "UNION SELECT 'ALL IN TABLESPACE'"); + /* ALTER INDEX after removing optional words IF EXISTS */ + else if (HeadMatches2("ALTER", "INDEX") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches2("ALTER", "INDEX")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + /* ALTER INDEX */ else if (Matches3("ALTER", "INDEX", MatchAny)) COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET"); @@ -1486,8 +1611,15 @@ psql_completion(const char *text, int start, int end) /* ALTER MATERIALIZED VIEW */ else if (Matches3("ALTER", "MATERIALIZED", "VIEW")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, + "UNION SELECT 'IF EXISTS'" "UNION SELECT 'ALL IN TABLESPACE'"); + /* ALTER MATERIALIZED VIEW with name after removing optional words */ + else if (HeadMatches3("ALTER", "MATERIALIZED", "VIEW") && + MidMatchAndRemove2(3, "IF", "EXISTS") && + Matches3("ALTER", "MATERIALIZED", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + /* ALTER USER,ROLE */ else if (Matches3("ALTER", "USER|ROLE", MatchAny) && !TailMatches2("USER", "MAPPING")) @@ -1538,8 +1670,23 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN DROP */ else if (Matches4("ALTER", "DOMAIN", MatchAny, "DROP")) COMPLETE_WITH_LIST3("CONSTRAINT", "DEFAULT", "NOT NULL"); - /* ALTER DOMAIN DROP|RENAME|VALIDATE CONSTRAINT */ - else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT")) + /* ALTER DOMAIN RENAME|VALIDATE CONSTRAINT */ + else if (Matches5("ALTER", "DOMAIN", MatchAny, "RENAME|VALIDATE", "CONSTRAINT")) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_constraint_of_type); + } + /* ALTER DOMAIN DROP CONSTRAINT */ + else if (Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT")) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_constraint_of_type + "UNION SELECT 'IF EXISTS'"); + } + /* Try the same match after removing optional words IF EXISTS */ + else if (HeadMatches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT") && + MidMatchAndRemove2(5, "IF", "EXISTS") && + Matches5("ALTER", "DOMAIN", MatchAny, "DROP", "CONSTRAINT")) { completion_info_charp = prev3_wd; COMPLETE_WITH_QUERY(Query_for_constraint_of_type); @@ -1554,8 +1701,13 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN SET */ else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET")) COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA"); - /* ALTER SEQUENCE */ - else if (Matches3("ALTER", "SEQUENCE", MatchAny)) + else if (Matches2("ALTER", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, + " UNION SELECT 'IF EXISTS'"); + /* ALTER SEQUENCE with name after removing optional words IF EXISTS */ + else if (HeadMatches2("ALTER", "SEQUENCE") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("ALTER", "SEQUENCE", MatchAny)) { static const char *const list_ALTERSEQUENCE[] = {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE", @@ -1578,8 +1730,14 @@ psql_completion(const char *text, int start, int end) /* ALTER SYSTEM SET|RESET */ else if (Matches3("ALTER", "SYSTEM", "SET|RESET")) COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); - /* ALTER VIEW */ - else if (Matches3("ALTER", "VIEW", MatchAny)) + /* ALTER VIEW */ + else if (Matches2("ALTER", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, + "UNION SELECT 'IF EXISTS'"); + /* ALTER VIEW with subcommands after removing optional worlds */ + else if (HeadMatches2("ALTER", "VIEW") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("ALTER", "VIEW", MatchAny)) COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA"); /* ALTER MATERIALIZED VIEW */ @@ -1587,11 +1745,14 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA"); - /* ALTER POLICY */ + /* ALTER POLICY */ else if (Matches2("ALTER", "POLICY")) - COMPLETE_WITH_QUERY(Query_for_list_of_policies); - /* ALTER POLICY ON */ - else if (Matches3("ALTER", "POLICY", MatchAny)) + COMPLETE_WITH_QUERY(Query_for_list_of_policies + "UNION SELECT 'IF EXISTS'"); + /* ALTER POLICY with ON after removing optional words IF EXISTS */ + else if (HeadMatches2("ALTER", "POLICY") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("ALTER", "POLICY", MatchAny)) COMPLETE_WITH_CONST("ON"); /* ALTER POLICY ON */ else if (Matches4("ALTER", "POLICY", MatchAny, "ON")) @@ -1715,8 +1876,10 @@ psql_completion(const char *text, int start, int end) else if (Matches5("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"))) + /* ALTER [FOREIGN] TABLE xxx RENAME yyy */ + else if ((HeadMatches2("ALTER", "TABLE") || + HeadMatches3("ALTER", "FOREIGN", "TABLE")) && + TailMatches2("RENAME", MatchAnyExcept("CONSTRAINT|TO"))) COMPLETE_WITH_CONST("TO"); /* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */ @@ -1726,15 +1889,47 @@ psql_completion(const char *text, int start, int end) /* If we have ALTER TABLE DROP, provide COLUMN or CONSTRAINT */ else if (Matches4("ALTER", "TABLE", MatchAny, "DROP")) COMPLETE_WITH_LIST2("COLUMN", "CONSTRAINT"); + /* ALTER TABLE DROP COLUMN may take IF EXISTS */ + else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN")) + COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'"); + /* ALTER TABLE with ADD/ALTER/DROP after removing optional words */ + else if (HeadMatches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP") && + MidMatchAndRemove1(4, "COLUMN") && + MidMatchAndRemove2(4, "IF", "EXISTS") && + Matches4("ALTER", "TABLE", MatchAny, "ADD|ALTER|DROP")) + COMPLETE_WITH_ATTR(prev2_wd, ""); /* If we have ALTER TABLE DROP COLUMN, provide list of columns */ else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN")) + COMPLETE_WITH_ATTR(prev3_wd, "UNION SELECT 'IF EXISTS'"); + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN") && + MidMatchAndRemove2(5, "IF", "EXISTS") && + Matches5("ALTER", "TABLE", MatchAny, "DROP", "COLUMN")) COMPLETE_WITH_ATTR(prev3_wd, ""); /* - * If we have ALTER TABLE ALTER|DROP|RENAME|VALIDATE CONSTRAINT, + * If we have ALTER TABLE ALTER|RENAME|VALIDATE CONSTRAINT, * provide list of constraints */ - else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME|VALIDATE", "CONSTRAINT")) + else if (Matches5("ALTER", "TABLE", MatchAny, "ALTER|RENAME|VALIDATE", "CONSTRAINT")) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_constraint_of_table); + } + /* + * If we have ALTER TABLE DROP CONSTRAINT, + * provide list of constraints + */ + else if (Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT")) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_constraint_of_table + "UNION SELECT 'IF EXISTS'"); + } + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT") && + MidMatchAndRemove2(5, "IF", "EXISTS") && + Matches5("ALTER", "TABLE", MatchAny, "DROP", "CONSTRAINT")) { completion_info_charp = prev3_wd; COMPLETE_WITH_QUERY(Query_for_constraint_of_table); @@ -1857,8 +2052,13 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA"); else if (Matches5("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny)) COMPLETE_WITH_LIST6("ADD MAPPING FOR", "ALTER MAPPING", - "DROP MAPPING FOR", - "OWNER TO", "RENAME TO", "SET SCHEMA"); + "DROP MAPPING", "OWNER TO", "RENAME TO", "SET SCHEMA"); + else if (Matches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING")) + COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR"); + /* Remove optional words IF EXISTS */ + else if (HeadMatches7("ALTER", "TEXT", "SEARCH", "CONFIGURATION", MatchAny, "DROP", "MAPPING") && + MidMatchAndRemove2(7, "IF", "EXISTS") && + false) {} /* Nothing to do for now */ /* complete ALTER TYPE with actions */ else if (Matches3("ALTER", "TYPE", MatchAny)) @@ -1868,6 +2068,12 @@ psql_completion(const char *text, int start, int end) /* complete ALTER TYPE ADD with actions */ else if (Matches4("ALTER", "TYPE", MatchAny, "ADD")) COMPLETE_WITH_LIST2("ATTRIBUTE", "VALUE"); + else if (Matches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE")) + COMPLETE_WITH_LIST2("IF NOT EXISTS", ""); + /* Remove optional words IF NOT EXISTS */ + else if (HeadMatches5("ALTER", "TYPE", MatchAny, "ADD", "VALUE") && + MidMatchAndRemove3(5, "IF", "NOT", "EXISTS") && + false) {} /* Nothing to do for now */ /* ALTER TYPE RENAME */ else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME")) COMPLETE_WITH_LIST2("ATTRIBUTE", "TO"); @@ -1879,10 +2085,15 @@ psql_completion(const char *text, int start, int end) * If we have ALTER TYPE ALTER/DROP/RENAME ATTRIBUTE, provide list * of attributes */ - else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE")) + else if (Matches5("ALTER", "TYPE", MatchAny, "ALTER|RENAME", "ATTRIBUTE")) COMPLETE_WITH_ATTR(prev3_wd, ""); + else if (Matches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE")) + COMPLETE_WITH_ATTR(prev3_wd, " UNION SELECT 'IF EXISTS'"); + /* Remove optional words IF EXISTS */ + else if (HeadMatches5("ALTER", "TYPE", MatchAny, "DROP", "ATTRIBUTE") && + MidMatchAndRemove2(5, "IF", "EXISTS") && /* ALTER TYPE ALTER ATTRIBUTE */ - else if (Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny)) + Matches6("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny)) COMPLETE_WITH_CONST("TYPE"); /* complete ALTER GROUP */ else if (Matches3("ALTER", "GROUP", MatchAny)) @@ -2031,6 +2242,12 @@ psql_completion(const char *text, int start, int end) /* CREATE EXTENSION */ /* Complete with available extensions rather than installed ones. */ else if (Matches2("CREATE", "EXTENSION")) + COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions + " UNION SELECT 'IF NOT EXISTS'"); + /* Try the same after removing optional words IF NOT EXISTS */ + else if (HeadMatches2("CREATE", "EXTENSION") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + Matches2("CREATE", "EXTENSION")) COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions); /* CREATE EXTENSION */ else if (Matches3("CREATE", "EXTENSION", MatchAny)) @@ -2046,6 +2263,14 @@ psql_completion(const char *text, int start, int end) else if (Matches2("CREATE", "FOREIGN")) COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE"); + /* CREATE FOREIGN TABLE */ + else if (Matches3("CREATE", "FOREIGN", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, + " UNION SELECT 'IF NOT EXISTS'"); + /* Remove optional words IF NOT EXISTS */ + else if (HeadMatches3("CREATE", "FOREIGN", "TABLE") && + MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") && + false) {} /* FALL THROUGH */ /* CREATE FOREIGN DATA WRAPPER */ else if (Matches5("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny)) COMPLETE_WITH_LIST3("HANDLER", "VALIDATOR", "OPTIONS"); @@ -2055,29 +2280,37 @@ psql_completion(const char *text, int start, int end) else if (TailMatches2("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")) + /* Remove optional word UNIQUE from CREATE UNIQUE INDEX */ + else if (HeadMatches3("CREATE", MatchAny, "INDEX") && + MidMatchAndRemove1(1, "UNIQUE") && + false) {} /* FALL THROUGH */ + /* If we have CREATE INDEX, then add "ON", "CONCURRENTLY" or IF NOT EXISTS, + and existing indexes */ + else if (Matches2("CREATE", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, " UNION SELECT 'ON'" - " UNION SELECT 'CONCURRENTLY'"); - /* Complete ... INDEX|CONCURRENTLY [] ON with a list of tables */ - else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") || - TailMatches2("INDEX|CONCURRENTLY", "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL); - - /* - * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing - * indexes - */ - else if (TailMatches3("CREATE|UNIQUE", "INDEX", "CONCURRENTLY")) + " UNION SELECT 'CONCURRENTLY'" + " UNION SELECT 'IF NOT EXISTS'"); + /* Complete CREATE INDEX CONCURRENTLY with "ON" or IF NOT EXISTS and + * existing indexes */ + else if (Matches3("CREATE", "INDEX", "CONCURRENTLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, + " UNION SELECT 'IF NOT EXISTS'" " UNION SELECT 'ON'"); - /* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] with "ON" */ - else if (TailMatches3("CREATE|UNIQUE", "INDEX", MatchAny) || - TailMatches4("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny)) + + /* Remove optional words "CONCURRENTLY", "IF NOT EXISTS" */ + else if (HeadMatches2("CREATE", "INDEX") && + MidMatchAndRemove1(2, "CONCURRENTLY") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + false) {} /* FALL THROUGH */ + + /* Complete CREATE INDEX [] ON with a list of tables */ + else if (Matches4("CREATE", "INDEX", MatchAny, "ON") || + Matches3("CREATE", "INDEX", "ON")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL); + + /* Complete CREATE INDEX with "ON" */ + else if (Matches3("CREATE", "INDEX", MatchAny)) COMPLETE_WITH_CONST("ON"); /* @@ -2085,17 +2318,16 @@ psql_completion(const char *text, int start, int end) * should really be in parens) */ else if (TailMatches4("INDEX", MatchAny, "ON", MatchAny) || - TailMatches3("INDEX|CONCURRENTLY", "ON", MatchAny)) + TailMatches3("INDEX", "ON", MatchAny)) COMPLETE_WITH_LIST2("(", "USING"); - else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "(") || - TailMatches4("INDEX|CONCURRENTLY", "ON", MatchAny, "(")) + else if (Matches5("INDEX", MatchAny, "ON", MatchAny, "(") || + Matches4("INDEX", "ON", MatchAny, "(")) COMPLETE_WITH_ATTR(prev2_wd, ""); /* same if you put in USING */ else if (TailMatches5("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") || + else if (TailMatches5("INDEX", MatchAny, "ON", MatchAny, "USING") || TailMatches4("INDEX", "ON", MatchAny, "USING")) COMPLETE_WITH_QUERY(Query_for_list_of_access_methods); else if (TailMatches4("ON", MatchAny, "USING", MatchAny) && @@ -2149,27 +2381,56 @@ psql_completion(const char *text, int start, int end) else if (TailMatches4("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)) +/* Remove optional words TEMPORARY/TEMP */ + else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") && + MidMatchAndRemove1(1, "TEMP|TEMPORARY") && + false) {} /* FALL THROUGH */ +/* CREATE SEQUENCE */ + else if(Matches2("CREATE", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, + " UNION SELECT 'IF NOT EXISTS'"); + else if(HeadMatches2("CREATE", "SEQUENCE") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + Matches3("CREATE", "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 (Matches4("CREATE", "SEQUENCE", MatchAny, "NO")) COMPLETE_WITH_LIST3("MINVALUE", "MAXVALUE", "CYCLE"); /* CREATE SERVER */ else if (Matches3("CREATE", "SERVER", MatchAny)) COMPLETE_WITH_LIST3("TYPE", "VERSION", "FOREIGN DATA WRAPPER"); -/* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ +/* CREATE SCHEMA */ + else if (Matches2("CREATE", "SCHEMA")) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas + " UNION SELECT 'IF NOT EXISTS'"); + /* Remove optional words IF NOT EXISTS */ + else if (HeadMatches2("CREATE", "SCHEMA") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + false) {} /* FALL THROUGH*/ + +/* CREATE TABLE */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ - else if (TailMatches2("CREATE", "TEMP|TEMPORARY")) + else if (Matches2("CREATE", "TEMP|TEMPORARY")) COMPLETE_WITH_LIST3("SEQUENCE", "TABLE", "VIEW"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ - else if (TailMatches2("CREATE", "UNLOGGED")) + else if (Matches2("CREATE", "UNLOGGED")) COMPLETE_WITH_LIST2("TABLE", "MATERIALIZED VIEW"); + /* CREATE TABLE with name after removing optional words */ + else if (HeadMatches3("CREATE", MatchAny, "TABLE") && + MidMatchAndRemove1(1, "TEMP|TEMPORARY|UNLOGGED") && + false) {} /* FALL THROUGH*/ + else if (Matches2("CREATE", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, + " UNION SELECT 'IF NOT EXISTS'"); + + /* Remove optional words here */ + else if (HeadMatches2("CREATE", "TABLE") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + false) {} /* FALL THROUGH */ + /* CREATE TABLESPACE */ else if (Matches3("CREATE", "TABLESPACE", MatchAny)) COMPLETE_WITH_LIST2("OWNER", "LOCATION"); @@ -2183,18 +2444,18 @@ psql_completion(const char *text, int start, int end) else if (Matches5("CREATE", "TEXT", "SEARCH", "CONFIGURATION", MatchAny)) COMPLETE_WITH_CONST("("); -/* CREATE TRIGGER --- is allowed inside CREATE SCHEMA, so use TailMatches */ +/* CREATE TRIGGER */ /* complete CREATE TRIGGER with BEFORE,AFTER,INSTEAD OF */ - else if (TailMatches3("CREATE", "TRIGGER", MatchAny)) + else if (Matches3("CREATE", "TRIGGER", MatchAny)) COMPLETE_WITH_LIST3("BEFORE", "AFTER", "INSTEAD OF"); /* complete CREATE TRIGGER BEFORE,AFTER with an event */ - else if (TailMatches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER")) + else if (Matches4("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER")) COMPLETE_WITH_LIST4("INSERT", "DELETE", "UPDATE", "TRUNCATE"); /* complete CREATE TRIGGER INSTEAD OF with an event */ - else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF")) + else if (Matches5("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF")) COMPLETE_WITH_LIST3("INSERT", "DELETE", "UPDATE"); /* complete CREATE TRIGGER BEFORE,AFTER sth with OR,ON */ - else if (TailMatches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) || + else if (Matches5("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny) || TailMatches6("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny)) COMPLETE_WITH_LIST2("ON", "OR"); @@ -2251,9 +2512,14 @@ psql_completion(const char *text, int start, int end) else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN")) COMPLETE_WITH_LIST2("GROUP", "ROLE"); -/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */ - /* Complete CREATE VIEW with AS */ - else if (TailMatches3("CREATE", "VIEW", MatchAny)) +/* CREATE VIEW */ + else if (Matches2("CREATE", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, + " UNION SELECT 'IF NOT EXISTS'"); + /* CREATE VIEW with AS after removing optional words */ + else if (HeadMatches2("CREATE", "VIEW") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + Matches3("CREATE", "VIEW", MatchAny)) COMPLETE_WITH_CONST("AS"); /* Complete "CREATE VIEW AS with "SELECT" */ else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS")) @@ -2262,6 +2528,15 @@ psql_completion(const char *text, int start, int end) /* CREATE MATERIALIZED VIEW */ else if (Matches2("CREATE", "MATERIALIZED")) COMPLETE_WITH_CONST("VIEW"); + else if (Matches3("CREATE", "MATERIALIZED", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, + " UNION SELECT 'IF NOT EXISTS'"); + /* Try the same after removing optional words IF NOT EXISTS. VIEW will be + * completed afterwards */ + else if (HeadMatches3("CREATE", "MATERIALIZED", "VIEW") && + MidMatchAndRemove3(3, "IF", "NOT", "EXISTS") && + Matches3("CREATE", "MATERIALIZED", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); /* Complete CREATE MATERIALIZED VIEW with AS */ else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny)) COMPLETE_WITH_CONST("AS"); @@ -2321,28 +2596,61 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); /* help completing some of the variants */ - else if (Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny)) + else if (Matches2("DROP", "AGGREGATE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, + " UNION SELECT 'IF EXISTS'"); + else if (Matches2("DROP", "FUNCTION")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, + " UNION SELECT 'IF EXISTS'"); + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches2("DROP", "AGGREGATE|FUNCTION") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("DROP", "AGGREGATE|FUNCTION", MatchAny)) COMPLETE_WITH_CONST("("); else if (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, "(")) COMPLETE_WITH_FUNCTION_ARG(prev2_wd); else if (Matches2("DROP", "FOREIGN")) COMPLETE_WITH_LIST2("DATA WRAPPER", "TABLE"); + else if (Matches4("DROP", "FOREIGN", "DATA", "WRAPPER")) + COMPLETE_WITH_QUERY(Query_for_list_of_fdws + " UNION SELECT 'IF EXISTS'"); + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches4("DROP", "FOREIGN", "DATA", "WRAPPER") && + MidMatchAndRemove2(4, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ /* DROP INDEX */ else if (Matches2("DROP", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, + " UNION SELECT 'IF EXISTS'" " UNION SELECT 'CONCURRENTLY'"); else if (Matches3("DROP", "INDEX", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); - else if (Matches3("DROP", "INDEX", MatchAny)) - COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); - else if (Matches4("DROP", "INDEX", "CONCURRENTLY", MatchAny)) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, + " UNION SELECT 'IF EXISTS'"); + /* Try the same after optional words CONCURRENTLY and IF NOT EXISTS */ + else if (HeadMatches2("DROP", "INDEX") && + MidMatchAndRemove1(2, "CONCURRENTLY") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("DROP", "INDEX", MatchAny)) COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); /* DROP MATERIALIZED VIEW */ else if (Matches2("DROP", "MATERIALIZED")) COMPLETE_WITH_CONST("VIEW"); + else if (Matches2("DROP", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, + " UNION SELECT 'IF EXISTS'"); + /* Remove optional words IF EXISTS */ + else if (HeadMatches2("DROP", "VIEW") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ else if (Matches3("DROP", "MATERIALIZED", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, + " UNION SELECT 'IF EXISTS'"); + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches3("DROP", "MATERIALIZED", "VIEW") && + MidMatchAndRemove2(3, "IF", "EXISTS") && + Matches3("DROP", "MATERIALIZED", "VIEW")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); /* DROP OWNED BY */ @@ -2355,7 +2663,13 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST4("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE"); /* DROP TRIGGER */ - else if (Matches3("DROP", "TRIGGER", MatchAny)) + else if (Matches2("DROP", "TRIGGER")) + COMPLETE_WITH_QUERY(Query_for_list_of_triggers + " UNION SELECT 'IF EXISTS'"); + /* Try the same after removing optional words IF EXISTS */ + else if (HeadMatches2("DROP", "TRIGGER") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("DROP", "TRIGGER", MatchAny)) COMPLETE_WITH_CONST("ON"); else if (Matches4("DROP", "TRIGGER", MatchAny, "ON")) { @@ -2375,15 +2689,27 @@ psql_completion(const char *text, int start, int end) else if (Matches2("DROP", "EVENT")) COMPLETE_WITH_CONST("TRIGGER"); else if (Matches3("DROP", "EVENT", "TRIGGER")) + COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers + " UNION SELECT 'IF EXISTS'"); + /* Trye the same after removing optional words IF EXISTS */ + else if (HeadMatches3("DROP", "EVENT", "TRIGGER") && + MidMatchAndRemove2(3, "IF", "EXISTS") && + Matches3("DROP", "EVENT", "TRIGGER")) COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers); - /* DROP POLICY */ + /* DROP POLICY */ else if (Matches2("DROP", "POLICY")) + COMPLETE_WITH_QUERY(Query_for_list_of_policies + " UNION SELECT 'IF EXISTS'"); + /* Try the same after after removing optional words IF EXISTS */ + else if (HeadMatches2("DROP", "POLICY") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches2("DROP", "POLICY")) COMPLETE_WITH_QUERY(Query_for_list_of_policies); - /* DROP POLICY ON */ + /* DROP POLICY */ else if (Matches3("DROP", "POLICY", MatchAny)) COMPLETE_WITH_CONST("ON"); - /* DROP POLICY ON
*/ + /* DROP POLICY ON */ else if (Matches4("DROP", "POLICY", MatchAny, "ON")) { completion_info_charp = prev2_wd; @@ -2391,7 +2717,13 @@ psql_completion(const char *text, int start, int end) } /* DROP RULE */ - else if (Matches3("DROP", "RULE", MatchAny)) + else if (Matches2("DROP", "RULE")) + COMPLETE_WITH_QUERY(Query_for_list_of_rules + "UNION SELECT 'IF EXISTS'"); + /* DROP RULE , after removing optional words IF EXISTS */ + else if (HeadMatches2("DROP", "RULE") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + Matches3("DROP", "RULE", MatchAny)) COMPLETE_WITH_CONST("ON"); else if (Matches4("DROP", "RULE", MatchAny, "ON")) { @@ -2401,6 +2733,52 @@ psql_completion(const char *text, int start, int end) else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); + /* IF EXISTS processing for DROP everything else */ + else if (Matches2("DROP", + "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|" + "GROUP|LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|" + "SERVER|TABLE|TABLESPACE|TYPE|USER") || + Matches4("DROP", "TEXT", "SEARCH", + "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE")) + + { + const pgsql_thing_t *ent = find_thing_entry(prev_wd); + char *addition = " UNION SELECT 'IF EXISTS'"; + + if (ent) + { + /* Completing USER needs special treat */ + if (pg_strcasecmp(prev_wd, "USER") == 0) + { + COMPLETE_WITH_QUERY( + ADDLIST2(Query_for_list_of_roles, "MAPPING", "IF EXISTS")); + } + else if (ent->query) + { + char *buf = pg_malloc(strlen(ent->query) + + strlen(addition) + 1); + strcpy(buf, ent->query); + strcat(buf, addition); + COMPLETE_WITH_QUERY(buf); + free(buf); + } + else if (ent->squery) + COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, + " UNION SELECT 'IF EXISTS'"); + } + } + /* Remove optional IF EXISTS from DROP */ + else if (HeadMatches2("DROP", + "CAST|COLLATION|CONVERSION|DATABASE|DOMAIN|GROUP|" + "LANGUAGE|OPERATOR|ROLE|SCHEMA|SEQUENCE|SERVER|" + "TABLE|TABLESPACE|TYPE|USER") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ + else if (HeadMatches4("DROP", "TEXT", "SEARCH", + "CONFIGURATION|DICTIONARY|PARSER|TEMPLATE") && + MidMatchAndRemove2(4, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ + /* EXECUTE */ else if (Matches1("EXECUTE")) COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements); @@ -2447,8 +2825,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_CONST("OPTIONS"); /* FOREIGN TABLE */ - else if (TailMatches2("FOREIGN", "TABLE") && - !TailMatches3("CREATE", MatchAny, MatchAny)) + else if (TailMatches2("FOREIGN", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL); /* FOREIGN SERVER */ @@ -2900,8 +3277,13 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_CONST("="); /* USER MAPPING */ - else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING")) + else if (Matches3("ALTER|CREATE", "USER", "MAPPING")) COMPLETE_WITH_CONST("FOR"); + else if (Matches3("DROP", "USER", "MAPPING")) + COMPLETE_WITH_LIST2("FOR", "IF EXISTS FOR"); + else if (HeadMatches3("DROP", "USER", "MAPPING") && + MidMatchAndRemove2(3, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ else if (Matches4("CREATE", "USER", "MAPPING", "FOR")) COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'CURRENT_USER'" @@ -3133,19 +3515,14 @@ psql_completion(const char *text, int start, int end) */ else { - int i; + const pgsql_thing_t *ent = find_thing_entry(prev_wd); - for (i = 0; words_after_create[i].name; i++) + if (ent) { - if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0) - { - if (words_after_create[i].query) - COMPLETE_WITH_QUERY(words_after_create[i].query); - else if (words_after_create[i].squery) - COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery, - NULL); - break; - } + if (ent->query) + COMPLETE_WITH_QUERY(ent->query); + else if (ent->squery) + COMPLETE_WITH_SCHEMA_QUERY(*ent->squery, NULL); } } @@ -3674,6 +4051,18 @@ complete_from_files(const char *text, int state) /* HELPER FUNCTIONS */ +/* + * Return the index (reverse to the index of previous_words) of the tailmost + * (topmost in the array) appearance of w. + */ +static int +find_last_index_of(char *w, char **previous_words, int len) +{ + int i; + + for (i = 0 ; i < len && !word_matches(w, previous_words[i]) ; i++); + return i < len ? (len - i - 1) : 0; +} /* * Make a pg_strdup copy of s and convert the case according to @@ -3920,6 +4309,24 @@ get_guctype(const char *varname) return guctype; } +/* + * Finds the entry in words_after_create[] that matches the word. + * NULL if not found. + */ +static const pgsql_thing_t * +find_thing_entry(char *word) +{ + int i; + + for (i = 0; words_after_create[i].name; i++) + { + if (pg_strcasecmp(word, words_after_create[i].name) == 0) + return words_after_create + i; + } + + return NULL; +} + #ifdef NOT_USED /* -- 2.9.2