Hello, this is the new version of this patch. At Fri, 26 Feb 2016 13:17:26 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyot...@lab.ntt.co.jp> wrote in <20160226.131726.54224488.horiguchi.kyot...@lab.ntt.co.jp> > Hello, thank you for the comments. > > At Mon, 15 Feb 2016 15:43:57 +0300, Artur Zakirov <a.zaki...@postgrespro.ru> > wrote in <56c1c80d.7020...@postgrespro.ru> > > I did some tests with your patch. But I am not confident in > > tab-complete.c. > > > > And I have some notes: > > > > 1 - I execute git apply command and get the following warning: > > > > ../0001-Suggest-IF-NOT-EXISTS-for-tab-completion-of-psql.patch:302: > > trailing whitespace. > > /* > > warning: 1 line adds whitespace errors. > > > > This is because of superfluous whitespace I think. > > Oops. I'll remove it. > > > 2 - In psql I write "create table if" and press <TAB>. psql adds the > > following: > > > > create table IF NOT EXISTS > > > > I think psql should continue with lower case if user wrote query with > > loser case text: > > Good catch! Hmm. COMPLETE_WITH_SCHEMA_QUERY() does it. For > example, the following existing completion behaves in the same > way. > > "drop index c" =<tab>=> "drop index CONCURRENTLY" > > The results of schema queries should be treated in case-sensitive > way so the additional keywords by 'UNION' are also treated > case-sensitively. > > COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, > " UNION SELECT 'CONCURRENTLY'"); > > Fixing this is another problem. So I'd like to leave this alone > here. > > > create table if not exists > > > > 3 - Same with "IF EXISTS". If a write "alter view if" and press <TAB> > > psql writes: > > > > alter view IF EXISTS
Finally, the only thing done in this new patch is removing one dangling space. reagrds, -- Kyotaro Horiguchi NTT Open Source Software Center
>From f9610a87d73629ac3fcc25fcf8e667c5ca3e921d Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp> Date: Fri, 5 Feb 2016 16:50:35 +0900 Subject: [PATCH] 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 | 559 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 463 insertions(+), 96 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 5f27120..d3eab8c 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 "\ @@ -906,7 +915,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}, @@ -915,7 +924,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 @@ -944,6 +953,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); @@ -952,6 +962,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); @@ -1107,6 +1118,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 @@ -1124,31 +1138,72 @@ 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. + */ +#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) && \ @@ -1156,7 +1211,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) && \ @@ -1165,7 +1220,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) && \ @@ -1175,7 +1230,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) && \ @@ -1186,7 +1241,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) && \ @@ -1199,43 +1254,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)) /* @@ -1243,19 +1298,34 @@ 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)])) /* Known command-starting keywords. */ static const char *const sql_commands[] = { @@ -1327,9 +1397,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 */ @@ -1342,7 +1419,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'"); + /* Try 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")) @@ -1420,6 +1503,18 @@ 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'"); + + /* Try ALTER FOREIGN TABLE after removing optinal words IF EXISTS */ + /* Complete for DROP together */ + 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 <name> */ else if (Matches4("ALTER", "FOREIGN", "TABLE", MatchAny)) { @@ -1431,10 +1526,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'"); + /* Try 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 <name> */ else if (Matches3("ALTER", "INDEX", MatchAny)) COMPLETE_WITH_LIST4("OWNER TO", "RENAME TO", "SET", "RESET"); @@ -1463,8 +1569,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'"); + /* Try ALTER MATERIALIZED VIEW after removing optional words IF EXISTS */ + 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 <name> */ else if (Matches3("ALTER", "USER|ROLE", MatchAny) && !TailMatches2("USER", "MAPPING")) @@ -1515,8 +1628,23 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN <sth> DROP */ else if (Matches4("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")) + /* ALTER DOMAIN <sth> 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 <sth> 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); @@ -1531,8 +1659,13 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN <sth> SET */ else if (Matches4("ALTER", "DOMAIN", MatchAny, "SET")) COMPLETE_WITH_LIST3("DEFAULT", "NOT NULL", "SCHEMA"); - /* ALTER SEQUENCE <name> */ - else if (Matches3("ALTER", "SEQUENCE", MatchAny)) + else if (Matches2("ALTER", "SEQUENCE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, + " UNION SELECT 'IF EXISTS'"); + /* Try ALTER SEQUENCE 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", @@ -1555,6 +1688,14 @@ psql_completion(const char *text, int start, int end) /* ALTER SYSTEM SET|RESET <name> */ else if (Matches3("ALTER", "SYSTEM", "SET|RESET")) COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); + /* ALTER VIEW */ + else if (Matches2("ALTER", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, + "UNION SELECT 'IF EXISTS'"); + /* Try ALTER VIEW after removing optional worlds IF EXISTS */ + else if (HeadMatches2("ALTER", "VIEW") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + false){} /* FALL THROUGH */ /* ALTER VIEW <name> */ else if (Matches3("ALTER", "VIEW", MatchAny)) COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", @@ -1564,9 +1705,14 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA"); - /* ALTER POLICY <name> */ + /* ALTER POLICY */ else if (Matches2("ALTER", "POLICY")) - COMPLETE_WITH_QUERY(Query_for_list_of_policies); + COMPLETE_WITH_QUERY(Query_for_list_of_policies + "UNION SELECT 'IF EXISTS'"); + /* Try ALTER POLICY after removing optional words IF EXISTS */ + else if (HeadMatches2("ALTER", "POLICY") && + MidMatchAndRemove2(2, "IF", "EXISTS") && + false) {} /* FALL THROUGH */ /* ALTER POLICY <name> ON */ else if (Matches3("ALTER", "POLICY", MatchAny)) COMPLETE_WITH_CONST("ON"); @@ -1692,8 +1838,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 */ @@ -1705,13 +1853,36 @@ psql_completion(const char *text, int start, int end) 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")) + 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 <sth> ALTER|DROP|RENAME|VALIDATE CONSTRAINT, + * If we have ALTER TABLE <sth> 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 <sth> 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); @@ -1844,6 +2015,12 @@ psql_completion(const char *text, int start, int end) /* complete ALTER TYPE <foo> 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 <foo> RENAME */ else if (Matches4("ALTER", "TYPE", MatchAny, "RENAME")) COMPLETE_WITH_LIST2("ATTRIBUTE", "TO"); @@ -1990,6 +2167,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 <name> */ else if (Matches3("CREATE", "EXTENSION", MatchAny)) @@ -2005,6 +2188,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"); @@ -2013,23 +2204,39 @@ psql_completion(const char *text, int start, int end) /* First off we complete CREATE UNIQUE with "INDEX" */ else if (TailMatches2("CREATE", "UNIQUE")) COMPLETE_WITH_CONST("INDEX"); - /* If we have CREATE|UNIQUE INDEX, then add "ON", "CONCURRENTLY", + + /* 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 (TailMatches2("CREATE|UNIQUE", "INDEX")) + else if (Matches2("CREATE", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, " UNION SELECT 'ON'" - " UNION SELECT 'CONCURRENTLY'"); - /* Complete ... INDEX|CONCURRENTLY [<name>] 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] <sth> 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 [<name>] 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 <sth> with "ON" */ + else if (Matches3("CREATE", "INDEX", MatchAny)) COMPLETE_WITH_CONST("ON"); /* @@ -2037,10 +2244,10 @@ 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, "(")) @@ -2101,27 +2308,50 @@ 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)) +/* CREATE SEQUENCE, removing optional words TEMPORARY/TEMP */ + else if (HeadMatches3("CREATE", MatchAny, "SEQUENCE") && + MidMatchAndRemove1(1, "TEMP|TEMPORARY") && + 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 <name> */ 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 <name> */ + 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"); + /* Remove optional words here */ + 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"); @@ -2135,18 +2365,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 <name> 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 <name> 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 <name> 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 <name> 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"); @@ -2203,9 +2433,16 @@ 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 */ +/* CREATE VIEW */ + else if (Matches2("CREATE", "VIEW")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, + " UNION SELECT 'IF NOT EXISTS'"); + /* Remove optional words IF NOT EXISTS */ + else if (HeadMatches2("CREATE", "VIEW") && + MidMatchAndRemove3(2, "IF", "NOT", "EXISTS") && + false) {} /* FALL THROUGH */ /* Complete CREATE VIEW <name> with AS */ - else if (TailMatches3("CREATE", "VIEW", MatchAny)) + else if (Matches3("CREATE", "VIEW", MatchAny)) COMPLETE_WITH_CONST("AS"); /* Complete "CREATE VIEW <sth> AS with "SELECT" */ else if (TailMatches4("CREATE", "VIEW", MatchAny, "AS")) @@ -2214,6 +2451,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 <name> with AS */ else if (Matches4("CREATE", "MATERIALIZED", "VIEW", MatchAny)) COMPLETE_WITH_CONST("AS"); @@ -2272,28 +2518,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 */ @@ -2306,7 +2585,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")) { @@ -2320,15 +2605,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 <name> */ + /* 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 <name> ON */ + /* DROP POLICY <name> */ else if (Matches3("DROP", "POLICY", MatchAny)) COMPLETE_WITH_CONST("ON"); - /* DROP POLICY <name> ON <table> */ + /* DROP POLICY <name> ON */ else if (Matches4("DROP", "POLICY", MatchAny, "ON")) { completion_info_charp = prev2_wd; @@ -2336,7 +2633,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 <name>, 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")) { @@ -2346,6 +2649,46 @@ 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) + { + 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); @@ -2392,8 +2735,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 */ @@ -3058,19 +3400,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); } } @@ -3587,6 +3924,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 @@ -3833,6 +4182,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 /* -- 1.8.3.1
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers