Index: command.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/command.c,v retrieving revision 1.95 diff -u -r1.95 command.c --- command.c 2003/04/04 20:40:45 1.95 +++ command.c 2003/04/16 15:37:53 @@ -66,7 +66,386 @@ static bool do_connect(const char *new_dbname, const char *new_user); static bool do_shell(const char *command); +static char *eat_token(char *cp, char *token, size_t token_len); +static char *eat_whitespace(char *cp); +static size_t token_len(char *cp); + +#define _(x) gettext((x)) + +/* These macros are a tad evil but saves a gagillion keystrokes and + * should only be used in HandleMySQLShowCmds(). */ +#define MYSQL_SHOW_PARSE_OPT_LIKE(cmd, cmd_len) do { \ + cp = eat_whitespace(&tkn1[tkn1_len]); \ + cp_pos = 0; \ + if (cp == NULL) \ + goto help; \ + strncpy(my_line, cmd, cmd_len); \ + tkn2 = eat_token(cp, "LIKE", 4); \ + tkn2_len = token_len(tkn2); \ + if (tkn2 != NULL) { \ + sprintf(&my_line[cmd_len], " %.*s*", tkn2_len, tkn2); \ + options_string = &my_line[cmd_len]; \ + } \ + if (!QUIET()) \ + fprintf(stderr, "\n\tTIP: In %s, \"%s\" is natively written as \"\\%s%.*s%.*s%.*s\"\n\n", getprogname(), line, cmd, (tkn2_len ? 1 : 0), " ", tkn2_len, tkn2, (tkn2_len ? 1 : 0), "*"); \ +} while(0) + +#define MYSQL_SHOW_COPY_ARG(cmd, cmd_len) do { \ + strncpy(my_line, cmd, cmd_len); \ + if (!QUIET()) { \ + fprintf(stderr, "\n\tTIP: In %s, \"%s\" is natively written as \"\\%s\"\n\n", getprogname(), line, cmd); \ + } \ +} while(0) + /*---------- + * HandleMySQLShowCmds: + * + * Handles all the different "SHOW" commands while in MySQL + * compatibility mode, ordinarily called by MainLoop(). + * + * This function is a ripoff of HandleSlashCmds() and acts as a + * translation layer between the user input and exec_command(). + * + * 'query_buf' contains the query-so-far, which may be modified by + * execution of the backslash command (for example, \r clears it) + * query_buf can be NULL if there is no query so far. + * + * Returns a status code indicating what action is desired, see command.h. + *---------- + */ + +backslashResult +HandleMySQLShowCmds(const char *line, + PQExpBuffer query_buf, + const char **end_of_cmd, + volatile int *paren_level) +{ + backslashResult status = CMD_SKIP_LINE; + bool run_cmd; + char *cp, *my_line, *tkn1, *tkn2; + char *options_string = NULL; + size_t cp_pos, tkn1_len, tkn2_len; + const char *continue_parse = NULL; /* tell the mainloop where the + * backslash command ended */ + +#ifdef USE_ASSERT_CHECKING + assert(line); +#endif + + my_line = xstrdup(line); + run_cmd = true; + cp_pos = strlen(line); + continue_parse = &line[cp_pos]; + if (end_of_cmd != NULL) + *end_of_cmd = &line[cp_pos]; + + /* + * Find the thing that we're showing + */ + cp = tkn1 = eat_token(my_line, "SHOW", 4); + tkn1_len = token_len(tkn1); + if (cp == NULL) { + status = CMD_UNKNOWN; + goto error; + } + + if (cp[0] == '\0') { + help: + MYSQL_SHOW_COPY_ARG("?", 2); + } else if (strncasecmp(tkn1, "AGGREGATES", tkn1_len) == 0) { + /* SHOW AGGREGATES [LIKE pattern] */ + /* \da [PATTERN] list aggregate functions */ + MYSQL_SHOW_PARSE_OPT_LIKE("da", 3); + } else if (strncasecmp(tkn1, "CASTS", tkn1_len) == 0) { + /* SHOW CASTS */ + /* \dC list casts */ + MYSQL_SHOW_COPY_ARG("dC", 3); + } else if (strncasecmp(tkn1, "CATALOGS", tkn1_len) == 0) { + /* SHOW CATALOGS [LIKE pattern] */ + /* \dS lists system catalogs */ + MYSQL_SHOW_PARSE_OPT_LIKE("dS", 3); + } else if (strncasecmp(tkn1, "COLUMNS", tkn1_len) == 0) { + /* SHOW COLUMNS FROM tbl_name */ + /* \dt [PATTERN] list columns in a table */ + cp = eat_whitespace(&tkn1[tkn1_len]); + cp_pos = 0; + if (cp == NULL) + goto help; + + /* Skip past the token word FROM and return the next token */ + cp = tkn2 = eat_token(cp, "FROM", 4); + tkn2_len = token_len(tkn2); + if (tkn2 == NULL || tkn2[0] == '\0') + goto help; + + /* Abuse my_line since my_line is always shorter than the string passed in */ + strncpy(my_line, "d", 2); + sprintf(&my_line[2], "%.*s", tkn2_len, tkn2); + options_string = &my_line[2]; + if (!QUIET()) + fprintf(stderr, "\n\tTIP: In %s, \"%s\" is natively written as \"\\%s%.*s%.*s\"\n\n", getprogname(), line, "d", (tkn2_len ? 1 : 0), " ", tkn2_len, tkn2); + } else if (strncasecmp(tkn1, "COMMENTS", tkn1_len) == 0) { + /* SHOW COMMENTS [LIKE pattern] */ + /* \dd [PATTERN] show comment for object */ + MYSQL_SHOW_PARSE_OPT_LIKE("dd", 3); + } else if (strncasecmp(tkn1, "CONVERSIONS", tkn1_len) == 0) { + /* SHOW CONVERSIONS [LIKE pattern] */ + /* \dc [PATTERN] list conversions */ + MYSQL_SHOW_COPY_ARG("dc", 3); + } else if (strncasecmp(tkn1, "DATABASES", tkn1_len) == 0) { + /* \l list all databases */ + MYSQL_SHOW_COPY_ARG("l", 2); + } else if (strncasecmp(tkn1, "DOMAINS", tkn1_len) == 0) { + /* SHOW DOMAINS [LIKE pattern] */ + /* \dD [PATTERN] list domains */ + MYSQL_SHOW_PARSE_OPT_LIKE("dD", 3); + } else if (strncasecmp(tkn1, "FUNCTIONS", tkn1_len) == 0) { + /* SHOW FUNCTIONS [LIKE pattern] */ + /* \df [PATTERN] list functions */ + MYSQL_SHOW_PARSE_OPT_LIKE("df", 3); + } else if (strncasecmp(tkn1, "HELP", tkn1_len) == 0) { + /* SHOW HELP [ON (function_name|ALL) ]*/ + /* \h [NAME] help on syntax of SQL commands, * for all commands */ + strncpy(my_line, "h", 2); + cp = eat_whitespace(&tkn1[tkn1_len]); + cp_pos = 0; + if (cp == NULL) + goto help; + + /* Skip past the token word FROM and return the next token */ + cp = tkn2 = eat_token(cp, "ON", 2); + tkn2_len = token_len(tkn2); + if (tkn2 == NULL || tkn2[0] == '\0') { + strncpy(my_line, "h", 2); + } else { + strncpy(my_line, "h", 2); + if (strncasecmp(tkn2, "ALL", 3) == 0) { + strncpy(&my_line[2], " *", 3); + tkn2 = &my_line[3]; + } else + sprintf(&my_line[2], " %.*s", tkn2_len, tkn2); + + options_string = &my_line[3]; + } + if (!QUIET()) + fprintf(stderr, "\n\tTIP: In %s, \"%s\" is natively written as \"\\%s%.*s%.*s\"\n\n", getprogname(), line, "h", (tkn2_len ? 1 : 0), " ", tkn2_len, tkn2); + } else if (strncasecmp(tkn1, "INDEXES", tkn1_len) == 0 || + strncasecmp(tkn1, "INDEX", tkn1_len) == 0) { + /* SHOW INDEX (FROM table_name|LIKE pattern) */ + /* FROM = query with arg comparing to table names. LIKE = query with arg comparing to index names */ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + cp = eat_whitespace(&tkn1[tkn1_len]); + cp_pos = 0; + if (cp == NULL) + goto help; + + if (strncasecmp(cp, "FROM", 4) == 0) { + cp = tkn2 = eat_token(cp, "FROM", 4); + + tkn2_len = token_len(tkn2); + if (tkn2 == NULL || tkn2[0] == '\0') + goto help; + + initPQExpBuffer(&buf); + /* Ned to populate the buffer */ + printfPQExpBuffer(&buf, + "SELECT n.nspname AS \"Schema\", c2.relname AS \"Table\", c.relname AS \"Name\",\n" + " u.usename AS \"Owner\", i.indisclustered AS \"Clustered\", i.indisunique AS \"Unique\",\n" + " i.indisprimary AS \"Primary\"\n" + " FROM pg_catalog.pg_class c\n" + " JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid\n" + " JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid\n" + " LEFT JOIN pg_catalog.pg_user u ON u.usesysid= c.relowner\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" + " WHERE c.relkind IN ('i','') AND pg_catalog.pg_table_is_visible(c.oid)\n" + " AND c2.relname ~ '^%.*s' ORDER BY c2.relname, c.relname", + tkn2_len, tkn2); + + } else if (strncasecmp(cp, "LIKE", 4) == 0) { + cp = tkn2 = eat_token(cp, "LIKE", 4); + + tkn2_len = token_len(tkn2); + if (tkn2 == NULL || tkn2[0] == '\0') + goto help; + + initPQExpBuffer(&buf); + + /* Ned to populate the buffer */ + printfPQExpBuffer(&buf, + "SELECT n.nspname AS \"Schema\", c2.relname AS \"Table\", c.relname AS \"Name\",\n" + " u.usename AS \"Owner\", i.indisclustered AS \"Clustered\", i.indisunique AS \"Unique\",\n" + " i.indisprimary AS \"Primary\"\n" + " FROM pg_catalog.pg_class c\n" + " JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid\n" + " JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid\n" + " LEFT JOIN pg_catalog.pg_user u ON u.usesysid= c.relowner\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" + " WHERE c.relkind IN ('i','') AND pg_catalog.pg_table_is_visible(c.oid)\n" + " AND c.relname ~ '^%.*s' ORDER BY c.relname", + tkn2_len, tkn2); + } else + goto help; + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + goto error; + + if (PQntuples(res) == 0 && !QUIET()) + fprintf(pset.queryFout, _("No matching indexes found.\n")); + else + { + myopt.nullPrint = NULL; + myopt.title = _("List of indexes"); + + printQuery(res, &myopt, pset.queryFout); + } + + PQclear(res); + run_cmd = false; + options_string = &my_line[2]; + } else if (strncasecmp(tkn1, "LARGEOBJECTS", tkn1_len) == 0) { + /* SHOW LARGEOBJECTS */ + /* \dl list large objects */ + MYSQL_SHOW_COPY_ARG("dl", 3); + } else if (strncasecmp(tkn1, "OPERATORS", tkn1_len) == 0) { + /* SHOW OPERATORS [LIKE name] */ + /* \do [NAME] list operators */ + MYSQL_SHOW_PARSE_OPT_LIKE("do", 3); + } else if (strncasecmp(tkn1, "PRIVILEGES", tkn1_len) == 0) { + /* SHOW PRIVILEGES (ON name|LIKE pattern) */ + /* \dp [PATTERN] list table access privileges */ + MYSQL_SHOW_PARSE_OPT_LIKE("dp", 3); + } else if (strncasecmp(tkn1, "PROCESSLIST", tkn1_len) == 0) { + /* SHOW PROCESSLIST */ + /* SELECT * FROM pg_catalog.pg_stat_activity ; */ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT a.datname AS \"Database\", a.procpid AS \"PID\", a.usename AS \"Username\",\n" + " a.current_query AS \"Current Query\" FROM pg_catalog.pg_stat_activity a"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + goto error; + + if (PQntuples(res) == 0 && !QUIET()) + fprintf(pset.queryFout, _("No stats available.\n")); + else + { + myopt.nullPrint = NULL; + myopt.title = _("List of processes"); + + printQuery(res, &myopt, pset.queryFout); + } + + PQclear(res); + run_cmd = false; + options_string = &my_line[2]; + } else if (strncasecmp(tkn1, "SCHEMAS", tkn1_len) == 0) { + /* SHOW SCHEMAS [LIKE pattern] */ + /* \dn [PATTERN] list schemas */ + MYSQL_SHOW_PARSE_OPT_LIKE("dn", 3); + } else if (strncasecmp(tkn1, "SEQUENCES", tkn1_len) == 0) { + /* SHOW SEQUENCES [LIKE pattern] */ + /* ds [PATTERN] list sequences */ + MYSQL_SHOW_PARSE_OPT_LIKE("ds", 3); + } else if (strncasecmp(tkn1, "STATUS", tkn1_len) == 0) { + /* SHOW STATUS */ + /* SELECT * FROM pg_catalog.pg_stat_database ; */ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT d.datname AS \"Database\", d.numbackends AS \"Num Backends\",\n" + " d.xact_commit AS \"Txn Commit\", d.xact_rollback AS \"Txn Rollback\",\n" + " d.blks_read AS \"Blocks Read\", d.blks_hit AS \"Blocks Hit\"\n" + " FROM pg_catalog.pg_stat_database d"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + goto error; + + if (PQntuples(res) == 0 && !QUIET()) + fprintf(pset.queryFout, _("No status available.\n")); + else + { + myopt.nullPrint = NULL; + myopt.title = _("Status"); + + printQuery(res, &myopt, pset.queryFout); + } + + PQclear(res); + run_cmd = false; + options_string = &my_line[2]; + } else if (strncasecmp(tkn1, "TABLES", tkn1_len) == 0) { + /* SHOW TABLES [LIKE pattern] */ + /* \dt [PATTERN] list tables */ + MYSQL_SHOW_PARSE_OPT_LIKE("dt", 3); + } else if (strncasecmp(tkn1, "TYPES", tkn1_len) == 0) { + /* SHOW TYPES [LIKE pattern] */ + /* \dT [PATTERN] list data types */ + MYSQL_SHOW_PARSE_OPT_LIKE("dT", 3); + } else if (strncasecmp(tkn1, "USERS", tkn1_len) == 0) { + /* SHOW USERS [LIKE pattern] */ + /* \du [PATTERN] list users */ + MYSQL_SHOW_PARSE_OPT_LIKE("du", 3); + } else if (strncasecmp(tkn1, "VARIABLES", tkn1_len) == 0) { + /* INCOMPLETE - XXXXXXXXXXXXXXXXXX + * Pushing "SHOW [TAB][TAB]y" however, does work */ + /* SHOW VARIABLES */ + /* Need to iterate through the list of variables that would + * normally be shown via "SHOW [TAB][TAB]" when not in mysql + * compatibility mode. This mode will be an oddity in that + * it won't return normal results and will look a tad funky, + * I think. This could iterate through the list of + * variables stored in *pgsql_variables[] and show their + * values. */ + MYSQL_SHOW_COPY_ARG("?", 2); + } else if (strncasecmp(tkn1, "VIEWS", tkn1_len) == 0) { + /* SHOW VIEWS [LIKE pattern] */ + /* \dv [PATTERN list views */ + MYSQL_SHOW_PARSE_OPT_LIKE("dv", 3); + } else { + /* SHOW [variable_name] */ + /* This is syntatically the same the normal SHOW syntax + * without MySQL compatibility mode turned on. */ + /* BUG XXXXXXXXXXXXXX This currently works, but in an odd + * manner, and why, I'm not sure. + * + * "SHOW TimeZone;" Doesn't work, but + * "SHOW TimeZone\n;" Does work. + */ + strncpy(my_line, "SHOW", 5); + tkn2 = eat_whitespace(&tkn1[tkn1_len]); + tkn2_len = token_len(tkn2); + strncpy(&my_line[6], tkn2, tkn2_len); + options_string = tkn2; + } + + if (run_cmd == true) + status = exec_command(my_line, options_string, &continue_parse, query_buf, paren_level); + + error: + free(my_line); + query_buf->len = 0; + + return status; +} + + +/*---------- * HandleSlashCmds: * * Handles all the different commands that start with '\', @@ -1726,6 +2105,48 @@ break; } return "unknown"; +} + + +static char *eat_token(char *cp, char *token, size_t token_len) { + size_t cp_pos; + char *p; + + p = cp; + /* Skip past the token */ + cp_pos = strncasecmp(p, token, token_len); + if (cp_pos != 0) + return(NULL); + + /* Skip past the spaces after the token */ + p = eat_whitespace(&p[token_len]); + return(p); +} + +static char *eat_whitespace(char *cp) { + size_t cp_pos; + + if (cp == NULL) + return(NULL); + + for (cp_pos = 0; cp[cp_pos] != NULL; cp_pos++) + if (!isspace(cp[cp_pos])) + break; + + return(&cp[cp_pos]); +} + +static size_t token_len(char *cp) { + size_t cp_pos; + + if (cp == NULL) + return((size_t)0); + + for (cp_pos = 0; cp[cp_pos] != NULL; cp_pos++) + if (isspace(cp[cp_pos])) + break; + + return(cp_pos); } Index: command.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/command.h,v retrieving revision 1.14 diff -u -r1.14 command.h --- command.h 2002/03/27 19:16:13 1.14 +++ command.h 2003/04/16 15:37:53 @@ -26,6 +26,11 @@ } backslashResult; +backslashResult HandleMySQLShowCmds(const char *line, + PQExpBuffer query_buf, + const char **end_of_cmd, + volatile int *paren_level); + backslashResult HandleSlashCmds(const char *line, PQExpBuffer query_buf, const char **end_of_cmd, Index: help.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/help.c,v retrieving revision 1.72 diff -u -r1.72 help.c --- help.c 2003/04/14 16:23:36 1.72 +++ help.c 2003/04/16 15:37:54 @@ -97,6 +97,7 @@ puts(_(" -v NAME=VALUE set psql variable 'NAME' to 'VALUE'")); puts(_(" -X do not read startup file (~/.psqlrc)")); puts(_(" --help show this help, then exit")); + puts(_(" --mysql, -m Run in quasi-MySQL compatibility mode")); puts(_(" --version output version information, then exit")); puts(_("\nInput and output options:")); Index: mainloop.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/mainloop.c,v retrieving revision 1.55 diff -u -r1.55 mainloop.c --- mainloop.c 2003/03/21 03:28:29 1.55 +++ mainloop.c 2003/04/16 15:37:54 @@ -274,7 +274,7 @@ else if (line[i] == '/' && line[i + thislen] == '*') { in_xcomment++; - if (in_xcomment == 1) + if (in_xcomment == 1) ADVANCE_1; } @@ -404,11 +404,12 @@ } /* backslash command */ - else if (bslash_count) + else if (bslash_count || (pset.mode_mysql == true && i > 0 && strncasecmp(&line[i - 1], "SHOW", 4) == 0)) { const char *end_of_cmd = NULL; - line[i - prevlen] = '\0'; /* overwrites backslash */ + if (bslash_count) + line[i - prevlen] = '\0'; /* overwrites backslash */ /* is there anything else on the line for the command? */ if (line[query_start + strspn(line + query_start, " \t\n\r")] != '\0') @@ -423,9 +424,15 @@ appendPQExpBufferStr(query_buf, line + query_start); } - /* handle backslash command */ - slashCmdStatus = HandleSlashCmds(&line[i], - query_buf->len > 0 ? query_buf : previous_buf, + /* Handle backslash command/MySQL compatibility */ + if (pset.mode_mysql == true && strncasecmp(&line[i - 1], "SHOW", 4) == 0) + slashCmdStatus = HandleMySQLShowCmds(&line[i - 1], + query_buf->len > 0 ? query_buf : previous_buf, + &end_of_cmd, + &paren_level); + else + slashCmdStatus = HandleSlashCmds(&line[i], + query_buf->len > 0 ? query_buf : previous_buf, &end_of_cmd, &paren_level); Index: settings.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/settings.h,v retrieving revision 1.13 diff -u -r1.13 settings.h --- settings.h 2002/03/05 00:01:02 1.13 +++ settings.h 2003/04/16 15:37:54 @@ -51,6 +51,7 @@ bool issuper; /* is the current user a superuser? (used * to form the prompt) */ bool timing; /* timing of all queries */ + bool mode_mysql; /* MySQL command compatibility (show commands) */ } PsqlSettings; extern PsqlSettings pset; Index: startup.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/startup.c,v retrieving revision 1.73 diff -u -r1.73 startup.c --- startup.c 2003/04/04 20:42:13 1.73 +++ startup.c 2003/04/16 15:37:54 @@ -128,6 +128,7 @@ pset.cur_cmd_source = stdin; pset.cur_cmd_interactive = false; + pset.mode_mysql = false; pset.encoding = PQenv2encoding(); pset.vars = CreateVariableSpace(); @@ -330,6 +331,7 @@ {"host", required_argument, NULL, 'h'}, {"html", no_argument, NULL, 'H'}, {"list", no_argument, NULL, 'l'}, + {"mysql", no_argument, NULL, 'm'}, {"no-readline", no_argument, NULL, 'n'}, {"output", required_argument, NULL, 'o'}, {"port", required_argument, NULL, 'p'}, @@ -359,7 +361,7 @@ memset(options, 0, sizeof *options); - while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:Hlno:p:P:qR:sStT:uU:v:VWxX?", + while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:Hlmno:p:P:qR:sStT:uU:v:VWxX?", long_options, &optindex)) != -1) { switch (c) @@ -404,6 +406,9 @@ break; case 'l': options->action = ACT_LIST_DB; + break; + case 'm': + pset.mode_mysql = true; break; case 'n': options->no_readline = true; Index: tab-complete.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/bin/psql/tab-complete.c,v retrieving revision 1.76 diff -u -r1.76 tab-complete.c --- tab-complete.c 2003/04/03 20:18:16 1.76 +++ tab-complete.c 2003/04/16 15:37:55 @@ -85,8 +85,11 @@ static char *complete_from_list(const char *text, int state); static PGresult *exec_query(char *query); +static bool find_word_in_list(char **word_list, int *valid_indexes, char *word); char *quote_file_name(char *text, int match_type, char *quote_pointer); +static char **merge_lists(char **list1, char **list2); + /*static char * dequote_file_name(char *text, char quote_char);*/ static char *previous_word(int point, int skip); @@ -435,7 +438,178 @@ {NULL, NO_SCHEMA, NULL} /* end of list */ }; +char *sql_commands[] = { + "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", + "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE", + "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", + "PREPARE", "REINDEX", "RESET", "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", + "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL +}; + +char **mysql_mode_show_merged = NULL; + +char *mysql_mode_show_words[] = { + "AGGREGATES", "CASTS", "CATALOGS", "COLUMNS", "COMMENTS", "CONVERSIONS", "DATABASES", + "DOMAINS", "FUNCTIONS", "HELP", "INDEXES", "LARGEOBJECTS", "OPERATORS", "PRIVILEGES", + "PROCESSLIST", "SCHEMAS", "SEQUENCES", "STATUS", "TABLES", "TYPES", "USERS", + "VARIABLES", "VIEWS", NULL +}; + +int mysql_mode_show_valid_like_commands[] = { + 0, 2, 4, 5, 7, 8, 10, 12, 13, 15, 16, 18, 19, 20, 22, -1 +}; +char *mysql_mode_show_select_origin_words[] = { + "FROM", "LIKE", NULL +}; + +int mysql_mode_show_valid_from_like[] = { + 10, -1 +}; + +char *pgsql_variables[] = { + /* these SET arguments are known in gram.y */ + "CONSTRAINTS", + "NAMES", + "SESSION", + "TRANSACTION", + + /* + * the rest should match USERSET and possibly SUSET entries in + * backend/utils/misc/guc.c. + */ + "australian_timezones", + "authentication_timeout", + "autocommit", + "checkpoint_segments", + "checkpoint_timeout", + "checkpoint_warning", + "client_encoding", + "client_min_messages", + "commit_delay", + "commit_siblings", + "cpu_index_tuple_cost", + "cpu_operator_cost", + "cpu_tuple_cost", + "DateStyle", + "db_user_namespace", + "deadlock_timeout", +#ifdef USE_ASSERT_CHECKING + "debug_assertions", +#endif + "debug_pretty_print", + "debug_print_parse", + "debug_print_plan", + "debug_print_rewritten", +#ifdef LOCK_DEBUG + "debug_deadlocks", +#endif + "default_statistics_target", + "default_transaction_isolation", + "default_transaction_read_only", + "dynamic_library_path", + "effective_cache_size", + "enable_hashagg", + "enable_hashjoin", + "enable_indexscan", + "enable_mergejoin", + "enable_nestloop", + "enable_seqscan", + "enable_sort", + "enable_tidscan", + "explain_pretty_print", + "extra_float_digits", + "from_collapse_limit", + "fsync", + "geqo", + "geqo_effort", + "geqo_generations", + "geqo_pool_size", + "geqo_random_seed", + "geqo_selection_bias", + "geqo_threshold", + "join_collapse_limit", + "krb_server_keyfile", + "lc_messages", + "lc_monetary", + "lc_numeric", + "lc_time", +#ifdef BTREE_BUILD_STATS + "log_btree_build_stats", +#endif + "log_connections", + "log_duration", + "log_executor_stats", + "log_hostname", + "log_min_error_statement", + "log_min_messages", + "log_parser_stats", + "log_pid", + "log_planner_stats", + "log_source_port", + "log_statement", + "log_statement_stats", + "log_timestamp", + "max_connections", + "max_expr_depth", + "max_files_per_process", + "max_fsm_pages", + "max_fsm_relations", + "max_locks_per_transaction", + "password_encryption", + "pre_auth_delay", + "preload_libraries", + "port", + "random_page_cost", + "regex_flavor", + "search_path", + "server_encoding", + "silent_mode", + "shared_buffers", + "seed", + "server_encoding", + "session_authorization", + "shared_buffers", + "sort_mem", + "sql_inheritance", + "ssl", + "statement_timeout", + "stats_block_level", + "stats_command_string", + "stats_reset_on_server_start", + "stats_row_level", + "stats_start_collector", + "superuser_reserved_connections", +#ifdef HAVE_SYSLOG + "syslog", + "syslog_facility", + "syslog_ident", +#endif + "tcpip_socket", + "TimeZone", + "trace_notify", +#ifdef LOCK_DEBUG + "trace_lock_oidmin", + "trace_lock_table", + "trace_locks", + "trace_lwlocks", + "trace_userlocks", +#endif + "transaction_isolation", + "transaction_read_only", + "transform_null_equals", + "unix_socket_directory", + "unix_socket_group", + "unix_socket_permissions", + "vacuum_mem", + "virtual_host", + "wal_buffers", + "wal_debug", + "wal_sync_method", + "zero_damaged_pages", + NULL +}; + /* A couple of macros to ease typing. You can use these to complete the given string with 1) The results from a query you pass it. (Perhaps one of those above?) @@ -473,123 +647,10 @@ *prev3_wd, *prev4_wd; - static char *sql_commands[] = { - "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", - "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE", - "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", - "PREPARE", "REINDEX", "RESET", "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", - "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL - }; - - static char *pgsql_variables[] = { - /* these SET arguments are known in gram.y */ - "CONSTRAINTS", - "NAMES", - "SESSION", - "TRANSACTION", - - /* - * the rest should match USERSET and possibly SUSET entries in - * backend/utils/misc/guc.c. - */ - "australian_timezones", - "autocommit", - "client_encoding", - "client_min_messages", - "commit_delay", - "commit_siblings", - "cpu_index_tuple_cost", - "cpu_operator_cost", - "cpu_tuple_cost", - "DateStyle", - "deadlock_timeout", - "debug_pretty_print", - "debug_print_parse", - "debug_print_plan", - "debug_print_rewritten", - "default_statistics_target", - "default_transaction_isolation", - "default_transaction_read_only", - "dynamic_library_path", - "effective_cache_size", - "enable_hashagg", - "enable_hashjoin", - "enable_indexscan", - "enable_mergejoin", - "enable_nestloop", - "enable_seqscan", - "enable_sort", - "enable_tidscan", - "explain_pretty_print", - "extra_float_digits", - "from_collapse_limit", - "fsync", - "geqo", - "geqo_effort", - "geqo_generations", - "geqo_pool_size", - "geqo_random_seed", - "geqo_selection_bias", - "geqo_threshold", - "join_collapse_limit", - "krb_server_keyfile", - "lc_messages", - "lc_monetary", - "lc_numeric", - "lc_time", - "log_duration", - "log_executor_stats", - "log_min_error_statement", - "log_min_messages", - "log_parser_stats", - "log_planner_stats", - "log_statement", - "log_statement_stats", - "max_connections", - "max_expr_depth", - "max_files_per_process", - "max_fsm_pages", - "max_fsm_relations", - "max_locks_per_transaction", - "password_encryption", - "port", - "random_page_cost", - "regex_flavor", - "search_path", - "shared_buffers", - "seed", - "server_encoding", - "sort_mem", - "sql_inheritance", - "ssl", - "statement_timeout", - "stats_block_level", - "stats_command_string", - "stats_reset_on_server_start", - "stats_row_level", - "stats_start_collector", - "superuser_reserved_connections", - "syslog", - "syslog_facility", - "syslog_ident", - "tcpip_socket", - "TimeZone", - "trace_notify", - "transform_null_equals", - "unix_socket_directory", - "unix_socket_group", - "unix_socket_permissions", - "vacuum_mem", - "wal_buffers", - "wal_debug", - "wal_sync_method", - NULL - }; - static char *backslash_commands[] = { - "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright", + "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright", "\\d", "\\da", "\\dc", "\\dC", "\\dd", "\\dD", "\\df", "\\di", - "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", + "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\e", "\\echo", "\\encoding", "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l", @@ -630,13 +691,13 @@ /* CREATE or DROP but not ALTER TABLE sth DROP */ /* complete with something you can create or drop */ - else if (strcasecmp(prev_wd, "CREATE") == 0 || + else if (strcasecmp(prev_wd, "CREATE") == 0 || (strcasecmp(prev_wd, "DROP") == 0 && strcasecmp(prev3_wd,"TABLE") != 0 )) matches = completion_matches(text, create_command_generator); /* ALTER */ - /* complete with what you can alter (TABLE, GROUP, USER, ...) + /* complete with what you can alter (TABLE, GROUP, USER, ...) * unless we're in ALTER TABLE sth ALTER*/ else if (strcasecmp(prev_wd, "ALTER") == 0 && strcasecmp(prev3_wd, "TABLE") != 0 ) @@ -695,7 +756,7 @@ } /* If we have TABLE DROP COLUMN, provide list of columns */ else if (strcasecmp(prev4_wd, "TABLE") == 0 && - strcasecmp(prev2_wd, "DROP") == 0 && + strcasecmp(prev2_wd, "DROP") == 0 && strcasecmp(prev_wd, "COLUMN") == 0) COMPLETE_WITH_ATTR(prev3_wd); @@ -983,7 +1044,7 @@ "SELECT 'SCHEMA' AS relname "); /* Complete "GRANT/REVOKE * ON * " with "TO" */ - else if ((strcasecmp(prev4_wd, "GRANT") == 0 || + else if ((strcasecmp(prev4_wd, "GRANT") == 0 || strcasecmp(prev4_wd, "REVOKE") == 0) && strcasecmp(prev2_wd, "ON") == 0) { @@ -1102,6 +1163,42 @@ /* SET, RESET, SHOW */ /* Complete with a variable name */ + else if (pset.mode_mysql == true && + (strcasecmp(prev3_wd, "SHOW") == 0 && + find_word_in_list(mysql_mode_show_words, mysql_mode_show_valid_like_commands, prev2_wd) == true && + strncasecmp(prev_wd, "LIKE", 4) == 0)) + { + if (strncasecmp(prev2_wd, "INDEX", 5) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); + else + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + } + else if (pset.mode_mysql == true && (strcasecmp(prev2_wd, "SHOW") == 0 && + find_word_in_list(mysql_mode_show_words, mysql_mode_show_valid_from_like, prev_wd) == true)) + COMPLETE_WITH_LIST(mysql_mode_show_select_origin_words); + else if (pset.mode_mysql == true && (strcasecmp(prev2_wd, "SHOW") == 0 && + find_word_in_list(mysql_mode_show_words, mysql_mode_show_valid_like_commands, prev_wd) == true)) + COMPLETE_WITH_CONST("LIKE"); + else if (pset.mode_mysql == true && (strcasecmp(prev2_wd, "SHOW") == 0 && + strcasecmp(prev_wd, "COLUMNS") == 0)) + COMPLETE_WITH_CONST("FROM"); + else if (pset.mode_mysql == true && (strcasecmp(prev2_wd, "SHOW") == 0 && + strcasecmp(prev_wd, "HELP") == 0)) + COMPLETE_WITH_CONST("ON"); + else if (pset.mode_mysql == true && (strcasecmp(prev3_wd, "SHOW") == 0 && + strcasecmp(prev2_wd, "HELP") == 0 && + strcasecmp(prev_wd, "ON") == 0)) + COMPLETE_WITH_LIST(sql_commands); + else if (pset.mode_mysql == true && strcasecmp(prev_wd, "SHOW") == 0) + { + if (mysql_mode_show_merged == NULL) + mysql_mode_show_merged = merge_lists(mysql_mode_show_words, pgsql_variables); + + if (mysql_mode_show_merged != NULL) + COMPLETE_WITH_LIST(mysql_mode_show_merged); + else + COMPLETE_WITH_LIST(mysql_mode_show_words); + } else if ((strcasecmp(prev_wd, "SET") == 0 && strcasecmp(prev3_wd, "UPDATE") != 0) || strcasecmp(prev_wd, "RESET") == 0 || @@ -1399,9 +1496,9 @@ /* This creates a list of matching things, according to a query pointed to by completion_charp. The query can be one of two kinds: - - A simple query which must contain a %d and a %s, which will be replaced + - A simple query which must contain a %d and a %s, which will be replaced by the string length of the text and the text itself. The query may also - have another %s in it, which will be replaced by the value of + have another %s in it, which will be replaced by the value of completion_info_charp. or: - A schema query used for completion of both schema and relation names; @@ -1658,6 +1755,46 @@ return s; } + + +static bool +find_word_in_list(char **word_list, int *valid_indexes, char *word) +{ + size_t i; + + for (i = 0; valid_indexes[i] != -1; i++) + if (strcasecmp(word_list[valid_indexes[i]], word) == 0) + return(true); + + return(false); +} + +static char ** +merge_lists(char **list1, char **list2) +{ + size_t i, list1_len, list2_len; + char *p; + char **newlist; + + for (list1_len = 0; list1[list1_len] != NULL; list1_len++) + p = list1[list1_len]; + + for (list2_len = 0; list2[list2_len] != NULL; list2_len++) + p = list2[list2_len]; + + newlist = malloc(sizeof(char *) * (list1_len + list2_len)); + if (newlist == NULL) + return(NULL); + + for (list1_len = 0, i = 0; list1[list1_len] != NULL; list1_len++, i++) + newlist[i] = list1[list1_len]; + + for (list2_len = 0; list2[list2_len] != NULL; list2_len++, i++) + newlist[i] = list2[list2_len]; + + newlist[i] = NULL; + return(newlist); +} #if 0