diff options
Diffstat (limited to 'client/mysql.cc')
-rw-r--r-- | client/mysql.cc | 5610 |
1 files changed, 5610 insertions, 0 deletions
diff --git a/client/mysql.cc b/client/mysql.cc new file mode 100644 index 00000000..1c842dae --- /dev/null +++ b/client/mysql.cc @@ -0,0 +1,5610 @@ +/* + Copyright (c) 2000, 2018, Oracle and/or its affiliates. + Copyright (c) 2009, 2022, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* mysql command tool + * Commands compatible with mSQL by David J. Hughes + * + * Written by: + * Michael 'Monty' Widenius + * Andi Gutmans <andi@zend.com> + * Zeev Suraski <zeev@zend.com> + * Jani Tolonen <jani@mysql.com> + * Matt Wagner <matt@mysql.com> + * Jeremy Cole <jcole@mysql.com> + * Tonu Samuel <tonu@mysql.com> + * Harrison Fisk <harrison@mysql.com> + * + **/ + +#include "client_priv.h" +#include <m_ctype.h> +#include <stdarg.h> +#include <my_dir.h> +#ifndef __GNU_LIBRARY__ +#define __GNU_LIBRARY__ // Skip warnings in getopt.h +#endif +#include "my_readline.h" +#include <signal.h> +#include <violite.h> +#include <my_sys.h> +#include <source_revision.h> +#if defined(HAVE_LOCALE_H) +#include <locale.h> +#endif + +const char *VER= "15.1"; + +/* Don't try to make a nice table if the data is too big */ +#define MAX_COLUMN_LENGTH 1024 + +/* Buffer to hold 'version' and 'version_comment' */ +static char *server_version= NULL; + +/* Array of options to pass to libemysqld */ +#define MAX_SERVER_ARGS 64 + +#include "sql_string.h" +#include "client_metadata.h" + +extern "C" { +#if defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) +#include <curses.h> +#include <term.h> +#else +#if defined(HAVE_TERMIOS_H) +#include <termios.h> +#include <unistd.h> +#elif defined(HAVE_TERMBITS_H) +#include <termbits.h> +#elif defined(HAVE_ASM_TERMBITS_H) && (!defined __GLIBC__ || !(__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ > 0)) +#include <asm/termbits.h> // Standard linux +#endif +#undef VOID +#if defined(HAVE_TERMCAP_H) +#include <termcap.h> +#else +#ifdef HAVE_CURSES_H +#include <curses.h> +#endif +#undef SYSV // hack to avoid syntax error +#ifdef HAVE_TERM_H +#include <term.h> +#endif +#endif +#endif /* defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) */ + +#undef bcmp // Fix problem with new readline +#if !defined(_WIN32) +# ifdef __APPLE__ +# include <editline/readline.h> +# else +# include <readline.h> +# if !defined(USE_LIBEDIT_INTERFACE) +# include <history.h> +# endif +# endif +#define HAVE_READLINE +#endif +#define USE_POPEN +} + +static CHARSET_INFO *charset_info= &my_charset_latin1; + +#if defined(_WIN32) +/* + Set console mode for the whole duration of the client session. + + We need for input + - line input (i.e read lines from console) + - echo typed characters + - "cooked" mode, i.e we do not want to handle all keystrokes, + like DEL etc ourselves, yet. We might want handle keystrokes + in the future, to implement tab completion, and better + (multiline) history. + + Disable VT escapes for the output.We do not know what kind of escapes SELECT would return. +*/ +struct Console_mode +{ + HANDLE in= GetStdHandle(STD_INPUT_HANDLE); + HANDLE out= GetStdHandle(STD_OUTPUT_HANDLE); + DWORD mode_in=0; + DWORD mode_out=0; + + enum {STDIN_CHANGED = 1, STDOUT_CHANGED = 2}; + int changes=0; + + Console_mode() + { + if (in && in != INVALID_HANDLE_VALUE && GetConsoleMode(in, &mode_in)) + { + SetConsoleMode(in, ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT); + changes |= STDIN_CHANGED; + } + + if (out && out != INVALID_HANDLE_VALUE && GetConsoleMode(out, &mode_out)) + { +#ifdef ENABLE_VIRTUAL_TERMINAL_INPUT + SetConsoleMode(out, mode_out & ~ENABLE_VIRTUAL_TERMINAL_INPUT); + changes |= STDOUT_CHANGED; +#endif + } + } + + ~Console_mode() + { + if (changes & STDIN_CHANGED) + SetConsoleMode(in, mode_in); + + if(changes & STDOUT_CHANGED) + SetConsoleMode(out, mode_out); + } +}; + +static Console_mode my_conmode; + +#define MAX_CGETS_LINE_LEN 65535 +/** Read line from console, chomp EOL*/ +static char *win_readline() +{ + static wchar_t wstrbuf[MAX_CGETS_LINE_LEN]; + static char strbuf[MAX_CGETS_LINE_LEN * 4]; + + DWORD nchars= 0; + uint len= 0; + SetLastError(0); + if (!ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), wstrbuf, MAX_CGETS_LINE_LEN-1, + &nchars, NULL)) + goto err; + if (nchars == 0 && GetLastError() == ERROR_OPERATION_ABORTED) + goto err; + + for (;nchars > 0; nchars--) + { + if (wstrbuf[nchars - 1] != '\n' && wstrbuf[nchars - 1] != '\r') + break; + } + + if (nchars > 0) + { + uint errors; + len= my_convert(strbuf, sizeof(strbuf), charset_info, + (const char *) wstrbuf, nchars * sizeof(wchar_t), + &my_charset_utf16le_bin, &errors); + } + strbuf[len]= 0; + return strbuf; +err: + return NULL; +} +#endif + + +#ifdef HAVE_VIDATTR +static int have_curses= 0; +static void my_vidattr(chtype attrs) +{ + if (have_curses) + vidattr(attrs); +} +#else +#undef HAVE_SETUPTERM +#define my_vidattr(A) {} // Can't get this to work +#endif + +#ifdef FN_NO_CASE_SENSE +#define cmp_database(cs,A,B) my_strcasecmp((cs), (A), (B)) +#else +#define cmp_database(cs,A,B) strcmp((A),(B)) +#endif + +#include "completion_hash.h" +#include <welcome_copyright_notice.h> // ORACLE_WELCOME_COPYRIGHT_NOTICE + +#define PROMPT_CHAR '\\' +#define DEFAULT_DELIMITER ";" + +#define MAX_BATCH_BUFFER_SIZE (1024L * 1024L * 1024L) + +typedef struct st_status +{ + int exit_status; + ulong query_start_line; + char *file_name; + LINE_BUFFER *line_buff; + bool batch,add_to_history; +} STATUS; + + +static HashTable ht; +static char **defaults_argv; + +enum enum_info_type { INFO_INFO,INFO_ERROR,INFO_RESULT}; +typedef enum enum_info_type INFO_TYPE; + +static MYSQL mysql; /* The connection */ +static my_bool ignore_errors=0,wait_flag=0,quick=0, + connected=0,opt_raw_data=0,unbuffered=0,output_tables=0, + opt_rehash=1,skip_updates=0,safe_updates=0,one_database=0, + opt_compress=0, using_opt_local_infile=0, + vertical=0, line_numbers=1, column_names=1,opt_html=0, + opt_xml=0,opt_nopager=1, opt_outfile=0, named_cmds= 0, + tty_password= 0, opt_nobeep=0, opt_reconnect=1, + opt_secure_auth= 0, + default_pager_set= 0, opt_sigint_ignore= 0, + auto_vertical_output= 0, + show_warnings= 0, executing_query= 0, + ignore_spaces= 0, opt_binhex= 0, opt_progress_reports; +static my_bool debug_info_flag, debug_check_flag, batch_abort_on_error; +static my_bool column_types_flag; +static my_bool preserve_comments= 0; +static my_bool in_com_source, aborted= 0; +static ulong opt_max_allowed_packet, opt_net_buffer_length; +static uint verbose=0,opt_silent=0,opt_mysql_port=0, opt_local_infile=0; +static uint my_end_arg; +static char * opt_mysql_unix_port=0; +static int connect_flag=CLIENT_INTERACTIVE; +static my_bool opt_binary_mode= FALSE; +static my_bool opt_connect_expired_password= FALSE; +static int interrupted_query= 0; +static char *current_host,*current_db,*current_user=0,*opt_password=0, + *current_prompt=0, *delimiter_str= 0, + *default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME, + *opt_init_command= 0; +static char *histfile; +static char *histfile_tmp; +static String glob_buffer,old_buffer; +static String processed_prompt; +static char *full_username=0,*part_username=0,*default_prompt=0; +static int wait_time = 5; +static STATUS status; +static ulong select_limit,max_join_size,opt_connect_timeout=0; +static char mysql_charsets_dir[FN_REFLEN+1]; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; +static const char *xmlmeta[] = { + "&", "&", + "<", "<", + ">", ">", + "\"", """, + /* Turn \0 into a space. Why not �? That's not valid XML or HTML. */ + "\0", " ", + 0, 0 +}; +static const char *day_names[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; +static const char *month_names[]={"Jan","Feb","Mar","Apr","May","Jun","Jul", + "Aug","Sep","Oct","Nov","Dec"}; +static char default_pager[FN_REFLEN]; +static char pager[FN_REFLEN], outfile[FN_REFLEN]; +static FILE *PAGER, *OUTFILE; +static MEM_ROOT hash_mem_root; +static uint prompt_counter; +static char delimiter[16]= DEFAULT_DELIMITER; +static uint delimiter_length= 1; +unsigned short terminal_width= 80; + +static uint opt_protocol=0; +static const char *opt_protocol_type= ""; + +#include "sslopt-vars.h" + +const char *default_dbug_option="d:t:o,/tmp/mariadb.trace"; + +void tee_fprintf(FILE *file, const char *fmt, ...); +void tee_fputs(const char *s, FILE *file); +void tee_puts(const char *s, FILE *file); +void tee_putc(int c, FILE *file); +static void tee_print_sized_data(const char *, unsigned int, unsigned int, bool); +/* The names of functions that actually do the manipulation. */ +static int get_options(int argc,char **argv); +extern "C" my_bool get_one_option(int optid, const struct my_option *opt, + const char *argument); +static int com_quit(String *str,char*), + com_go(String *str,char*), com_ego(String *str,char*), + com_print(String *str,char*), + com_help(String *str,char*), com_clear(String *str,char*), + com_connect(String *str,char*), com_status(String *str,char*), + com_use(String *str,char*), com_source(String *str, char*), + com_rehash(String *str, char*), com_tee(String *str, char*), + com_notee(String *str, char*), com_charset(String *str,char*), + com_prompt(String *str, char*), com_delimiter(String *str, char*), + com_warnings(String *str, char*), com_nowarnings(String *str, char*); + +#ifdef USE_POPEN +static int com_nopager(String *str, char*), com_pager(String *str, char*), + com_edit(String *str,char*), com_shell(String *str, char *); +#endif + +static int read_and_execute(bool interactive); +static int sql_connect(char *host,char *database,char *user,char *password, + uint silent); +static const char *server_version_string(MYSQL *mysql); +static int put_info(const char *str,INFO_TYPE info,uint error=0, + const char *sql_state=0); +static int put_error(MYSQL *mysql); +static void safe_put_field(const char *pos,ulong length); +static void xmlencode_print(const char *src, uint length); +static void init_pager(); +static void end_pager(); +static void init_tee(const char *); +static void end_tee(); +static const char* construct_prompt(); +enum get_arg_mode { CHECK, GET, GET_NEXT}; +static char *get_arg(char *line, get_arg_mode mode); +static void init_username(); +static void add_int_to_prompt(int toadd); +static int get_result_width(MYSQL_RES *res); +static int get_field_disp_length(MYSQL_FIELD * field); +#ifndef EMBEDDED_LIBRARY +static uint last_progress_report_length= 0; +static void report_progress(const MYSQL *mysql, uint stage, uint max_stage, + double progress, const char *proc_info, + uint proc_info_length); +#endif +static void report_progress_end(); + +/* A structure which contains information on the commands this program + can understand. */ + +typedef struct { + const char *name; /* User printable name of the function. */ + char cmd_char; /* msql command character */ + int (*func)(String *str,char *); /* Function to call to do the job. */ + bool takes_params; /* Max parameters for command */ + const char *doc; /* Documentation for this function. */ +} COMMANDS; + +static COMMANDS commands[] = { + { "?", '?', com_help, 1, "Synonym for `help'." }, + { "clear", 'c', com_clear, 0, "Clear the current input statement."}, + { "connect",'r', com_connect,1, + "Reconnect to the server. Optional arguments are db and host." }, + { "delimiter", 'd', com_delimiter, 1, + "Set statement delimiter." }, +#ifdef USE_POPEN + { "edit", 'e', com_edit, 0, "Edit command with $EDITOR."}, +#endif + { "ego", 'G', com_ego, 0, + "Send command to MariaDB server, display result vertically."}, + { "exit", 'q', com_quit, 0, "Exit mysql. Same as quit."}, + { "go", 'g', com_go, 0, "Send command to MariaDB server." }, + { "help", 'h', com_help, 1, "Display this help." }, +#ifdef USE_POPEN + { "nopager",'n', com_nopager,0, "Disable pager, print to stdout." }, +#endif + { "notee", 't', com_notee, 0, "Don't write into outfile." }, +#ifdef USE_POPEN + { "pager", 'P', com_pager, 1, + "Set PAGER [to_pager]. Print the query results via PAGER." }, +#endif + { "print", 'p', com_print, 0, "Print current command." }, + { "prompt", 'R', com_prompt, 1, "Change your mysql prompt."}, + { "quit", 'q', com_quit, 0, "Quit mysql." }, + { "rehash", '#', com_rehash, 0, "Rebuild completion hash." }, + { "source", '.', com_source, 1, + "Execute an SQL script file. Takes a file name as an argument."}, + { "status", 's', com_status, 0, "Get status information from the server."}, +#ifdef USE_POPEN + { "system", '!', com_shell, 1, "Execute a system shell command."}, +#endif + { "tee", 'T', com_tee, 1, + "Set outfile [to_outfile]. Append everything into given outfile." }, + { "use", 'u', com_use, 1, + "Use another database. Takes database name as argument." }, + { "charset", 'C', com_charset, 1, + "Switch to another charset. Might be needed for processing binlog with multi-byte charsets." }, + { "warnings", 'W', com_warnings, 0, + "Show warnings after every statement." }, + { "nowarning", 'w', com_nowarnings, 0, + "Don't show warnings after every statement." }, + /* Get bash-like expansion for some commands */ + { "create table", 0, 0, 0, ""}, + { "create database", 0, 0, 0, ""}, + { "show databases", 0, 0, 0, ""}, + { "show fields from", 0, 0, 0, ""}, + { "show keys from", 0, 0, 0, ""}, + { "show tables", 0, 0, 0, ""}, + { "load data from", 0, 0, 0, ""}, + { "alter table", 0, 0, 0, ""}, + { "set option", 0, 0, 0, ""}, + { "lock tables", 0, 0, 0, ""}, + { "unlock tables", 0, 0, 0, ""}, + /* generated 2006-12-28. Refresh occasionally from lexer. */ + { "ACTION", 0, 0, 0, ""}, + { "ADD", 0, 0, 0, ""}, + { "AFTER", 0, 0, 0, ""}, + { "AGAINST", 0, 0, 0, ""}, + { "AGGREGATE", 0, 0, 0, ""}, + { "ALL", 0, 0, 0, ""}, + { "ALGORITHM", 0, 0, 0, ""}, + { "ALTER", 0, 0, 0, ""}, + { "ANALYZE", 0, 0, 0, ""}, + { "AND", 0, 0, 0, ""}, + { "ANY", 0, 0, 0, ""}, + { "AS", 0, 0, 0, ""}, + { "ASC", 0, 0, 0, ""}, + { "ASCII", 0, 0, 0, ""}, + { "ASENSITIVE", 0, 0, 0, ""}, + { "AUTO_INCREMENT", 0, 0, 0, ""}, + { "AVG", 0, 0, 0, ""}, + { "AVG_ROW_LENGTH", 0, 0, 0, ""}, + { "BACKUP", 0, 0, 0, ""}, + { "BDB", 0, 0, 0, ""}, + { "BEFORE", 0, 0, 0, ""}, + { "BEGIN", 0, 0, 0, ""}, + { "BERKELEYDB", 0, 0, 0, ""}, + { "BETWEEN", 0, 0, 0, ""}, + { "BIGINT", 0, 0, 0, ""}, + { "BINARY", 0, 0, 0, ""}, + { "BINLOG", 0, 0, 0, ""}, + { "BIT", 0, 0, 0, ""}, + { "BLOB", 0, 0, 0, ""}, + { "BOOL", 0, 0, 0, ""}, + { "BOOLEAN", 0, 0, 0, ""}, + { "BOTH", 0, 0, 0, ""}, + { "BTREE", 0, 0, 0, ""}, + { "BY", 0, 0, 0, ""}, + { "BYTE", 0, 0, 0, ""}, + { "CACHE", 0, 0, 0, ""}, + { "CALL", 0, 0, 0, ""}, + { "CASCADE", 0, 0, 0, ""}, + { "CASCADED", 0, 0, 0, ""}, + { "CASE", 0, 0, 0, ""}, + { "CHAIN", 0, 0, 0, ""}, + { "CHANGE", 0, 0, 0, ""}, + { "CHANGED", 0, 0, 0, ""}, + { "CHAR", 0, 0, 0, ""}, + { "CHARACTER", 0, 0, 0, ""}, + { "CHARSET", 0, 0, 0, ""}, + { "CHECK", 0, 0, 0, ""}, + { "CHECKSUM", 0, 0, 0, ""}, + { "CIPHER", 0, 0, 0, ""}, + { "CLIENT", 0, 0, 0, ""}, + { "CLOSE", 0, 0, 0, ""}, + { "CODE", 0, 0, 0, ""}, + { "COLLATE", 0, 0, 0, ""}, + { "COLLATION", 0, 0, 0, ""}, + { "COLUMN", 0, 0, 0, ""}, + { "COLUMNS", 0, 0, 0, ""}, + { "COMMENT", 0, 0, 0, ""}, + { "COMMIT", 0, 0, 0, ""}, + { "COMMITTED", 0, 0, 0, ""}, + { "COMPACT", 0, 0, 0, ""}, + { "COMPRESSED", 0, 0, 0, ""}, + { "CONCURRENT", 0, 0, 0, ""}, + { "CONDITION", 0, 0, 0, ""}, + { "CONNECTION", 0, 0, 0, ""}, + { "CONSISTENT", 0, 0, 0, ""}, + { "CONSTRAINT", 0, 0, 0, ""}, + { "CONTAINS", 0, 0, 0, ""}, + { "CONTINUE", 0, 0, 0, ""}, + { "CONVERT", 0, 0, 0, ""}, + { "CREATE", 0, 0, 0, ""}, + { "CROSS", 0, 0, 0, ""}, + { "CUBE", 0, 0, 0, ""}, + { "CURRENT_DATE", 0, 0, 0, ""}, + { "CURRENT_TIME", 0, 0, 0, ""}, + { "CURRENT_TIMESTAMP", 0, 0, 0, ""}, + { "CURRENT_USER", 0, 0, 0, ""}, + { "CURSOR", 0, 0, 0, ""}, + { "DATA", 0, 0, 0, ""}, + { "DATABASE", 0, 0, 0, ""}, + { "DATABASES", 0, 0, 0, ""}, + { "DATE", 0, 0, 0, ""}, + { "DATETIME", 0, 0, 0, ""}, + { "DAY", 0, 0, 0, ""}, + { "DAY_HOUR", 0, 0, 0, ""}, + { "DAY_MICROSECOND", 0, 0, 0, ""}, + { "DAY_MINUTE", 0, 0, 0, ""}, + { "DAY_SECOND", 0, 0, 0, ""}, + { "DEALLOCATE", 0, 0, 0, ""}, + { "DEC", 0, 0, 0, ""}, + { "DECIMAL", 0, 0, 0, ""}, + { "DECLARE", 0, 0, 0, ""}, + { "DEFAULT", 0, 0, 0, ""}, + { "DEFINER", 0, 0, 0, ""}, + { "DELAYED", 0, 0, 0, ""}, + { "DELAY_KEY_WRITE", 0, 0, 0, ""}, + { "DELETE", 0, 0, 0, ""}, + { "DESC", 0, 0, 0, ""}, + { "DESCRIBE", 0, 0, 0, ""}, + { "DES_KEY_FILE", 0, 0, 0, ""}, + { "DETERMINISTIC", 0, 0, 0, ""}, + { "DIRECTORY", 0, 0, 0, ""}, + { "DISABLE", 0, 0, 0, ""}, + { "DISCARD", 0, 0, 0, ""}, + { "DISTINCT", 0, 0, 0, ""}, + { "DISTINCTROW", 0, 0, 0, ""}, + { "DIV", 0, 0, 0, ""}, + { "DO", 0, 0, 0, ""}, + { "DOUBLE", 0, 0, 0, ""}, + { "DROP", 0, 0, 0, ""}, + { "DUAL", 0, 0, 0, ""}, + { "DUMPFILE", 0, 0, 0, ""}, + { "DUPLICATE", 0, 0, 0, ""}, + { "DYNAMIC", 0, 0, 0, ""}, + { "EACH", 0, 0, 0, ""}, + { "ELSE", 0, 0, 0, ""}, + { "ELSEIF", 0, 0, 0, ""}, + { "ENABLE", 0, 0, 0, ""}, + { "ENCLOSED", 0, 0, 0, ""}, + { "END", 0, 0, 0, ""}, + { "ENGINE", 0, 0, 0, ""}, + { "ENGINES", 0, 0, 0, ""}, + { "ENUM", 0, 0, 0, ""}, + { "ERRORS", 0, 0, 0, ""}, + { "ESCAPE", 0, 0, 0, ""}, + { "ESCAPED", 0, 0, 0, ""}, + { "EVENTS", 0, 0, 0, ""}, + { "EXECUTE", 0, 0, 0, ""}, + { "EXISTS", 0, 0, 0, ""}, + { "EXIT", 0, 0, 0, ""}, + { "EXPANSION", 0, 0, 0, ""}, + { "EXPLAIN", 0, 0, 0, ""}, + { "EXTENDED", 0, 0, 0, ""}, + { "FALSE", 0, 0, 0, ""}, + { "FAST", 0, 0, 0, ""}, + { "FETCH", 0, 0, 0, ""}, + { "FIELDS", 0, 0, 0, ""}, + { "FILE", 0, 0, 0, ""}, + { "FIRST", 0, 0, 0, ""}, + { "FIXED", 0, 0, 0, ""}, + { "FLOAT", 0, 0, 0, ""}, + { "FLOAT4", 0, 0, 0, ""}, + { "FLOAT8", 0, 0, 0, ""}, + { "FLUSH", 0, 0, 0, ""}, + { "FOR", 0, 0, 0, ""}, + { "FORCE", 0, 0, 0, ""}, + { "FOREIGN", 0, 0, 0, ""}, + { "FOUND", 0, 0, 0, ""}, + { "FROM", 0, 0, 0, ""}, + { "FULL", 0, 0, 0, ""}, + { "FULLTEXT", 0, 0, 0, ""}, + { "FUNCTION", 0, 0, 0, ""}, + { "GEOMETRY", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTION", 0, 0, 0, ""}, + { "GET_FORMAT", 0, 0, 0, ""}, + { "GLOBAL", 0, 0, 0, ""}, + { "GRANT", 0, 0, 0, ""}, + { "GRANTS", 0, 0, 0, ""}, + { "GROUP", 0, 0, 0, ""}, + { "HANDLER", 0, 0, 0, ""}, + { "HASH", 0, 0, 0, ""}, + { "HAVING", 0, 0, 0, ""}, + { "HELP", 0, 0, 0, ""}, + { "HIGH_PRIORITY", 0, 0, 0, ""}, + { "HOSTS", 0, 0, 0, ""}, + { "HOUR", 0, 0, 0, ""}, + { "HOUR_MICROSECOND", 0, 0, 0, ""}, + { "HOUR_MINUTE", 0, 0, 0, ""}, + { "HOUR_SECOND", 0, 0, 0, ""}, + { "IDENTIFIED", 0, 0, 0, ""}, + { "IF", 0, 0, 0, ""}, + { "IGNORE", 0, 0, 0, ""}, + { "IMPORT", 0, 0, 0, ""}, + { "IN", 0, 0, 0, ""}, + { "INDEX", 0, 0, 0, ""}, + { "INDEXES", 0, 0, 0, ""}, + { "INFILE", 0, 0, 0, ""}, + { "INNER", 0, 0, 0, ""}, + { "INNOBASE", 0, 0, 0, ""}, + { "INNODB", 0, 0, 0, ""}, + { "INOUT", 0, 0, 0, ""}, + { "INSENSITIVE", 0, 0, 0, ""}, + { "INSERT", 0, 0, 0, ""}, + { "INSERT_METHOD", 0, 0, 0, ""}, + { "INT", 0, 0, 0, ""}, + { "INT1", 0, 0, 0, ""}, + { "INT2", 0, 0, 0, ""}, + { "INT3", 0, 0, 0, ""}, + { "INT4", 0, 0, 0, ""}, + { "INT8", 0, 0, 0, ""}, + { "INTEGER", 0, 0, 0, ""}, + { "INTERVAL", 0, 0, 0, ""}, + { "INTO", 0, 0, 0, ""}, + { "IO_THREAD", 0, 0, 0, ""}, + { "IS", 0, 0, 0, ""}, + { "ISOLATION", 0, 0, 0, ""}, + { "ISSUER", 0, 0, 0, ""}, + { "ITERATE", 0, 0, 0, ""}, + { "INVOKER", 0, 0, 0, ""}, + { "JOIN", 0, 0, 0, ""}, + { "KEY", 0, 0, 0, ""}, + { "KEYS", 0, 0, 0, ""}, + { "KILL", 0, 0, 0, ""}, + { "LANGUAGE", 0, 0, 0, ""}, + { "LAST", 0, 0, 0, ""}, + { "LEADING", 0, 0, 0, ""}, + { "LEAVE", 0, 0, 0, ""}, + { "LEAVES", 0, 0, 0, ""}, + { "LEFT", 0, 0, 0, ""}, + { "LEVEL", 0, 0, 0, ""}, + { "LIKE", 0, 0, 0, ""}, + { "LIMIT", 0, 0, 0, ""}, + { "LINES", 0, 0, 0, ""}, + { "LINESTRING", 0, 0, 0, ""}, + { "LOAD", 0, 0, 0, ""}, + { "LOCAL", 0, 0, 0, ""}, + { "LOCALTIME", 0, 0, 0, ""}, + { "LOCALTIMESTAMP", 0, 0, 0, ""}, + { "LOCK", 0, 0, 0, ""}, + { "LOCKS", 0, 0, 0, ""}, + { "LOGS", 0, 0, 0, ""}, + { "LONG", 0, 0, 0, ""}, + { "LONGBLOB", 0, 0, 0, ""}, + { "LONGTEXT", 0, 0, 0, ""}, + { "LOOP", 0, 0, 0, ""}, + { "LOW_PRIORITY", 0, 0, 0, ""}, + { "MASTER", 0, 0, 0, ""}, + { "MASTER_CONNECT_RETRY", 0, 0, 0, ""}, + { "MASTER_HOST", 0, 0, 0, ""}, + { "MASTER_LOG_FILE", 0, 0, 0, ""}, + { "MASTER_LOG_POS", 0, 0, 0, ""}, + { "MASTER_PASSWORD", 0, 0, 0, ""}, + { "MASTER_PORT", 0, 0, 0, ""}, + { "MASTER_SERVER_ID", 0, 0, 0, ""}, + { "MASTER_SSL", 0, 0, 0, ""}, + { "MASTER_SSL_CA", 0, 0, 0, ""}, + { "MASTER_SSL_CAPATH", 0, 0, 0, ""}, + { "MASTER_SSL_CERT", 0, 0, 0, ""}, + { "MASTER_SSL_CIPHER", 0, 0, 0, ""}, + { "MASTER_SSL_KEY", 0, 0, 0, ""}, + { "MASTER_USER", 0, 0, 0, ""}, + { "MATCH", 0, 0, 0, ""}, + { "MAX_CONNECTIONS_PER_HOUR", 0, 0, 0, ""}, + { "MAX_QUERIES_PER_HOUR", 0, 0, 0, ""}, + { "MAX_ROWS", 0, 0, 0, ""}, + { "MAX_UPDATES_PER_HOUR", 0, 0, 0, ""}, + { "MAX_USER_CONNECTIONS", 0, 0, 0, ""}, + { "MEDIUM", 0, 0, 0, ""}, + { "MEDIUMBLOB", 0, 0, 0, ""}, + { "MEDIUMINT", 0, 0, 0, ""}, + { "MEDIUMTEXT", 0, 0, 0, ""}, + { "MERGE", 0, 0, 0, ""}, + { "MICROSECOND", 0, 0, 0, ""}, + { "MIDDLEINT", 0, 0, 0, ""}, + { "MIGRATE", 0, 0, 0, ""}, + { "MINUTE", 0, 0, 0, ""}, + { "MINUTE_MICROSECOND", 0, 0, 0, ""}, + { "MINUTE_SECOND", 0, 0, 0, ""}, + { "MIN_ROWS", 0, 0, 0, ""}, + { "MOD", 0, 0, 0, ""}, + { "MODE", 0, 0, 0, ""}, + { "MODIFIES", 0, 0, 0, ""}, + { "MODIFY", 0, 0, 0, ""}, + { "MONTH", 0, 0, 0, ""}, + { "MULTILINESTRING", 0, 0, 0, ""}, + { "MULTIPOINT", 0, 0, 0, ""}, + { "MULTIPOLYGON", 0, 0, 0, ""}, + { "MUTEX", 0, 0, 0, ""}, + { "NAME", 0, 0, 0, ""}, + { "NAMES", 0, 0, 0, ""}, + { "NATIONAL", 0, 0, 0, ""}, + { "NATURAL", 0, 0, 0, ""}, + { "NCHAR", 0, 0, 0, ""}, + { "NEW", 0, 0, 0, ""}, + { "NEXT", 0, 0, 0, ""}, + { "NO", 0, 0, 0, ""}, + { "NONE", 0, 0, 0, ""}, + { "NOT", 0, 0, 0, ""}, + { "NO_WRITE_TO_BINLOG", 0, 0, 0, ""}, + { "NULL", 0, 0, 0, ""}, + { "NUMERIC", 0, 0, 0, ""}, + { "NVARCHAR", 0, 0, 0, ""}, + { "OFFSET", 0, 0, 0, ""}, + { "OLD_PASSWORD", 0, 0, 0, ""}, + { "ON", 0, 0, 0, ""}, + { "ONE", 0, 0, 0, ""}, + { "OPEN", 0, 0, 0, ""}, + { "OPTIMIZE", 0, 0, 0, ""}, + { "OPTION", 0, 0, 0, ""}, + { "OPTIONALLY", 0, 0, 0, ""}, + { "OR", 0, 0, 0, ""}, + { "ORDER", 0, 0, 0, ""}, + { "OUT", 0, 0, 0, ""}, + { "OUTER", 0, 0, 0, ""}, + { "OUTFILE", 0, 0, 0, ""}, + { "PACK_KEYS", 0, 0, 0, ""}, + { "PARTIAL", 0, 0, 0, ""}, + { "PASSWORD", 0, 0, 0, ""}, + { "PHASE", 0, 0, 0, ""}, + { "POINT", 0, 0, 0, ""}, + { "POLYGON", 0, 0, 0, ""}, + { "PRECISION", 0, 0, 0, ""}, + { "PREPARE", 0, 0, 0, ""}, + { "PREV", 0, 0, 0, ""}, + { "PRIMARY", 0, 0, 0, ""}, + { "PRIVILEGES", 0, 0, 0, ""}, + { "PROCEDURE", 0, 0, 0, ""}, + { "PROCESS", 0, 0, 0, ""}, + { "PROCESSLIST", 0, 0, 0, ""}, + { "PURGE", 0, 0, 0, ""}, + { "QUARTER", 0, 0, 0, ""}, + { "QUERY", 0, 0, 0, ""}, + { "QUICK", 0, 0, 0, ""}, + { "READ", 0, 0, 0, ""}, + { "READS", 0, 0, 0, ""}, + { "REAL", 0, 0, 0, ""}, + { "RECOVER", 0, 0, 0, ""}, + { "REDUNDANT", 0, 0, 0, ""}, + { "REFERENCES", 0, 0, 0, ""}, + { "REGEXP", 0, 0, 0, ""}, + { "RELAY_LOG_FILE", 0, 0, 0, ""}, + { "RELAY_LOG_POS", 0, 0, 0, ""}, + { "RELAY_THREAD", 0, 0, 0, ""}, + { "RELEASE", 0, 0, 0, ""}, + { "RELOAD", 0, 0, 0, ""}, + { "RENAME", 0, 0, 0, ""}, + { "REPAIR", 0, 0, 0, ""}, + { "REPEATABLE", 0, 0, 0, ""}, + { "REPLACE", 0, 0, 0, ""}, + { "REPLICATION", 0, 0, 0, ""}, + { "REPEAT", 0, 0, 0, ""}, + { "REQUIRE", 0, 0, 0, ""}, + { "RESET", 0, 0, 0, ""}, + { "RESTORE", 0, 0, 0, ""}, + { "RESTRICT", 0, 0, 0, ""}, + { "RESUME", 0, 0, 0, ""}, + { "RETURN", 0, 0, 0, ""}, + { "RETURNS", 0, 0, 0, ""}, + { "REVOKE", 0, 0, 0, ""}, + { "RIGHT", 0, 0, 0, ""}, + { "RLIKE", 0, 0, 0, ""}, + { "ROLLBACK", 0, 0, 0, ""}, + { "ROLLUP", 0, 0, 0, ""}, + { "ROUTINE", 0, 0, 0, ""}, + { "ROW", 0, 0, 0, ""}, + { "ROWS", 0, 0, 0, ""}, + { "ROW_FORMAT", 0, 0, 0, ""}, + { "RTREE", 0, 0, 0, ""}, + { "SAVEPOINT", 0, 0, 0, ""}, + { "SCHEMA", 0, 0, 0, ""}, + { "SCHEMAS", 0, 0, 0, ""}, + { "SECOND", 0, 0, 0, ""}, + { "SECOND_MICROSECOND", 0, 0, 0, ""}, + { "SECURITY", 0, 0, 0, ""}, + { "SELECT", 0, 0, 0, ""}, + { "SENSITIVE", 0, 0, 0, ""}, + { "SEPARATOR", 0, 0, 0, ""}, + { "SERIAL", 0, 0, 0, ""}, + { "SERIALIZABLE", 0, 0, 0, ""}, + { "SESSION", 0, 0, 0, ""}, + { "SET", 0, 0, 0, ""}, + { "SHARE", 0, 0, 0, ""}, + { "SHOW", 0, 0, 0, ""}, + { "SHUTDOWN", 0, 0, 0, ""}, + { "SIGNED", 0, 0, 0, ""}, + { "SIMPLE", 0, 0, 0, ""}, + { "SLAVE", 0, 0, 0, ""}, + { "SNAPSHOT", 0, 0, 0, ""}, + { "SMALLINT", 0, 0, 0, ""}, + { "SOME", 0, 0, 0, ""}, + { "SONAME", 0, 0, 0, ""}, + { "SOUNDS", 0, 0, 0, ""}, + { "SPATIAL", 0, 0, 0, ""}, + { "SPECIFIC", 0, 0, 0, ""}, + { "SQL", 0, 0, 0, ""}, + { "SQLEXCEPTION", 0, 0, 0, ""}, + { "SQLSTATE", 0, 0, 0, ""}, + { "SQLWARNING", 0, 0, 0, ""}, + { "SQL_BIG_RESULT", 0, 0, 0, ""}, + { "SQL_BUFFER_RESULT", 0, 0, 0, ""}, + { "SQL_CACHE", 0, 0, 0, ""}, + { "SQL_CALC_FOUND_ROWS", 0, 0, 0, ""}, + { "SQL_NO_CACHE", 0, 0, 0, ""}, + { "SQL_SMALL_RESULT", 0, 0, 0, ""}, + { "SQL_THREAD", 0, 0, 0, ""}, + { "SQL_TSI_SECOND", 0, 0, 0, ""}, + { "SQL_TSI_MINUTE", 0, 0, 0, ""}, + { "SQL_TSI_HOUR", 0, 0, 0, ""}, + { "SQL_TSI_DAY", 0, 0, 0, ""}, + { "SQL_TSI_WEEK", 0, 0, 0, ""}, + { "SQL_TSI_MONTH", 0, 0, 0, ""}, + { "SQL_TSI_QUARTER", 0, 0, 0, ""}, + { "SQL_TSI_YEAR", 0, 0, 0, ""}, + { "SSL", 0, 0, 0, ""}, + { "START", 0, 0, 0, ""}, + { "STARTING", 0, 0, 0, ""}, + { "STATUS", 0, 0, 0, ""}, + { "STOP", 0, 0, 0, ""}, + { "STORAGE", 0, 0, 0, ""}, + { "STRAIGHT_JOIN", 0, 0, 0, ""}, + { "STRING", 0, 0, 0, ""}, + { "STRIPED", 0, 0, 0, ""}, + { "SUBJECT", 0, 0, 0, ""}, + { "SUPER", 0, 0, 0, ""}, + { "SUSPEND", 0, 0, 0, ""}, + { "TABLE", 0, 0, 0, ""}, + { "TABLES", 0, 0, 0, ""}, + { "TABLESPACE", 0, 0, 0, ""}, + { "TEMPORARY", 0, 0, 0, ""}, + { "TEMPTABLE", 0, 0, 0, ""}, + { "TERMINATED", 0, 0, 0, ""}, + { "TEXT", 0, 0, 0, ""}, + { "THEN", 0, 0, 0, ""}, + { "TIME", 0, 0, 0, ""}, + { "TIMESTAMP", 0, 0, 0, ""}, + { "TIMESTAMPADD", 0, 0, 0, ""}, + { "TIMESTAMPDIFF", 0, 0, 0, ""}, + { "TINYBLOB", 0, 0, 0, ""}, + { "TINYINT", 0, 0, 0, ""}, + { "TINYTEXT", 0, 0, 0, ""}, + { "TO", 0, 0, 0, ""}, + { "TRAILING", 0, 0, 0, ""}, + { "TRANSACTION", 0, 0, 0, ""}, + { "TRIGGER", 0, 0, 0, ""}, + { "TRIGGERS", 0, 0, 0, ""}, + { "TRUE", 0, 0, 0, ""}, + { "TRUNCATE", 0, 0, 0, ""}, + { "TYPE", 0, 0, 0, ""}, + { "TYPES", 0, 0, 0, ""}, + { "UNCOMMITTED", 0, 0, 0, ""}, + { "UNDEFINED", 0, 0, 0, ""}, + { "UNDO", 0, 0, 0, ""}, + { "UNICODE", 0, 0, 0, ""}, + { "UNION", 0, 0, 0, ""}, + { "UNIQUE", 0, 0, 0, ""}, + { "UNKNOWN", 0, 0, 0, ""}, + { "UNLOCK", 0, 0, 0, ""}, + { "UNSIGNED", 0, 0, 0, ""}, + { "UNTIL", 0, 0, 0, ""}, + { "UPDATE", 0, 0, 0, ""}, + { "UPGRADE", 0, 0, 0, ""}, + { "USAGE", 0, 0, 0, ""}, + { "USE", 0, 0, 0, ""}, + { "USER", 0, 0, 0, ""}, + { "USER_RESOURCES", 0, 0, 0, ""}, + { "USE_FRM", 0, 0, 0, ""}, + { "USING", 0, 0, 0, ""}, + { "UTC_DATE", 0, 0, 0, ""}, + { "UTC_TIME", 0, 0, 0, ""}, + { "UTC_TIMESTAMP", 0, 0, 0, ""}, + { "VALUE", 0, 0, 0, ""}, + { "VALUES", 0, 0, 0, ""}, + { "VARBINARY", 0, 0, 0, ""}, + { "VARCHAR", 0, 0, 0, ""}, + { "VARCHARACTER", 0, 0, 0, ""}, + { "VARIABLES", 0, 0, 0, ""}, + { "VARYING", 0, 0, 0, ""}, + { "WARNINGS", 0, 0, 0, ""}, + { "WEEK", 0, 0, 0, ""}, + { "WHEN", 0, 0, 0, ""}, + { "WHERE", 0, 0, 0, ""}, + { "WHILE", 0, 0, 0, ""}, + { "VIEW", 0, 0, 0, ""}, + { "WITH", 0, 0, 0, ""}, + { "WORK", 0, 0, 0, ""}, + { "WRITE", 0, 0, 0, ""}, + { "X509", 0, 0, 0, ""}, + { "XOR", 0, 0, 0, ""}, + { "XA", 0, 0, 0, ""}, + { "YEAR", 0, 0, 0, ""}, + { "YEAR_MONTH", 0, 0, 0, ""}, + { "ZEROFILL", 0, 0, 0, ""}, + { "ABS", 0, 0, 0, ""}, + { "ACOS", 0, 0, 0, ""}, + { "ADDDATE", 0, 0, 0, ""}, + { "ADDTIME", 0, 0, 0, ""}, + { "AES_ENCRYPT", 0, 0, 0, ""}, + { "AES_DECRYPT", 0, 0, 0, ""}, + { "AREA", 0, 0, 0, ""}, + { "ASIN", 0, 0, 0, ""}, + { "ASBINARY", 0, 0, 0, ""}, + { "ASTEXT", 0, 0, 0, ""}, + { "ASWKB", 0, 0, 0, ""}, + { "ASWKT", 0, 0, 0, ""}, + { "ATAN", 0, 0, 0, ""}, + { "ATAN2", 0, 0, 0, ""}, + { "BENCHMARK", 0, 0, 0, ""}, + { "BIN", 0, 0, 0, ""}, + { "BIT_COUNT", 0, 0, 0, ""}, + { "BIT_OR", 0, 0, 0, ""}, + { "BIT_AND", 0, 0, 0, ""}, + { "BIT_XOR", 0, 0, 0, ""}, + { "CAST", 0, 0, 0, ""}, + { "CEIL", 0, 0, 0, ""}, + { "CEILING", 0, 0, 0, ""}, + { "BIT_LENGTH", 0, 0, 0, ""}, + { "CENTROID", 0, 0, 0, ""}, + { "CHAR_LENGTH", 0, 0, 0, ""}, + { "CHARACTER_LENGTH", 0, 0, 0, ""}, + { "COALESCE", 0, 0, 0, ""}, + { "COERCIBILITY", 0, 0, 0, ""}, + { "COMPRESS", 0, 0, 0, ""}, + { "CONCAT", 0, 0, 0, ""}, + { "CONCAT_WS", 0, 0, 0, ""}, + { "CONNECTION_ID", 0, 0, 0, ""}, + { "CONV", 0, 0, 0, ""}, + { "CONVERT_TZ", 0, 0, 0, ""}, + { "COUNT", 0, 0, 0, ""}, + { "COS", 0, 0, 0, ""}, + { "COT", 0, 0, 0, ""}, + { "CRC32", 0, 0, 0, ""}, + { "CROSSES", 0, 0, 0, ""}, + { "CURDATE", 0, 0, 0, ""}, + { "CURTIME", 0, 0, 0, ""}, + { "DATE_ADD", 0, 0, 0, ""}, + { "DATEDIFF", 0, 0, 0, ""}, + { "DATE_FORMAT", 0, 0, 0, ""}, + { "DATE_SUB", 0, 0, 0, ""}, + { "DAYNAME", 0, 0, 0, ""}, + { "DAYOFMONTH", 0, 0, 0, ""}, + { "DAYOFWEEK", 0, 0, 0, ""}, + { "DAYOFYEAR", 0, 0, 0, ""}, + { "DECODE", 0, 0, 0, ""}, + { "DEGREES", 0, 0, 0, ""}, + { "DES_ENCRYPT", 0, 0, 0, ""}, + { "DES_DECRYPT", 0, 0, 0, ""}, + { "DIMENSION", 0, 0, 0, ""}, + { "DISJOINT", 0, 0, 0, ""}, + { "ELT", 0, 0, 0, ""}, + { "ENCODE", 0, 0, 0, ""}, + { "ENCRYPT", 0, 0, 0, ""}, + { "ENDPOINT", 0, 0, 0, ""}, + { "ENVELOPE", 0, 0, 0, ""}, + { "EQUALS", 0, 0, 0, ""}, + { "EXTERIORRING", 0, 0, 0, ""}, + { "EXTRACT", 0, 0, 0, ""}, + { "EXP", 0, 0, 0, ""}, + { "EXPORT_SET", 0, 0, 0, ""}, + { "FIELD", 0, 0, 0, ""}, + { "FIND_IN_SET", 0, 0, 0, ""}, + { "FLOOR", 0, 0, 0, ""}, + { "FORMAT", 0, 0, 0, ""}, + { "FOUND_ROWS", 0, 0, 0, ""}, + { "FROM_DAYS", 0, 0, 0, ""}, + { "FROM_UNIXTIME", 0, 0, 0, ""}, + { "GET_LOCK", 0, 0, 0, ""}, + { "GEOMETRYN", 0, 0, 0, ""}, + { "GEOMETRYTYPE", 0, 0, 0, ""}, + { "GEOMCOLLFROMTEXT", 0, 0, 0, ""}, + { "GEOMCOLLFROMWKB", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTIONFROMTEXT", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTIONFROMWKB", 0, 0, 0, ""}, + { "GEOMETRYFROMTEXT", 0, 0, 0, ""}, + { "GEOMETRYFROMWKB", 0, 0, 0, ""}, + { "GEOMFROMTEXT", 0, 0, 0, ""}, + { "GEOMFROMWKB", 0, 0, 0, ""}, + { "GLENGTH", 0, 0, 0, ""}, + { "GREATEST", 0, 0, 0, ""}, + { "GROUP_CONCAT", 0, 0, 0, ""}, + { "GROUP_UNIQUE_USERS", 0, 0, 0, ""}, + { "HEX", 0, 0, 0, ""}, + { "IFNULL", 0, 0, 0, ""}, + { "INET_ATON", 0, 0, 0, ""}, + { "INET_NTOA", 0, 0, 0, ""}, + { "INSTR", 0, 0, 0, ""}, + { "INTERIORRINGN", 0, 0, 0, ""}, + { "INTERSECTS", 0, 0, 0, ""}, + { "ISCLOSED", 0, 0, 0, ""}, + { "ISEMPTY", 0, 0, 0, ""}, + { "ISNULL", 0, 0, 0, ""}, + { "IS_FREE_LOCK", 0, 0, 0, ""}, + { "IS_USED_LOCK", 0, 0, 0, ""}, + { "LAST_INSERT_ID", 0, 0, 0, ""}, + { "ISSIMPLE", 0, 0, 0, ""}, + { "LAST_DAY", 0, 0, 0, ""}, + { "LAST_VALUE", 0, 0, 0, ""}, + { "LCASE", 0, 0, 0, ""}, + { "LEAST", 0, 0, 0, ""}, + { "LENGTH", 0, 0, 0, ""}, + { "LN", 0, 0, 0, ""}, + { "LINEFROMTEXT", 0, 0, 0, ""}, + { "LINEFROMWKB", 0, 0, 0, ""}, + { "LINESTRINGFROMTEXT", 0, 0, 0, ""}, + { "LINESTRINGFROMWKB", 0, 0, 0, ""}, + { "LOAD_FILE", 0, 0, 0, ""}, + { "LOCATE", 0, 0, 0, ""}, + { "LOG", 0, 0, 0, ""}, + { "LOG2", 0, 0, 0, ""}, + { "LOG10", 0, 0, 0, ""}, + { "LOWER", 0, 0, 0, ""}, + { "LPAD", 0, 0, 0, ""}, + { "LTRIM", 0, 0, 0, ""}, + { "MAKE_SET", 0, 0, 0, ""}, + { "MAKEDATE", 0, 0, 0, ""}, + { "MAKETIME", 0, 0, 0, ""}, + { "MASTER_GTID_WAIT", 0, 0, 0, ""}, + { "MASTER_POS_WAIT", 0, 0, 0, ""}, + { "MAX", 0, 0, 0, ""}, + { "MBRCONTAINS", 0, 0, 0, ""}, + { "MBRDISJOINT", 0, 0, 0, ""}, + { "MBREQUAL", 0, 0, 0, ""}, + { "MBRINTERSECTS", 0, 0, 0, ""}, + { "MBROVERLAPS", 0, 0, 0, ""}, + { "MBRTOUCHES", 0, 0, 0, ""}, + { "MBRWITHIN", 0, 0, 0, ""}, + { "MD5", 0, 0, 0, ""}, + { "MID", 0, 0, 0, ""}, + { "MIN", 0, 0, 0, ""}, + { "MLINEFROMTEXT", 0, 0, 0, ""}, + { "MLINEFROMWKB", 0, 0, 0, ""}, + { "MPOINTFROMTEXT", 0, 0, 0, ""}, + { "MPOINTFROMWKB", 0, 0, 0, ""}, + { "MPOLYFROMTEXT", 0, 0, 0, ""}, + { "MPOLYFROMWKB", 0, 0, 0, ""}, + { "MONTHNAME", 0, 0, 0, ""}, + { "MULTILINESTRINGFROMTEXT", 0, 0, 0, ""}, + { "MULTILINESTRINGFROMWKB", 0, 0, 0, ""}, + { "MULTIPOINTFROMTEXT", 0, 0, 0, ""}, + { "MULTIPOINTFROMWKB", 0, 0, 0, ""}, + { "MULTIPOLYGONFROMTEXT", 0, 0, 0, ""}, + { "MULTIPOLYGONFROMWKB", 0, 0, 0, ""}, + { "NAME_CONST", 0, 0, 0, ""}, + { "NOW", 0, 0, 0, ""}, + { "NULLIF", 0, 0, 0, ""}, + { "NUMGEOMETRIES", 0, 0, 0, ""}, + { "NUMINTERIORRINGS", 0, 0, 0, ""}, + { "NUMPOINTS", 0, 0, 0, ""}, + { "OCTET_LENGTH", 0, 0, 0, ""}, + { "OCT", 0, 0, 0, ""}, + { "ORD", 0, 0, 0, ""}, + { "OVERLAPS", 0, 0, 0, ""}, + { "PERIOD_ADD", 0, 0, 0, ""}, + { "PERIOD_DIFF", 0, 0, 0, ""}, + { "PI", 0, 0, 0, ""}, + { "POINTFROMTEXT", 0, 0, 0, ""}, + { "POINTFROMWKB", 0, 0, 0, ""}, + { "POINTN", 0, 0, 0, ""}, + { "POLYFROMTEXT", 0, 0, 0, ""}, + { "POLYFROMWKB", 0, 0, 0, ""}, + { "POLYGONFROMTEXT", 0, 0, 0, ""}, + { "POLYGONFROMWKB", 0, 0, 0, ""}, + { "POSITION", 0, 0, 0, ""}, + { "POW", 0, 0, 0, ""}, + { "POWER", 0, 0, 0, ""}, + { "QUOTE", 0, 0, 0, ""}, + { "RADIANS", 0, 0, 0, ""}, + { "RAND", 0, 0, 0, ""}, + { "RELEASE_LOCK", 0, 0, 0, ""}, + { "REVERSE", 0, 0, 0, ""}, + { "ROUND", 0, 0, 0, ""}, + { "ROW_COUNT", 0, 0, 0, ""}, + { "RPAD", 0, 0, 0, ""}, + { "RTRIM", 0, 0, 0, ""}, + { "SEC_TO_TIME", 0, 0, 0, ""}, + { "SESSION_USER", 0, 0, 0, ""}, + { "SUBDATE", 0, 0, 0, ""}, + { "SIGN", 0, 0, 0, ""}, + { "SIN", 0, 0, 0, ""}, + { "SHA", 0, 0, 0, ""}, + { "SHA1", 0, 0, 0, ""}, + { "SLEEP", 0, 0, 0, ""}, + { "SOUNDEX", 0, 0, 0, ""}, + { "SPACE", 0, 0, 0, ""}, + { "SQRT", 0, 0, 0, ""}, + { "SRID", 0, 0, 0, ""}, + { "STARTPOINT", 0, 0, 0, ""}, + { "STD", 0, 0, 0, ""}, + { "STDDEV", 0, 0, 0, ""}, + { "STDDEV_POP", 0, 0, 0, ""}, + { "STDDEV_SAMP", 0, 0, 0, ""}, + { "STR_TO_DATE", 0, 0, 0, ""}, + { "STRCMP", 0, 0, 0, ""}, + { "SUBSTR", 0, 0, 0, ""}, + { "SUBSTRING", 0, 0, 0, ""}, + { "SUBSTRING_INDEX", 0, 0, 0, ""}, + { "SUBTIME", 0, 0, 0, ""}, + { "SUM", 0, 0, 0, ""}, + { "SYSDATE", 0, 0, 0, ""}, + { "SYSTEM_USER", 0, 0, 0, ""}, + { "TAN", 0, 0, 0, ""}, + { "TIME_FORMAT", 0, 0, 0, ""}, + { "TIME_TO_SEC", 0, 0, 0, ""}, + { "TIMEDIFF", 0, 0, 0, ""}, + { "TO_DAYS", 0, 0, 0, ""}, + { "TOUCHES", 0, 0, 0, ""}, + { "TRIM", 0, 0, 0, ""}, + { "UCASE", 0, 0, 0, ""}, + { "UNCOMPRESS", 0, 0, 0, ""}, + { "UNCOMPRESSED_LENGTH", 0, 0, 0, ""}, + { "UNHEX", 0, 0, 0, ""}, + { "UNIQUE_USERS", 0, 0, 0, ""}, + { "UNIX_TIMESTAMP", 0, 0, 0, ""}, + { "UPPER", 0, 0, 0, ""}, + { "UUID", 0, 0, 0, ""}, + { "VARIANCE", 0, 0, 0, ""}, + { "VAR_POP", 0, 0, 0, ""}, + { "VAR_SAMP", 0, 0, 0, ""}, + { "VERSION", 0, 0, 0, ""}, + { "WEEKDAY", 0, 0, 0, ""}, + { "WEEKOFYEAR", 0, 0, 0, ""}, + { "WITHIN", 0, 0, 0, ""}, + { "X", 0, 0, 0, ""}, + { "Y", 0, 0, 0, ""}, + { "YEARWEEK", 0, 0, 0, ""}, + /* end sentinel */ + { (char *)NULL, 0, 0, 0, ""} +}; + +static const char *load_default_groups[]= +{ "mysql", "mariadb-client", "client", "client-server", "client-mariadb", 0 }; + +static int embedded_server_arg_count= 0; +static char *embedded_server_args[MAX_SERVER_ARGS]; +static const char *embedded_server_groups[]= +{ "server", "embedded", "mysql_SERVER", "mariadb_SERVER", 0 }; + +#ifdef HAVE_READLINE +static int not_in_history(const char *line); +static void initialize_readline (); +static void fix_history(String *final_command); +#endif + +static COMMANDS *find_command(char *name); +static COMMANDS *find_command(char cmd_name); +static bool add_line(String &, char *, size_t line_length, char *, bool *, bool); +static void remove_cntrl(String &buffer); +static void print_table_data(MYSQL_RES *result); +static void print_table_data_html(MYSQL_RES *result); +static void print_table_data_xml(MYSQL_RES *result); +static void print_tab_data(MYSQL_RES *result); +static void print_table_data_vertically(MYSQL_RES *result); +static void print_warnings(void); +static void end_timer(ulonglong start_time, char *buff); +static void nice_time(double sec,char *buff,bool part_second); +extern "C" sig_handler mysql_end(int sig) __attribute__ ((noreturn)); +extern "C" sig_handler handle_sigint(int sig); +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) +static sig_handler window_resize(int sig); +#endif + + +const char DELIMITER_NAME[]= "delimiter"; +const uint DELIMITER_NAME_LEN= sizeof(DELIMITER_NAME) - 1; +inline bool is_delimiter_command(char *name, ulong len) +{ + /* + Delimiter command has a parameter, so the length of the whole command + is larger than DELIMITER_NAME_LEN. We don't care the parameter, so + only name(first DELIMITER_NAME_LEN bytes) is checked. + */ + return (len >= DELIMITER_NAME_LEN && + !my_charset_latin1.strnncoll(name, DELIMITER_NAME_LEN, + DELIMITER_NAME, DELIMITER_NAME_LEN)); +} + +/** + Get the index of a command in the commands array. + + @param cmd_char Short form command. + + @return int + The index of the command is returned if it is found, else -1 is returned. +*/ +inline int get_command_index(char cmd_char) +{ + /* + All client-specific commands are in the first part of commands array + and have a function to implement it. + */ + for (uint i= 0; commands[i].func; i++) + if (commands[i].cmd_char == cmd_char) + return i; + return -1; +} + +static int delimiter_index= -1; +static int charset_index= -1; +static bool real_binary_mode= FALSE; + + +int main(int argc,char *argv[]) +{ + char buff[80]; + + MY_INIT(argv[0]); + DBUG_ENTER("main"); + DBUG_PROCESS(argv[0]); + + charset_index= get_command_index('C'); + delimiter_index= get_command_index('d'); + delimiter_str= delimiter; + default_prompt = my_strdup(PSI_NOT_INSTRUMENTED, getenv("MYSQL_PS1") ? + getenv("MYSQL_PS1") : + "\\N [\\d]> ",MYF(MY_WME)); + current_prompt = my_strdup(PSI_NOT_INSTRUMENTED, default_prompt,MYF(MY_WME)); + prompt_counter=0; + aborted= 0; + sf_leaking_memory= 1; /* no memory leak reports yet */ + + outfile[0]=0; // no (default) outfile + strmov(pager, "stdout"); // the default, if --pager wasn't given + + { + char *tmp=getenv("PAGER"); + if (tmp && strlen(tmp)) + { + default_pager_set= 1; + strmov(default_pager, tmp); + } + } + if (!isatty(0) || !isatty(1)) + { + status.batch=1; opt_silent=1; + ignore_errors=0; + } + else + status.add_to_history=1; + status.exit_status=1; + + { + /* + The file descriptor-layer may be out-of-sync with the file-number layer, + so we make sure that "stdout" is really open. If its file is closed then + explicitly close the FD layer. + */ + int stdout_fileno_copy; + stdout_fileno_copy= dup(fileno(stdout)); /* Okay if fileno fails. */ + if (stdout_fileno_copy == -1) + fclose(stdout); + else + close(stdout_fileno_copy); /* Clean up dup(). */ + } + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv=argv; + if ((status.exit_status= get_options(argc, (char **) argv))) + { + free_defaults(defaults_argv); + my_end(0); + exit(status.exit_status); + } + + if (status.batch && !status.line_buff && + !(status.line_buff= batch_readline_init(MAX_BATCH_BUFFER_SIZE, stdin))) + { + put_info("Can't initialize batch_readline - may be the input source is " + "a directory or a block device.", INFO_ERROR, 0); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + if (mysql_server_init(embedded_server_arg_count, embedded_server_args, + (char**) embedded_server_groups)) + { + put_error(NULL); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + sf_leaking_memory= 0; + glob_buffer.realloc(512); + completion_hash_init(&ht, 128); + init_alloc_root(PSI_NOT_INSTRUMENTED, &hash_mem_root, 16384, 0, MYF(0)); + if (sql_connect(current_host,current_db,current_user,opt_password, + opt_silent)) + { + quick= 1; // Avoid history + status.exit_status= 1; + mysql_end(-1); + } + if (!status.batch) + ignore_errors=1; // Don't abort monitor + + if (opt_sigint_ignore) + signal(SIGINT, SIG_IGN); + else + signal(SIGINT, handle_sigint); // Catch SIGINT to clean up + signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up + +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) + /* Readline will call this if it installs a handler */ + signal(SIGWINCH, window_resize); + /* call the SIGWINCH handler to get the default term width */ + window_resize(0); +#endif + + if (!status.batch) + { + put_info("Welcome to the MariaDB monitor. Commands end with ; or \\g.", + INFO_INFO); + my_snprintf((char*) glob_buffer.ptr(), glob_buffer.alloced_length(), + "Your %s connection id is %lu\nServer version: %s\n", + mysql_get_server_name(&mysql), + mysql_thread_id(&mysql), server_version_string(&mysql)); + put_info((char*) glob_buffer.ptr(),INFO_INFO); + put_info(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"), INFO_INFO); + } + +#ifdef HAVE_READLINE + initialize_readline(); + if (!status.batch && !quick && !opt_html && !opt_xml) + { + /* read-history from file, default ~/.mysql_history*/ + if (getenv("MYSQL_HISTFILE")) + histfile=my_strdup(PSI_NOT_INSTRUMENTED, getenv("MYSQL_HISTFILE"),MYF(MY_WME)); + else if (getenv("HOME")) + { + histfile=(char*) my_malloc(PSI_NOT_INSTRUMENTED, + strlen(getenv("HOME")) + strlen("/.mysql_history")+2, MYF(MY_WME)); + if (histfile) + sprintf(histfile,"%s/.mysql_history",getenv("HOME")); + char link_name[FN_REFLEN]; + if (my_readlink(link_name, histfile, 0) == 0 && + strncmp(link_name, "/dev/null", 10) == 0) + { + /* The .mysql_history file is a symlink to /dev/null, don't use it */ + my_free(histfile); + histfile= 0; + } + } + + /* We used to suggest setting MYSQL_HISTFILE=/dev/null. */ + if (histfile && strncmp(histfile, "/dev/null", 10) == 0) + histfile= NULL; + + if (histfile && histfile[0]) + { + if (verbose) + tee_fprintf(stdout, "Reading history-file %s\n",histfile); + read_history(histfile); + if (!(histfile_tmp= (char*) my_malloc(PSI_NOT_INSTRUMENTED, + strlen(histfile) + 5, MYF(MY_WME)))) + { + fprintf(stderr, "Couldn't allocate memory for temp histfile!\n"); + exit(1); + } + sprintf(histfile_tmp, "%s.TMP", histfile); + } + } + +#endif + + sprintf(buff, "%s", + "Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.\n"); + put_info(buff,INFO_INFO); + status.exit_status= read_and_execute(!status.batch); + if (opt_outfile) + end_tee(); + mysql_end(0); +#ifndef _lint + DBUG_RETURN(0); // Keep compiler happy +#endif +} + +sig_handler mysql_end(int sig) +{ +#ifndef _WIN32 + /* + Ignoring SIGQUIT and SIGINT signals when cleanup process starts. + This will help in resolving the double free issues, which occurs in case + the signal handler function is started in between the clean up function. + */ + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, SIG_IGN); +#endif + + mysql_close(&mysql); +#ifdef HAVE_READLINE + if (!status.batch && !quick && !opt_html && !opt_xml && + histfile && histfile[0]) + { + /* write-history */ + if (verbose) + tee_fprintf(stdout, "Writing history-file %s\n",histfile); + if (!write_history(histfile_tmp)) + my_rename(histfile_tmp, histfile, MYF(MY_WME)); + } + batch_readline_end(status.line_buff); + completion_hash_free(&ht); + free_root(&hash_mem_root,MYF(0)); + +#endif + if (sig >= 0) + put_info(sig ? "Aborted" : "Bye", INFO_RESULT); + glob_buffer.free(); + old_buffer.free(); + processed_prompt.free(); + my_free(server_version); + my_free(opt_password); + my_free(opt_mysql_unix_port); + my_free(histfile); + my_free(histfile_tmp); + my_free(current_db); + my_free(current_host); + my_free(current_user); + my_free(full_username); + my_free(part_username); + my_free(default_prompt); + my_free(current_prompt); + while (embedded_server_arg_count > 1) + my_free(embedded_server_args[--embedded_server_arg_count]); + mysql_server_end(); + free_defaults(defaults_argv); + my_end(my_end_arg); + exit(status.exit_status); +} + +#ifdef _WIN32 +#define CNV_BUFSIZE 1024 + +/** + Convert user,database,and password to requested charset. + + This is done in the single case when user connects with non-UTF8 + default-character-set, on UTF8 capable Windows. + + User, password, and database are UTF8 encoded, prior to the function, + this needs to be fixed, in case they contain non-ASCIIs. + + Mostly a workaround, to allow existng users with non-ASCII password + to survive upgrade without losing connectivity. +*/ +static void maybe_convert_charset(const char **user, const char **password, + const char **database, const char *csname) +{ + if (GetACP() != CP_UTF8 || !strncmp(csname, "utf8", 4)) + return; + static char bufs[3][CNV_BUFSIZE]; + const char **from[]= {user, password, database}; + CHARSET_INFO *cs= get_charset_by_csname(csname, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME)); + if (!cs) + return; + for (int i= 0; i < 3; i++) + { + const char *str= *from[i]; + if (!str) + continue; + uint errors; + uint len= my_convert(bufs[i], CNV_BUFSIZE, cs, str, (uint32) strlen(str), + &my_charset_utf8mb4_bin, &errors); + bufs[i][len]= 0; + *from[i]= bufs[i]; + } +} +#endif + +/* + set connection-specific options and call mysql_real_connect +*/ +static bool do_connect(MYSQL *mysql, const char *host, const char *user, + const char *password, const char *database, ulong flags) +{ + if (opt_secure_auth) + mysql_options(mysql, MYSQL_SECURE_AUTH, (char *) &opt_secure_auth); +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (opt_use_ssl && opt_protocol <= MYSQL_PROTOCOL_SOCKET) + { + mysql_ssl_set(mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysql"); +#ifdef _WIN32 + maybe_convert_charset(&user, &password, &database,default_charset); +#endif + + return mysql_real_connect(mysql, host, user, password, database, + opt_mysql_port, opt_mysql_unix_port, flags); +} + + +/* + This function handles sigint calls + If query is in process, kill query + If 'source' is executed, abort source command + no query in process, terminate like previous behavior + */ + +sig_handler handle_sigint(int sig) +{ + char kill_buffer[40]; + MYSQL *kill_mysql= NULL; + + /* terminate if no query being executed, or we already tried interrupting */ + if (!executing_query || (interrupted_query == 2)) + { + tee_fprintf(stdout, "Ctrl-C -- exit!\n"); + goto err; + } + + kill_mysql= mysql_init(kill_mysql); + if (!do_connect(kill_mysql,current_host, current_user, opt_password, "", 0)) + { + tee_fprintf(stdout, "Ctrl-C -- sorry, cannot connect to server to kill query, giving up ...\n"); + goto err; + } + + /* First time try to kill the query, second time the connection */ + interrupted_query++; + + /* mysqld < 5 does not understand KILL QUERY, skip to KILL CONNECTION */ + if ((interrupted_query == 1) && (mysql_get_server_version(&mysql) < 50000)) + interrupted_query= 2; + + /* kill_buffer is always big enough because max length of %lu is 15 */ + sprintf(kill_buffer, "KILL %s%lu", + (interrupted_query == 1) ? "QUERY " : "", + mysql_thread_id(&mysql)); + if (verbose) + tee_fprintf(stdout, "Ctrl-C -- sending \"%s\" to server ...\n", + kill_buffer); + mysql_real_query(kill_mysql, kill_buffer, (uint) strlen(kill_buffer)); + mysql_close(kill_mysql); + tee_fprintf(stdout, "Ctrl-C -- query killed. Continuing normally.\n"); + if (in_com_source) + aborted= 1; // Abort source command + return; + +err: +#ifdef _WIN32 + /* + When SIGINT is raised on Windows, the OS creates a new thread to handle the + interrupt. Once that thread completes, the main thread continues running + only to find that it's resources have already been free'd when the sigint + handler called mysql_end(). + */ + mysql_thread_end(); +#else + mysql_end(sig); +#endif +} + + +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) +sig_handler window_resize(int sig) +{ + struct winsize window_size; + + if (ioctl(fileno(stdin), TIOCGWINSZ, &window_size) == 0) + if (window_size.ws_col > 0) + terminal_width= window_size.ws_col; +} +#endif + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"help", 'I', "Synonym for -?", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"abort-source-on-error", OPT_ABORT_SOURCE_ON_ERROR, + "Abort 'source filename' operations in case of errors", + &batch_abort_on_error, &batch_abort_on_error, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-rehash", OPT_AUTO_REHASH, + "Enable automatic rehashing. One doesn't need to use 'rehash' to get table " + "and field completion, but startup and reconnecting may take a longer time. " + "Disable with --disable-auto-rehash.", + &opt_rehash, &opt_rehash, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, + 0, 0}, + {"no-auto-rehash", 'A', + "No automatic rehashing. One has to use 'rehash' to get table and field " + "completion. This gives a quicker start of mysql and disables rehashing " + "on reconnect.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-vertical-output", OPT_AUTO_VERTICAL_OUTPUT, + "Automatically switch to vertical output mode if the result is wider " + "than the terminal width.", + &auto_vertical_output, &auto_vertical_output, 0, GET_BOOL, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"batch", 'B', + "Don't use history file. Disable interactive behavior. (Enables --silent.)", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"binary-as-hex", 0, "Print binary data as hex", &opt_binhex, &opt_binhex, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", &charsets_dir, + &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"column-type-info", OPT_COLUMN_TYPES, "Display column type information.", + &column_types_flag, &column_types_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"comments", 'c', "Preserve comments. Send comments to the server." + " The default is --skip-comments (discard comments), enable with --comments.", + &preserve_comments, &preserve_comments, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log.", &default_dbug_option, + &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag, + &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'D', "Database to use.", ¤t_db, + ¤t_db, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"delimiter", OPT_DELIMITER, "Delimiter to be used.", &delimiter_str, + &delimiter_str, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"execute", 'e', "Execute command and quit. (Disables --force and history file.)", 0, + 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"enable-cleartext-plugin", OPT_COMPATIBILTY_CLEARTEXT_PLUGIN, "Obsolete option. Exists only for MySQL compatibility.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"vertical", 'E', "Print the output of a query (rows) vertically.", + &vertical, &vertical, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"force", 'f', "Continue even if we get an SQL error. Sets abort-source-on-error to 0", + &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"named-commands", 'G', + "Enable named commands. Named commands mean this program's internal " + "commands; see mysql> help . When enabled, the named commands can be " + "used from any line of the query, otherwise only from the first line, " + "before an enter. Disable with --disable-named-commands. This option " + "is disabled by default.", + &named_cmds, &named_cmds, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"ignore-spaces", 'i', "Ignore space after function names.", + &ignore_spaces, &ignore_spaces, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"init-command", OPT_INIT_COMMAND, + "SQL Command to execute when connecting to MariaDB server. Will " + "automatically be re-executed when reconnecting.", + &opt_init_command, &opt_init_command, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"local-infile", OPT_LOCAL_INFILE, "Enable/disable LOAD DATA LOCAL INFILE.", + &opt_local_infile, &opt_local_infile, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"no-beep", 'b', "Turn off beep on error.", &opt_nobeep, + &opt_nobeep, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", ¤t_host, + ¤t_host, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"html", 'H', "Produce HTML output.", &opt_html, &opt_html, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"xml", 'X', "Produce XML output.", &opt_xml, &opt_xml, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"line-numbers", OPT_LINE_NUMBERS, "Write line numbers for errors.", + &line_numbers, &line_numbers, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"skip-line-numbers", 'L', "Don't write line number for errors.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"unbuffered", 'n', "Flush buffer after each query.", &unbuffered, + &unbuffered, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"column-names", OPT_COLUMN_NAMES, "Write column names in results.", + &column_names, &column_names, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"skip-column-names", 'N', + "Don't write column names in results.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"sigint-ignore", OPT_SIGINT_IGNORE, "Ignore SIGINT (CTRL-C).", + &opt_sigint_ignore, &opt_sigint_ignore, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"one-database", 'o', + "Ignore statements except those that occur while the default " + "database is the one named at the command line.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef USE_POPEN + {"pager", OPT_PAGER, + "Pager to use to display results. If you don't supply an option, the " + "default pager is taken from your ENV variable PAGER. Valid pagers are " + "less, more, cat [> filename], etc. See interactive help (\\h) also. " + "This option does not work in batch mode. Disable with --disable-pager. " + "This option is disabled by default.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"password", 'p', + "Password to use when connecting to server. If password is not given it's asked from the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"port", 'P', "Port number to use for connection or 0 for default to, in " + "order of preference, my.cnf, $MYSQL_TCP_PORT, " +#if MYSQL_PORT_DEFAULT == 0 + "/etc/services, " +#endif + "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", + &opt_mysql_port, + &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"progress-reports", OPT_REPORT_PROGRESS, + "Get progress reports for long running commands (like ALTER TABLE)", + &opt_progress_reports, &opt_progress_reports, 0, GET_BOOL, NO_ARG, 1, 0, + 0, 0, 0, 0}, + {"prompt", OPT_PROMPT, "Set the command line prompt to this value.", + ¤t_prompt, ¤t_prompt, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe).", + &opt_protocol_type, &opt_protocol_type, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"quick", 'q', + "Don't cache result, print it row by row. This may slow down the server " + "if the output is suspended. Doesn't use history file.", + &quick, &quick, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"raw", 'r', "Write fields without conversion. Used with --batch.", + &opt_raw_data, &opt_raw_data, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"reconnect", OPT_RECONNECT, "Reconnect if the connection is lost. Disable " + "with --disable-reconnect. This option is enabled by default.", + &opt_reconnect, &opt_reconnect, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"silent", 's', "Be more silent. Print results with a tab as separator, " + "each row on new line.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include "sslopt-longopts.h" + {"table", 't', "Output in table format.", &output_tables, + &output_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"tee", OPT_TEE, + "Append everything into outfile. See interactive help (\\h) also. " + "Does not work in batch mode. Disable with --disable-tee. " + "This option is disabled by default.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", ¤t_user, + ¤t_user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"safe-updates", 'U', "Only allow UPDATE and DELETE that uses keys.", + &safe_updates, &safe_updates, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"i-am-a-dummy", 'U', "Synonym for option --safe-updates, -U.", + &safe_updates, &safe_updates, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"verbose", 'v', "Write more. (-v -v -v gives the table output format).", 0, + 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"wait", 'w', "Wait and retry if connection is down.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"connect_timeout", OPT_CONNECT_TIMEOUT, + "Number of seconds before connection timeout.", + &opt_connect_timeout, &opt_connect_timeout, 0, GET_ULONG, REQUIRED_ARG, + 0, 0, 3600*12, 0, 0, 0}, + {"max_allowed_packet", OPT_MAX_ALLOWED_PACKET, + "The maximum packet length to send to or receive from server.", + &opt_max_allowed_packet, &opt_max_allowed_packet, 0, + GET_ULONG, REQUIRED_ARG, 16 *1024L*1024L, 4096, + (longlong) 2*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0}, + {"net_buffer_length", OPT_NET_BUFFER_LENGTH, + "The buffer size for TCP/IP and socket communication.", + &opt_net_buffer_length, &opt_net_buffer_length, 0, GET_ULONG, + REQUIRED_ARG, 16384, 1024, 512*1024*1024L, MALLOC_OVERHEAD, 1024, 0}, + {"select_limit", OPT_SELECT_LIMIT, + "Automatic limit for SELECT when using --safe-updates.", + &select_limit, &select_limit, 0, GET_ULONG, REQUIRED_ARG, 1000L, + 1, ULONG_MAX, 0, 1, 0}, + {"max_join_size", OPT_MAX_JOIN_SIZE, + "Automatic limit for rows in a join when using --safe-updates.", + &max_join_size, &max_join_size, 0, GET_ULONG, REQUIRED_ARG, 1000000L, + 1, ULONG_MAX, 0, 1, 0}, + {"secure-auth", OPT_SECURE_AUTH, "Refuse client connecting to server if it" + " uses old (pre-4.1.1) protocol.", &opt_secure_auth, + &opt_secure_auth, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"server-arg", OPT_SERVER_ARG, "Send embedded server this as a parameter.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.", + &show_warnings, &show_warnings, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"binary-mode", 0, + "Binary mode allows certain character sequences to be processed as data " + "that would otherwise be treated with a special meaning by the parser. " + "Specifically, this switch turns off parsing of all client commands except " + "\\C and DELIMITER in non-interactive mode (i.e., when binary mode is " + "combined with either 1) piped input, 2) the --batch mysql option, or 3) " + "the 'source' command). Also, in binary mode, occurrences of '\\r\\n' and " + "ASCII '\\0' are preserved within strings, whereas by default, '\\r\\n' is " + "translated to '\\n' and '\\0' is disallowed in user input.", + &opt_binary_mode, &opt_binary_mode, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"connect-expired-password", 0, + "Notify the server that this client is prepared to handle expired " + "password sandbox mode even if --batch was specified.", + &opt_connect_expired_password, &opt_connect_expired_password, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void usage(int version) +{ +#ifdef HAVE_READLINE +#if defined(USE_LIBEDIT_INTERFACE) + const char* readline= ""; +#else + const char* readline= "readline"; +#endif + printf("%s Ver %s Distrib %s, for %s (%s) using %s %s\n", + my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE, + readline, rl_library_version); +#else + printf("%s Ver %s Distrib %s, for %s (%s), source revision %s\n", my_progname, VER, + MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE,SOURCE_REVISION); +#endif + + if (version) + return; + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("Usage: %s [OPTIONS] [database]\n", my_progname); + print_defaults("my", load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + switch(opt->id) { + case OPT_CHARSETS_DIR: + strmake_buf(mysql_charsets_dir, argument); + charsets_dir = mysql_charsets_dir; + break; + case OPT_DELIMITER: + if (argument == disabled_my_option) + { + strmov(delimiter, DEFAULT_DELIMITER); + } + else + { + /* Check that delimiter does not contain a backslash */ + if (!strstr(argument, "\\")) + { + strmake_buf(delimiter, argument); + } + else + { + put_info("DELIMITER cannot contain a backslash character", INFO_ERROR); + return 0; + } + } + delimiter_length= (uint)strlen(delimiter); + delimiter_str= delimiter; + break; + case OPT_LOCAL_INFILE: + using_opt_local_infile=1; + break; + case OPT_TEE: + if (argument == disabled_my_option) + { + if (opt_outfile) + end_tee(); + } + else + init_tee(argument); + break; + case OPT_PAGER: + if (argument == disabled_my_option) + opt_nopager= 1; + else + { + opt_nopager= 0; + if (argument && strlen(argument)) + { + default_pager_set= 1; + strmake_buf(pager, argument); + strmov(default_pager, pager); + } + else if (default_pager_set) + strmov(pager, default_pager); + else + opt_nopager= 1; + } + break; + case OPT_MYSQL_PROTOCOL: +#ifndef EMBEDDED_LIBRARY + if (!argument[0]) + opt_protocol= 0; + else if ((opt_protocol= + find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + exit(1); +#endif + break; + case OPT_SERVER_ARG: +#ifdef EMBEDDED_LIBRARY + /* + When the embedded server is being tested, the client needs to be + able to pass command-line arguments to the embedded server so it can + locate the language files and data directory. + */ + if (!embedded_server_arg_count) + { + embedded_server_arg_count= 1; + embedded_server_args[0]= (char*) ""; + } + if (embedded_server_arg_count == MAX_SERVER_ARGS-1 || + !(embedded_server_args[embedded_server_arg_count++]= + my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)))) + { + put_info("Can't use server argument", INFO_ERROR); + return 0; + } +#else /*EMBEDDED_LIBRARY */ + printf("WARNING: --server-arg option not supported in this configuration.\n"); +#endif + break; + case OPT_COMPATIBILTY_CLEARTEXT_PLUGIN: + /* + This option exists in MySQL client but not in MariaDB. Users switching from + MySQL might still have this option in their commands, and it will not work + in MariaDB unless it is handled. Therefore output a warning and continue. + */ + printf("WARNING: option '--enable-cleartext-plugin' is obsolete.\n"); + break; + case 'A': + opt_rehash= 0; + break; + case 'N': + column_names= 0; + break; + case 'e': + status.batch= 1; + status.add_to_history= 0; + if (!status.line_buff) + ignore_errors= 0; // do it for the first -e only + if (!(status.line_buff= batch_readline_command(status.line_buff, + (char*) argument))) + return 1; + break; + case 'o': + if (argument == disabled_my_option) + one_database= 0; + else + one_database= skip_updates= 1; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; // Don't require password + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*)argument++= 'x'; // Destroy argument + if (*start) + start[1]=0 ; + tty_password= 0; + } + else + tty_password= 1; + break; + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + debug_info_flag= 1; + break; + case 's': + if (argument == disabled_my_option) + opt_silent= 0; + else + opt_silent++; + break; + case 'v': + if (argument == disabled_my_option) + verbose= 0; + else + verbose++; + break; + case 'B': + status.batch= 1; + status.add_to_history= 0; + set_if_bigger(opt_silent,1); // more silent + break; + case 'W': +#ifdef _WIN32 + opt_protocol = MYSQL_PROTOCOL_PIPE; + opt_protocol_type= "pipe"; +#endif + break; +#include <sslopt-case.h> + case 'f': + batch_abort_on_error= 0; + break; + case 'V': + usage(1); + status.exit_status= 0; + mysql_end(-1); + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + case 'I': + case '?': + usage(0); + status.exit_status= 0; + mysql_end(-1); + } + return 0; +} + + +static int get_options(int argc, char **argv) +{ + char *tmp, *pagpoint; + int ho_error; + MYSQL_PARAMETERS *mysql_params= mysql_get_parameters(); + + tmp= (char *) getenv("MYSQL_HOST"); + if (tmp) + current_host= my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME)); + + pagpoint= getenv("PAGER"); + if (!((char*) (pagpoint))) + { + strmov(pager, "stdout"); + opt_nopager= 1; + } + else + strmov(pager, pagpoint); + strmov(default_pager, pager); + + opt_max_allowed_packet= *mysql_params->p_max_allowed_packet; + opt_net_buffer_length= *mysql_params->p_net_buffer_length; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + return(ho_error); + + *mysql_params->p_max_allowed_packet= opt_max_allowed_packet; + *mysql_params->p_net_buffer_length= opt_net_buffer_length; + + if (status.batch) /* disable pager and outfile in this case */ + { + strmov(default_pager, "stdout"); + strmov(pager, "stdout"); + opt_nopager= 1; + default_pager_set= 0; + opt_outfile= 0; + opt_reconnect= 0; + connect_flag= 0; /* Not in interactive mode */ + opt_progress_reports= 0; + } + + if (argc > 1) + { + usage(0); + exit(1); + } + if (argc == 1) + { + skip_updates= 0; + my_free(current_db); + current_db= my_strdup(PSI_NOT_INSTRUMENTED, *argv, MYF(MY_WME)); + } + if (tty_password) + opt_password= my_get_tty_password(NullS); + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + if (ignore_spaces) + connect_flag|= CLIENT_IGNORE_SPACE; + + if (opt_progress_reports) + connect_flag|= CLIENT_PROGRESS_OBSOLETE; + + return(0); +} + +static int read_and_execute(bool interactive) +{ + char *line= NULL; + char in_string=0; + ulong line_number=0; + bool ml_comment= 0; + COMMANDS *com; + size_t line_length= 0; + status.exit_status=1; + + real_binary_mode= !interactive && opt_binary_mode; + while (!aborted) + { + if (!interactive) + { + /* + batch_readline can return 0 on EOF or error. + In that case, we need to double check that we have a valid + line before actually setting line_length to read_length. + */ + line= batch_readline(status.line_buff, real_binary_mode); + if (line) + { + line_length= status.line_buff->read_length; + + /* + ASCII 0x00 is not allowed appearing in queries if it is not in binary + mode. + */ + if (!real_binary_mode && strlen(line) != line_length) + { + status.exit_status= 1; + String msg; + msg.append(STRING_WITH_LEN( + "ASCII '\\0' appeared in the statement, but this is not " + "allowed unless option --binary-mode is enabled and mysql is " + "run in non-interactive mode. Set --binary-mode to 1 if ASCII " + "'\\0' is expected. Query: '")); + msg.append(glob_buffer); + msg.append(line, strlen(line)); + msg.append(STRING_WITH_LEN("'.")); + put_info(msg.c_ptr(), INFO_ERROR); + break; + } + + /* + Skip UTF8 Byte Order Marker (BOM) 0xEFBBBF. + Editors like "notepad" put this marker in + the very beginning of a text file when + you save the file using "Unicode UTF-8" format. + */ + if (!line_number && + (uchar) line[0] == 0xEF && + (uchar) line[1] == 0xBB && + (uchar) line[2] == 0xBF) + { + line+= 3; + // decrease the line length accordingly to the 3 bytes chopped + line_length -=3; + } + } + line_number++; + if (!glob_buffer.length()) + status.query_start_line=line_number; + } + else + { + char *prompt= (char*) (ml_comment ? " /*> " : + glob_buffer.is_empty() ? construct_prompt() : + !in_string ? " -> " : + in_string == '\'' ? + " '> " : (in_string == '`' ? + " `> " : + " \"> ")); + if (opt_outfile && glob_buffer.is_empty()) + fflush(OUTFILE); + +#if defined(_WIN32) + tee_fputs(prompt, stdout); + line= win_readline(); +#else + if (opt_outfile) + fputs(prompt, OUTFILE); + /* + free the previous entered line. + Note: my_free() cannot be used here as the memory was allocated under + the readline/libedit library. + */ + if (line) + free(line); + line= readline(prompt); +#endif /* defined(_WIN32) */ + + /* + When Ctrl+d or Ctrl+z is pressed, the line may be NULL on some OS + which may cause coredump. + */ + if (opt_outfile && line) + fprintf(OUTFILE, "%s\n", line); + + line_length= line ? strlen(line) : 0; + } + // End of file or system error + if (!line) + { + if (status.line_buff && status.line_buff->error) + status.exit_status= 1; + else + status.exit_status= 0; + break; + } + + /* + Check if line is a mysql command line + (We want to allow help, print and clear anywhere at line start + */ + if ((named_cmds || glob_buffer.is_empty()) + && !ml_comment && !in_string && (com= find_command(line))) + { + if ((*com->func)(&glob_buffer,line) > 0) + break; + if (glob_buffer.is_empty()) // If buffer was emptied + in_string=0; +#ifdef HAVE_READLINE + if (interactive && status.add_to_history && not_in_history(line)) + add_history(line); +#endif + continue; + } + if (add_line(glob_buffer, line, line_length, &in_string, &ml_comment, + status.line_buff ? status.line_buff->truncated : 0)) + break; + } + /* if in batch mode, send last query even if it doesn't end with \g or go */ + + if (!interactive && !status.exit_status) + { + remove_cntrl(glob_buffer); + if (!glob_buffer.is_empty()) + { + status.exit_status=1; + if (com_go(&glob_buffer,line) <= 0) + status.exit_status=0; + } + } + +#if !defined(_WIN32) + if (interactive) + /* + free the last entered line. + Note: my_free() cannot be used here as the memory was allocated under + the readline/libedit library. + */ + free(line); +#endif + + /* + If the function is called by 'source' command, it will return to interactive + mode, so real_binary_mode should be FALSE. Otherwise, it will exit the + program, it is safe to set real_binary_mode to FALSE. + */ + real_binary_mode= FALSE; + + return status.exit_status; +} + + +/** + It checks if the input is a short form command. It returns the command's + pointer if a command is found, else return NULL. Note that if binary-mode + is set, then only \C is searched for. + + @param cmd_char A character of one byte. + + @return + the command's pointer or NULL. +*/ +static COMMANDS *find_command(char cmd_char) +{ + DBUG_ENTER("find_command"); + DBUG_PRINT("enter", ("cmd_char: %d", cmd_char)); + + int index= -1; + + /* + In binary-mode, we disallow all mysql commands except '\C' + and DELIMITER. + */ + if (real_binary_mode) + { + if (cmd_char == 'C') + index= charset_index; + } + else + index= get_command_index(cmd_char); + + if (index >= 0) + { + DBUG_PRINT("exit",("found command: %s", commands[index].name)); + DBUG_RETURN(&commands[index]); + } + else + DBUG_RETURN((COMMANDS *) 0); +} + +/** + It checks if the input is a long form command. It returns the command's + pointer if a command is found, else return NULL. Note that if binary-mode + is set, then only DELIMITER is searched for. + + @param name A string. + @return + the command's pointer or NULL. +*/ +static COMMANDS *find_command(char *name) +{ + uint len; + char *end; + DBUG_ENTER("find_command"); + + DBUG_ASSERT(name != NULL); + DBUG_PRINT("enter", ("name: '%s'", name)); + + while (my_isspace(charset_info, *name)) + name++; + /* + If there is an \\g in the row or if the row has a delimiter but + this is not a delimiter command, let add_line() take care of + parsing the row and calling find_command(). + */ + if ((!real_binary_mode && strstr(name, "\\g")) || + (strstr(name, delimiter) && + !is_delimiter_command(name, DELIMITER_NAME_LEN))) + DBUG_RETURN((COMMANDS *) 0); + + if ((end=strcont(name, " \t"))) + { + len=(uint) (end - name); + while (my_isspace(charset_info, *end)) + end++; + if (!*end) + end= 0; // no arguments to function + } + else + len= (uint) strlen(name); + + int index= -1; + if (real_binary_mode) + { + if (is_delimiter_command(name, len)) + index= delimiter_index; + } + else + { + /* + All commands are in the first part of commands array and have a function + to implement it. + */ + for (uint i= 0; commands[i].func; i++) + { + if (!my_charset_latin1.strnncoll((uchar*) name, len, + (uchar*) commands[i].name, len) && + (commands[i].name[len] == '\0') && + (!end || (commands[i].takes_params && get_arg(name, CHECK)))) + { + index= i; + break; + } + } + } + + if (index >= 0) + { + DBUG_PRINT("exit", ("found command: %s", commands[index].name)); + DBUG_RETURN(&commands[index]); + } + DBUG_RETURN((COMMANDS *) 0); +} + + +static bool add_line(String &buffer, char *line, size_t line_length, + char *in_string, bool *ml_comment, bool truncated) +{ + uchar inchar; + char buff[80], *pos, *out; + COMMANDS *com; + bool need_space= 0; + bool ss_comment= 0; + DBUG_ENTER("add_line"); + + if (!line[0] && buffer.is_empty()) + DBUG_RETURN(0); +#ifdef HAVE_READLINE + if (status.add_to_history && line[0] && not_in_history(line)) + add_history(line); +#endif + char *end_of_line= line + line_length; + + for (pos= out= line; pos < end_of_line; pos++) + { + inchar= (uchar) *pos; + if (!preserve_comments) + { + // Skip spaces at the beginning of a statement + if (my_isspace(charset_info,inchar) && (out == line) && + buffer.is_empty()) + continue; + } + +#ifdef USE_MB + // Accept multi-byte characters as-is + int length; + if (charset_info->use_mb() && + (length= my_ismbchar(charset_info, pos, end_of_line))) + { + if (!*ml_comment || preserve_comments) + { + while (length--) + *out++ = *pos++; + pos--; + } + else + pos+= length - 1; + continue; + } +#endif + if (!*ml_comment && inchar == '\\' && *in_string != '`' && + !(*in_string == '"' && + (mysql.server_status & SERVER_STATUS_ANSI_QUOTES)) && + !(*in_string && + (mysql.server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES))) + { + // Found possible one character command like \c + + /* + The null-terminating character (ASCII '\0') marks the end of user + input. Then, by default, upon encountering a '\0' while parsing, it + should stop. However, some data naturally contains binary zeros + (e.g., zipped files). Real_binary_mode signals the parser to expect + '\0' within the data and not to end parsing if found. + */ + if (!(inchar = (uchar) *++pos) && (!real_binary_mode || !*in_string)) + break; // readline adds one '\' + if (*in_string || inchar == 'N') // \N is short for NULL + { // Don't allow commands in string + *out++='\\'; + *out++= (char) inchar; + continue; + } + if ((com= find_command((char) inchar))) + { + // Flush previously accepted characters + if (out != line) + { + buffer.append(line, (uint) (out-line)); + out= line; + } + + if ((*com->func)(&buffer,pos-1) > 0) + DBUG_RETURN(1); // Quit + if (com->takes_params) + { + if (ss_comment) + { + /* + If a client-side macro appears inside a server-side comment, + discard all characters in the comment after the macro (that is, + until the end of the comment rather than the next delimiter) + */ + for (pos++; *pos && (*pos != '*' || *(pos + 1) != '/'); pos++) + ; + pos--; + } + else + { + for (pos++ ; + *pos && (*pos != *delimiter || + !is_prefix(pos + 1, delimiter + 1)) ; pos++) + ; // Remove parameters + if (!*pos) + pos--; + else + pos+= delimiter_length - 1; // Point at last delim char + } + } + } + else + { + sprintf(buff,"Unknown command '\\%c'.",inchar); + if (put_info(buff,INFO_ERROR) > 0) + DBUG_RETURN(1); + *out++='\\'; + *out++=(char) inchar; + continue; + } + } + else if (!*ml_comment && !*in_string && is_prefix(pos, delimiter)) + { + // Found a statement. Continue parsing after the delimiter + pos+= delimiter_length; + + if (preserve_comments) + { + while (my_isspace(charset_info, *pos)) + *out++= *pos++; + } + // Flush previously accepted characters + if (out != line) + { + buffer.append(line, (uint32) (out-line)); + out= line; + } + + if (preserve_comments && ((*pos == '#') || + ((*pos == '-') && + (pos[1] == '-') && + my_isspace(charset_info, pos[2])))) + { + // Add trailing single line comments to this statement + size_t length= strlen(pos); + buffer.append(pos, length); + pos+= length; + } + + pos--; + + if ((com= find_command(buffer.c_ptr()))) + { + + if ((*com->func)(&buffer, buffer.c_ptr()) > 0) + DBUG_RETURN(1); // Quit + } + else + { + if (com_go(&buffer, 0) > 0) // < 0 is not fatal + DBUG_RETURN(1); + } + buffer.length(0); + } + else if (!*ml_comment && + (!*in_string && + (inchar == '#' || + (inchar == '-' && pos[1] == '-' && + /* + The third byte is either whitespace or is the end of + the line -- which would occur only because of the + user sending newline -- which is itself whitespace + and should also match. + We also ignore lines starting with '--', even if there + isn't a whitespace after. (This makes it easier to run + mysql-test-run cases through the client) + */ + ((my_isspace(charset_info,pos[2]) || !pos[2]) || + (buffer.is_empty() && out == line)))))) + { + // Flush previously accepted characters + if (out != line) + { + buffer.append(line, (uint32) (out - line)); + out= line; + } + + // comment to end of line + if (preserve_comments) + { + bool started_with_nothing= !buffer.length(); + + buffer.append(pos, strlen(pos)); + + /* + A single-line comment by itself gets sent immediately so that + client commands (delimiter, status, etc) will be interpreted on + the next line. + */ + if (started_with_nothing) + { + if (com_go(&buffer, 0) > 0) // < 0 is not fatal + DBUG_RETURN(1); + buffer.length(0); + } + } + + break; + } + else if (!*in_string && inchar == '/' && *(pos+1) == '*' && + !(*(pos+2) == '!' || (*(pos+2) == 'M' && *(pos+3) == '!'))) + { + if (preserve_comments) + { + *out++= *pos++; // copy '/' + *out++= *pos; // copy '*' + } + else + pos++; + *ml_comment= 1; + if (out != line) + { + buffer.append(line,(uint) (out-line)); + out=line; + } + } + else if (*ml_comment && !ss_comment && inchar == '*' && *(pos + 1) == '/') + { + if (preserve_comments) + { + *out++= *pos++; // copy '*' + *out++= *pos; // copy '/' + } + else + pos++; + *ml_comment= 0; + if (out != line) + { + buffer.append(line, (uint32) (out - line)); + out= line; + } + // Consumed a 2 chars or more, and will add 1 at most, + // so using the 'line' buffer to edit data in place is ok. + need_space= 1; + } + else + { // Add found char to buffer + if (!*in_string && inchar == '/' && *(pos + 1) == '*' && + *(pos + 2) == '!') + ss_comment= 1; + else if (!*in_string && ss_comment && inchar == '*' && *(pos + 1) == '/') + ss_comment= 0; + if (inchar == *in_string) + *in_string= 0; + else if (!*ml_comment && !*in_string && + (inchar == '\'' || inchar == '"' || inchar == '`')) + *in_string= (char) inchar; + if (!*ml_comment || preserve_comments) + { + if (need_space && !my_isspace(charset_info, (char)inchar)) + *out++= ' '; + need_space= 0; + *out++= (char) inchar; + } + } + } + if (out != line || !buffer.is_empty()) + { + uint length=(uint) (out-line); + + if (!truncated && (!is_delimiter_command(line, length) || + (*in_string || *ml_comment))) + { + /* + Don't add a new line in case there's a DELIMITER command to be + added to the glob buffer (e.g. on processing a line like + "<command>;DELIMITER <non-eof>") : similar to how a new line is + not added in the case when the DELIMITER is the first command + entered with an empty glob buffer. However, if the delimiter is + part of a string or a comment, the new line should be added. (e.g. + SELECT '\ndelimiter\n';\n) + */ + *out++='\n'; + length++; + } + if (buffer.length() + length >= buffer.alloced_length()) + buffer.realloc(buffer.length()+length+IO_SIZE); + if ((!*ml_comment || preserve_comments) && buffer.append(line, length)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +/***************************************************************** + Interface to Readline Completion +******************************************************************/ + +#ifdef HAVE_READLINE + +C_MODE_START +static char *new_command_generator(const char *text, int); +static char **new_mysql_completion(const char *text, int start, int end); +C_MODE_END + +/* + Tell the GNU Readline library how to complete. We want to try to complete + on command names if this is the first word in the line, or on filenames + if not. +*/ + +#if defined(USE_NEW_READLINE_INTERFACE) +static int fake_magic_space(int, int); +extern "C" char *no_completion(const char*,int) +#elif defined(USE_LIBEDIT_INTERFACE) +static int fake_magic_space(const char *, int); +extern "C" int no_completion(const char*,int) +#else +extern "C" char *no_completion() +#endif +{ + return 0; /* No filename completion */ +} + +/* glues pieces of history back together if in pieces */ +static void fix_history(String *final_command) +{ + int total_lines = 1; + char *ptr = final_command->c_ptr(); + String fixed_buffer; /* Converted buffer */ + char str_char = '\0'; /* Character if we are in a string or not */ + + /* find out how many lines we have and remove newlines */ + while (*ptr != '\0') + { + switch (*ptr) { + /* string character */ + case '"': + case '\'': + case '`': + if (str_char == '\0') /* open string */ + str_char = *ptr; + else if (str_char == *ptr) /* close string */ + str_char = '\0'; + fixed_buffer.append(ptr,1); + break; + case '\n': + /* + not in string, change to space + if in string, leave it alone + */ + fixed_buffer.append(str_char == '\0' ? ' ' : '\n'); + total_lines++; + break; + case '\\': + fixed_buffer.append('\\'); + /* need to see if the backslash is escaping anything */ + if (str_char) + { + ptr++; + /* special characters that need escaping */ + if (*ptr == '\'' || *ptr == '"' || *ptr == '\\') + fixed_buffer.append(ptr,1); + else + ptr--; + } + break; + + default: + fixed_buffer.append(ptr,1); + } + ptr++; + } + if (total_lines > 1) + add_history(fixed_buffer.ptr()); +} + +/* + returns 0 if line matches the previous history entry + returns 1 if the line doesn't match the previous history entry +*/ +static int not_in_history(const char *line) +{ + HIST_ENTRY *oldhist = history_get(history_length); + + if (oldhist == 0) + return 1; + if (strcmp(oldhist->line,line) == 0) + return 0; + return 1; +} + + +#if defined(USE_NEW_READLINE_INTERFACE) +static int fake_magic_space(int, int) +#else +static int fake_magic_space(const char *, int) +#endif +{ + rl_insert(1, ' '); + return 0; +} + + +static void initialize_readline () +{ + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name= (char *) "mysql"; + rl_terminal_name= getenv("TERM"); +#ifdef HAVE_SETLOCALE + setlocale(LC_ALL,""); +#endif + + /* Tell the completer that we want a crack first. */ +#if defined(USE_NEW_READLINE_INTERFACE) + rl_attempted_completion_function= (rl_completion_func_t*)&new_mysql_completion; + rl_completion_entry_function= (rl_compentry_func_t*)&no_completion; + + rl_add_defun("magic-space", (rl_command_func_t *)&fake_magic_space, -1); +#elif defined(USE_LIBEDIT_INTERFACE) + rl_attempted_completion_function= (CPPFunction*)&new_mysql_completion; + rl_completion_entry_function= &no_completion; + rl_add_defun("magic-space", (Function*)&fake_magic_space, -1); +#else + rl_attempted_completion_function= (CPPFunction*)&new_mysql_completion; + rl_completion_entry_function= &no_completion; +#endif +} + +/* + Attempt to complete on the contents of TEXT. START and END show the + region of TEXT that contains the word to complete. We can use the + entire line in case we want to do some simple parsing. Return the + array of matches, or NULL if there aren't any. +*/ + +static char **new_mysql_completion(const char *text, + int start __attribute__((unused)), + int end __attribute__((unused))) +{ + if (!status.batch && !quick) +#if defined(USE_NEW_READLINE_INTERFACE) + return rl_completion_matches(text, new_command_generator); +#else + return completion_matches((char *)text, (CPFunction *)new_command_generator); +#endif + else + return (char**) 0; +} + +static char *new_command_generator(const char *text,int state) +{ + static int textlen; + char *ptr; + static Bucket *b; + static entry *e; + static uint i; + + if (!state) + textlen=(uint) strlen(text); + + if (textlen>0) + { /* lookup in the hash */ + if (!state) + { + uint len; + + b = find_all_matches(&ht,text,(uint) strlen(text),&len); + if (!b) + return NullS; + e = b->pData; + } + + if (e) + { + ptr= strdup(e->str); + e = e->pNext; + return ptr; + } + } + else + { /* traverse the entire hash, ugly but works */ + + if (!state) + { + /* find the first used bucket */ + for (i=0 ; i < ht.nTableSize ; i++) + { + if (ht.arBuckets[i]) + { + b = ht.arBuckets[i]; + e = b->pData; + break; + } + } + } + ptr= NullS; + while (e && !ptr) + { /* find valid entry in bucket */ + if ((uint) strlen(e->str) == b->nKeyLength) + ptr = strdup(e->str); + /* find the next used entry */ + e = e->pNext; + if (!e) + { /* find the next used bucket */ + b = b->pNext; + if (!b) + { + for (i++ ; i<ht.nTableSize; i++) + { + if (ht.arBuckets[i]) + { + b = ht.arBuckets[i]; + e = b->pData; + break; + } + } + } + else + e = b->pData; + } + } + if (ptr) + return ptr; + } + return NullS; +} + + +/* Build up the completion hash */ + +static void build_completion_hash(bool rehash, bool write_info) +{ + COMMANDS *cmd=commands; + MYSQL_RES *databases=0,*tables=0; + MYSQL_RES *fields; + static char ***field_names= 0; + MYSQL_ROW database_row,table_row; + MYSQL_FIELD *sql_field; + char buf[NAME_LEN*2+2]; // table name plus field name plus 2 + int i,j,num_fields; + DBUG_ENTER("build_completion_hash"); + + if (status.batch || quick || !current_db) + DBUG_VOID_RETURN; // We don't need completion in batches + if (!rehash) + DBUG_VOID_RETURN; + + /* Free old used memory */ + if (field_names) + field_names=0; + completion_hash_clean(&ht); + free_root(&hash_mem_root,MYF(0)); + + /* hash this file's known subset of SQL commands */ + while (cmd->name) { + add_word(&ht,(char*) cmd->name); + cmd++; + } + + /* hash MySQL functions (to be implemented) */ + + /* hash all database names */ + if (mysql_query(&mysql,"show databases") == 0) + { + if (!(databases = mysql_store_result(&mysql))) + put_info(mysql_error(&mysql),INFO_INFO); + else + { + while ((database_row=mysql_fetch_row(databases))) + { + char *str=strdup_root(&hash_mem_root, (char*) database_row[0]); + if (str) + add_word(&ht,(char*) str); + } + mysql_free_result(databases); + } + } + /* hash all table names */ + if (mysql_query(&mysql,"show tables")==0) + { + if (!(tables = mysql_store_result(&mysql))) + put_info(mysql_error(&mysql),INFO_INFO); + else + { + if (mysql_num_rows(tables) > 0 && !opt_silent && write_info) + { + tee_fprintf(stdout, "\ +Reading table information for completion of table and column names\n\ +You can turn off this feature to get a quicker startup with -A\n\n"); + } + while ((table_row=mysql_fetch_row(tables))) + { + char *str=strdup_root(&hash_mem_root, (char*) table_row[0]); + if (str && + !completion_hash_exists(&ht,(char*) str, (uint) strlen(str))) + add_word(&ht,str); + } + } + } + + /* hash all field names, both with the table prefix and without it */ + if (!tables) /* no tables */ + { + DBUG_VOID_RETURN; + } + mysql_data_seek(tables,0); + if (!(field_names= (char ***) alloc_root(&hash_mem_root,sizeof(char **) * + (uint) (mysql_num_rows(tables)+1)))) + { + mysql_free_result(tables); + DBUG_VOID_RETURN; + } + i=0; + while ((table_row=mysql_fetch_row(tables))) + { + if ((fields=mysql_list_fields(&mysql,(const char*) table_row[0],NullS))) + { + num_fields=mysql_num_fields(fields); + if (!(field_names[i] = (char **) alloc_root(&hash_mem_root, + sizeof(char *) * + (num_fields*2+1)))) + { + mysql_free_result(fields); + break; + } + field_names[i][num_fields*2]= NULL; + j=0; + while ((sql_field=mysql_fetch_field(fields))) + { + sprintf(buf,"%.64s.%.64s",table_row[0],sql_field->name); + field_names[i][j] = strdup_root(&hash_mem_root,buf); + add_word(&ht,field_names[i][j]); + field_names[i][num_fields+j] = strdup_root(&hash_mem_root, + sql_field->name); + if (!completion_hash_exists(&ht,field_names[i][num_fields+j], + (uint) strlen(field_names[i][num_fields+j]))) + add_word(&ht,field_names[i][num_fields+j]); + j++; + } + mysql_free_result(fields); + } + else + field_names[i]= 0; + + i++; + } + mysql_free_result(tables); + field_names[i]=0; // End pointer + DBUG_VOID_RETURN; +} + + /* for gnu readline */ + +#ifndef HAVE_INDEX +extern "C" { +extern char *index(const char *,int c),*rindex(const char *,int); + +char *index(const char *s,int c) +{ + for (;;) + { + if (*s == (char) c) return (char*) s; + if (!*s++) return NullS; + } +} + +char *rindex(const char *s,int c) +{ + reg3 char *t; + + t = NullS; + do if (*s == (char) c) t = (char*) s; while (*s++); + return (char*) t; +} +} +#endif +#endif /* HAVE_READLINE */ + + +static int reconnect(void) +{ + /* purecov: begin tested */ + if (opt_reconnect) + { + put_info("No connection. Trying to reconnect...",INFO_INFO); + (void) com_connect((String *) 0, 0); + if (opt_rehash) + com_rehash(NULL, NULL); + } + if (!connected) + return put_info("Can't connect to the server\n",INFO_ERROR); + my_free(server_version); + server_version= 0; + /* purecov: end */ + return 0; +} + +static void get_current_db() +{ + MYSQL_RES *res; + + /* If one_database is set, current_db is not supposed to change. */ + if (one_database) + return; + + my_free(current_db); + current_db= NULL; + /* In case of error below current_db will be NULL */ + if (!mysql_query(&mysql, "SELECT DATABASE()") && + (res= mysql_use_result(&mysql))) + { + MYSQL_ROW row= mysql_fetch_row(res); + if (row && row[0]) + current_db= my_strdup(PSI_NOT_INSTRUMENTED, row[0], MYF(MY_WME)); + mysql_free_result(res); + } +} + +/*************************************************************************** + The different commands +***************************************************************************/ + +int mysql_real_query_for_lazy(const char *buf, size_t length) +{ + for (uint retry=0;; retry++) + { + int error; + if (!mysql_real_query(&mysql,buf,(ulong)length)) + return 0; + error= put_error(&mysql); + if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || + !opt_reconnect) + return error; + if (reconnect()) + return error; + } +} + +int mysql_store_result_for_lazy(MYSQL_RES **result) +{ + if ((*result=mysql_store_result(&mysql))) + return 0; + + if (mysql_error(&mysql)[0]) + return put_error(&mysql); + return 0; +} + +static void print_help_item(MYSQL_ROW *cur, int num_name, int num_cat, char *last_char) +{ + char ccat= (*cur)[num_cat][0]; + if (*last_char != ccat) + { + put_info(ccat == 'Y' ? "categories:" : "topics:", INFO_INFO); + *last_char= ccat; + } + tee_fprintf(PAGER, " %s\n", (*cur)[num_name]); +} + + +static int com_server_help(String *buffer __attribute__((unused)), + char *line __attribute__((unused)), char *help_arg) +{ + MYSQL_ROW cur; + const char *server_cmd; + char cmd_buf[100 + 1]; + MYSQL_RES *result; + int error; + + if (help_arg[0] != '\'') + { + char *end_arg= strend(help_arg); + if(--end_arg) + { + while (my_isspace(charset_info,*end_arg)) + end_arg--; + *++end_arg= '\0'; + } + (void) strxnmov(cmd_buf, sizeof(cmd_buf), "help '", help_arg, "'", NullS); + } + else + (void) strxnmov(cmd_buf, sizeof(cmd_buf), "help ", help_arg, NullS); + + server_cmd= cmd_buf; + + if (!status.batch) + { + old_buffer= *buffer; + old_buffer.copy(); + } + + if (!connected && reconnect()) + return 1; + + if ((error= mysql_real_query_for_lazy(server_cmd,(int)strlen(server_cmd))) || + (error= mysql_store_result_for_lazy(&result))) + return error; + + if (result) + { + unsigned int num_fields= mysql_num_fields(result); + my_ulonglong num_rows= mysql_num_rows(result); + if (num_fields==3 && num_rows==1) + { + if (!(cur= mysql_fetch_row(result))) + { + error= -1; + goto err; + } + + init_pager(); + tee_fprintf(PAGER, "Name: \'%s\'\n", cur[0]); + tee_fprintf(PAGER, "Description:\n%s", cur[1]); + if (cur[2] && *((char*)cur[2])) + tee_fprintf(PAGER, "Examples:\n%s", cur[2]); + tee_fprintf(PAGER, "\n"); + end_pager(); + } + else if (num_fields >= 2 && num_rows) + { + init_pager(); + char last_char= 0; + + int UNINIT_VAR(num_name), UNINIT_VAR(num_cat); + + if (num_fields == 2) + { + put_info("Many help items for your request exist.", INFO_INFO); + put_info("To make a more specific request, please type 'help <item>',\nwhere <item> is one of the following", INFO_INFO); + num_name= 0; + num_cat= 1; + } + else if ((cur= mysql_fetch_row(result))) + { + tee_fprintf(PAGER, "You asked for help about help category: \"%s\"\n", cur[0]); + put_info("For more information, type 'help <item>', where <item> is one of the following", INFO_INFO); + num_name= 1; + num_cat= 2; + print_help_item(&cur,1,2,&last_char); + } + + while ((cur= mysql_fetch_row(result))) + print_help_item(&cur,num_name,num_cat,&last_char); + tee_fprintf(PAGER, "\n"); + end_pager(); + } + else + { + put_info("\nNothing found", INFO_INFO); + if (strncasecmp(server_cmd, "help 'contents'", 15) == 0) + { + put_info("\nPlease check if 'help tables' are loaded.\n", INFO_INFO); + goto err; + } + put_info("Please try to run 'help contents' for a list of all accessible topics\n", INFO_INFO); + } + } + +err: + mysql_free_result(result); + return error; +} + +static int +com_help(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + int i, j; + char * help_arg= strchr(line,' '), buff[32], *end; + if (help_arg) + { + while (my_isspace(charset_info,*help_arg)) + help_arg++; + if (*help_arg) + return com_server_help(buffer,line,help_arg); + } + + put_info("\nGeneral information about MariaDB can be found at\n" + "http://mariadb.org\n", INFO_INFO); + put_info("List of all client commands:", INFO_INFO); + if (!named_cmds) + put_info("Note that all text commands must be first on line and end with ';'",INFO_INFO); + for (i = 0; commands[i].name; i++) + { + end= strmov(buff, commands[i].name); + for (j= (int)strlen(commands[i].name); j < 10; j++) + end= strmov(end, " "); + if (commands[i].func) + tee_fprintf(stdout, "%s(\\%c) %s\n", buff, + commands[i].cmd_char, commands[i].doc); + } + if (connected && mysql_get_server_version(&mysql) >= 40100) + put_info("\nFor server side help, type 'help contents'\n", INFO_INFO); + return 0; +} + + + /* ARGSUSED */ +static int +com_clear(String *buffer,char *line __attribute__((unused))) +{ +#ifdef HAVE_READLINE + if (status.add_to_history) + fix_history(buffer); +#endif + buffer->length(0); + return 0; +} + +static void adjust_console_codepage(const char *name __attribute__((unused))) +{ +#ifdef _WIN32 + if (my_set_console_cp(name) < 0) + { + char buf[128]; + snprintf(buf, sizeof(buf), + "WARNING: Could not determine Windows codepage for charset '%s'," + "continue using codepage %u", name, GetConsoleOutputCP()); + put_info(buf, INFO_INFO); + } +#endif +} + + + /* ARGSUSED */ +static int +com_charset(String *buffer __attribute__((unused)), char *line) +{ + char buff[256], *param; + CHARSET_INFO * new_cs; + strmake_buf(buff, line); + param= get_arg(buff, GET); + if (!param || !*param) + { + return put_info("Usage: \\C charset_name | charset charset_name", + INFO_ERROR, 0); + } + new_cs= get_charset_by_csname(param, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME)); + if (new_cs) + { + charset_info= new_cs; + mysql_set_character_set(&mysql, charset_info->cs_name.str); + default_charset= (char *)charset_info->cs_name.str; + put_info("Charset changed", INFO_INFO); + adjust_console_codepage(charset_info->cs_name.str); + } + else put_info("Charset is not found", INFO_INFO); + return 0; +} + +/* + Execute command + Returns: 0 if ok + -1 if not fatal error + 1 if fatal error +*/ + + +static int +com_go(String *buffer,char *line __attribute__((unused))) +{ + char buff[200]; /* about 110 chars used so far */ + char time_buff[53+3+1]; /* time max + space & parens + NUL */ + MYSQL_RES *result; + ulonglong timer; + ulong warnings= 0; + uint error= 0; + int err= 0; + + interrupted_query= 0; + if (!status.batch) + { + old_buffer= *buffer; // Save for edit command + old_buffer.copy(); + } + + /* Remove garbage for nicer messages */ + LINT_INIT_STRUCT(buff[0]); + remove_cntrl(*buffer); + + if (buffer->is_empty()) + { + if (status.batch) // Ignore empty queries. + return 0; + return put_info("No query specified\n",INFO_ERROR); + + } + if (!connected && reconnect()) + { + buffer->length(0); // Remove query on error + return opt_reconnect ? -1 : 1; // Fatal error + } + if (verbose) + (void) com_print(buffer,0); + + if (skip_updates && + (buffer->length() < 4 || charset_info->strnncoll((const uchar*)buffer->ptr(),4, + (const uchar*)"SET ",4))) + { + (void) put_info("Ignoring query to other database",INFO_INFO); + return 0; + } + + timer= microsecond_interval_timer(); + executing_query= 1; + error= mysql_real_query_for_lazy(buffer->ptr(),buffer->length()); + report_progress_end(); + +#ifdef HAVE_READLINE + if (status.add_to_history) + { + const char *delim= vertical ? "\\G" : delimiter; + buffer->append(delim, strlen(delim)); + /* Append final command onto history */ + fix_history(buffer); + } +#endif + + buffer->length(0); + + if (error) + goto end; + + do + { + char *pos; + + if (quick) + { + if (!(result=mysql_use_result(&mysql)) && mysql_field_count(&mysql)) + { + error= put_error(&mysql); + goto end; + } + } + else + { + error= mysql_store_result_for_lazy(&result); + if (error) + goto end; + } + + if (verbose >= 3 || !opt_silent) + end_timer(timer, time_buff); + else + time_buff[0]= '\0'; + + /* Every branch must truncate buff. */ + if (result) + { + if (!mysql_num_rows(result) && ! quick && !column_types_flag) + { + strmov(buff, "Empty set"); + if (opt_xml) + { + /* + We must print XML header and footer + to produce a well-formed XML even if + the result set is empty (Bug#27608). + */ + init_pager(); + print_table_data_xml(result); + end_pager(); + } + } + else + { + init_pager(); + if (opt_html) + print_table_data_html(result); + else if (opt_xml) + print_table_data_xml(result); + else if (vertical || (auto_vertical_output && + (terminal_width < get_result_width(result)))) + print_table_data_vertically(result); + else if (opt_silent && verbose <= 2 && !output_tables) + print_tab_data(result); + else + print_table_data(result); + sprintf(buff,"%ld %s in set", + (long) mysql_num_rows(result), + (long) mysql_num_rows(result) == 1 ? "row" : "rows"); + end_pager(); + if (mysql_errno(&mysql)) + error= put_error(&mysql); + } + } + else if (mysql_affected_rows(&mysql) == ~(ulonglong) 0) + strmov(buff,"Query OK"); + else + sprintf(buff,"Query OK, %ld %s affected", + (long) mysql_affected_rows(&mysql), + (long) mysql_affected_rows(&mysql) == 1 ? "row" : "rows"); + + pos=strend(buff); + if ((warnings= mysql_warning_count(&mysql))) + { + *pos++= ','; + *pos++= ' '; + pos=int10_to_str(warnings, pos, 10); + pos=strmov(pos, " warning"); + if (warnings != 1) + *pos++= 's'; + } + strmov(pos, time_buff); + put_info(buff,INFO_RESULT); + if (mysql_info(&mysql)) + put_info(mysql_info(&mysql),INFO_RESULT); + put_info("",INFO_RESULT); // Empty row + + if (result && !mysql_eof(result)) /* Something wrong when using quick */ + error= put_error(&mysql); + else if (unbuffered) + fflush(stdout); + mysql_free_result(result); + } while (!(err= mysql_next_result(&mysql))); + if (err >= 1) + error= put_error(&mysql); + +end: + + /* Show warnings if any or error occurred */ + if (show_warnings == 1 && (warnings >= 1 || error)) + print_warnings(); + + if (!error && !status.batch && + (mysql.server_status & SERVER_STATUS_DB_DROPPED)) + get_current_db(); + + executing_query= 0; + return error; /* New command follows */ +} + + +static void init_pager() +{ +#ifdef USE_POPEN + if (!opt_nopager) + { + if (!(PAGER= popen(pager, "w"))) + { + tee_fprintf(stdout, "popen() failed! defaulting PAGER to stdout!\n"); + PAGER= stdout; + } + } + else +#endif + PAGER= stdout; +} + +static void end_pager() +{ +#ifdef USE_POPEN + if (!opt_nopager) + pclose(PAGER); +#endif +} + + +static void init_tee(const char *file_name) +{ + FILE* new_outfile; + if (opt_outfile) + end_tee(); + if (!(new_outfile= my_fopen(file_name, O_APPEND | O_WRONLY, MYF(MY_WME)))) + { + tee_fprintf(stdout, "Error logging to file '%s'\n", file_name); + return; + } + OUTFILE = new_outfile; + strmake_buf(outfile, file_name); + tee_fprintf(stdout, "Logging to file '%s'\n", file_name); + opt_outfile= 1; + return; +} + + +static void end_tee() +{ + my_fclose(OUTFILE, MYF(0)); + OUTFILE= 0; + opt_outfile= 0; + return; +} + + +static int +com_ego(String *buffer,char *line) +{ + int result; + bool oldvertical=vertical; + vertical=1; + result=com_go(buffer,line); + vertical=oldvertical; + return result; +} + + +static const char *fieldtype2str(enum enum_field_types type) +{ + switch (type) { + case MYSQL_TYPE_BIT: return "BIT"; + case MYSQL_TYPE_BLOB: return "BLOB"; + case MYSQL_TYPE_DATE: return "DATE"; + case MYSQL_TYPE_DATETIME: return "DATETIME"; + case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL"; + case MYSQL_TYPE_DECIMAL: return "DECIMAL"; + case MYSQL_TYPE_DOUBLE: return "DOUBLE"; + case MYSQL_TYPE_ENUM: return "ENUM"; + case MYSQL_TYPE_FLOAT: return "FLOAT"; + case MYSQL_TYPE_GEOMETRY: return "GEOMETRY"; + case MYSQL_TYPE_INT24: return "INT24"; + case MYSQL_TYPE_LONG: return "LONG"; + case MYSQL_TYPE_LONGLONG: return "LONGLONG"; + case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB"; + case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB"; + case MYSQL_TYPE_NEWDATE: return "NEWDATE"; + case MYSQL_TYPE_NULL: return "NULL"; + case MYSQL_TYPE_SET: return "SET"; + case MYSQL_TYPE_SHORT: return "SHORT"; + case MYSQL_TYPE_STRING: return "STRING"; + case MYSQL_TYPE_TIME: return "TIME"; + case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP"; + case MYSQL_TYPE_TINY: return "TINY"; + case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB"; + case MYSQL_TYPE_VAR_STRING: return "VAR_STRING"; + case MYSQL_TYPE_YEAR: return "YEAR"; + default: return "?-unknown-?"; + } +} + +static char *fieldflags2str(uint f) { + static char buf[1024]; + char *s=buf; + *s=0; +#define ff2s_check_flag(X) \ + if (f & X ## _FLAG) { s=strmov(s, # X " "); f &= ~ X ## _FLAG; } + ff2s_check_flag(NOT_NULL); + ff2s_check_flag(PRI_KEY); + ff2s_check_flag(UNIQUE_KEY); + ff2s_check_flag(MULTIPLE_KEY); + ff2s_check_flag(BLOB); + ff2s_check_flag(UNSIGNED); + ff2s_check_flag(ZEROFILL); + ff2s_check_flag(BINARY); + ff2s_check_flag(ENUM); + ff2s_check_flag(AUTO_INCREMENT); + ff2s_check_flag(TIMESTAMP); + ff2s_check_flag(SET); + ff2s_check_flag(NO_DEFAULT_VALUE); + ff2s_check_flag(NUM); + ff2s_check_flag(PART_KEY); + ff2s_check_flag(GROUP); + /* + CONTEXT_COLLATION_FLAG (former BINCMP_FLAG) is used at parse + time only and should never show up on the client side. Don't test it. + */ + ff2s_check_flag(ON_UPDATE_NOW); +#undef ff2s_check_flag + if (f) + sprintf(s, " unknows=0x%04x", f); + return buf; +} + +static void +print_field_types(MYSQL_RES *result) +{ + MYSQL_FIELD *field; + uint i=0; + + while ((field = mysql_fetch_field(result))) + { + Client_field_metadata metadata(field); + BinaryStringBuffer<128> data_type_metadata_str; + metadata.print_data_type_related_attributes(&data_type_metadata_str); + tee_fprintf(PAGER, "Field %3u: `%s`\n" + "Org_field: `%s`\n" + "Catalog: `%s`\n" + "Database: `%s`\n" + "Table: `%s`\n" + "Org_table: `%s`\n" + "Type: %s%s%.*s%s\n" + "Collation: %s (%u)\n" + "Length: %lu\n" + "Max_length: %lu\n" + "Decimals: %u\n" + "Flags: %s\n\n", + ++i, + field->name, field->org_name, field->catalog, field->db, + field->table, field->org_table, fieldtype2str(field->type), + data_type_metadata_str.length() ? " (" : "", + data_type_metadata_str.length(), data_type_metadata_str.ptr(), + data_type_metadata_str.length() ? ")" : "", + get_charset_name(field->charsetnr), field->charsetnr, + field->length, field->max_length, field->decimals, + fieldflags2str(field->flags)); + } + tee_puts("", PAGER); +} + + +/* Used to determine if we should invoke print_as_hex for this field */ + +static bool +is_binary_field(MYSQL_FIELD *field) +{ + if ((field->charsetnr == 63) && + (field->type == MYSQL_TYPE_BIT || + field->type == MYSQL_TYPE_BLOB || + field->type == MYSQL_TYPE_LONG_BLOB || + field->type == MYSQL_TYPE_MEDIUM_BLOB || + field->type == MYSQL_TYPE_TINY_BLOB || + field->type == MYSQL_TYPE_VAR_STRING || + field->type == MYSQL_TYPE_STRING || + field->type == MYSQL_TYPE_VARCHAR || + field->type == MYSQL_TYPE_GEOMETRY)) + return 1; + return 0; +} + + +/* Print binary value as hex literal (0x ...) */ + +static void +print_as_hex(FILE *output_file, const char *str, size_t len, size_t total_bytes_to_send) +{ + const char *ptr= str, *end= ptr+len; + size_t i; + fprintf(output_file, "0x"); + for(; ptr < end; ptr++) + fprintf(output_file, "%02X", *((uchar*)ptr)); + for (i= 2*len+2; i < total_bytes_to_send; i++) + tee_putc((int)' ', output_file); +} + + +static void +print_table_data(MYSQL_RES *result) +{ + String separator(256); + MYSQL_ROW cur; + bool *num_flag; + + num_flag=(bool*) my_alloca(sizeof(bool)*mysql_num_fields(result)); + if (column_types_flag) + { + print_field_types(result); + if (!mysql_num_rows(result)) + { + my_afree((uchar*) num_flag); + return; + } + mysql_field_seek(result,0); + } + separator.copy("+",1,charset_info); + while (MYSQL_FIELD *field= mysql_fetch_field(result)) + { + uint length= column_names ? field->name_length : 0; + if (quick) + length= MY_MAX(length,field->length); + else + length= MY_MAX(length,field->max_length); + if (length < 4 && !IS_NOT_NULL(field->flags)) + length=4; // Room for "NULL" + if (opt_binhex && is_binary_field(field)) + length= 2 + length * 2; + field->max_length=length; + num_flag[mysql_field_tell(result) - 1]= IS_NUM(field->type); + separator.fill(separator.length()+length+2,'-'); + separator.append('+'); + } + separator.append('\0'); // End marker for \0 + tee_puts((char*) separator.ptr(), PAGER); + if (column_names) + { + mysql_field_seek(result,0); + (void) tee_fputs("|", PAGER); + while (MYSQL_FIELD *field= mysql_fetch_field(result)) + { + size_t name_length= (uint) strlen(field->name); + size_t numcells= charset_info->numcells(field->name, + field->name + name_length); + size_t display_length= field->max_length + name_length - numcells; + tee_fprintf(PAGER, " %-*s |",(int) MY_MIN(display_length, + MAX_COLUMN_LENGTH), + field->name); + } + (void) tee_fputs("\n", PAGER); + tee_puts((char*) separator.ptr(), PAGER); + } + + while ((cur= mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths= mysql_fetch_lengths(result); + (void) tee_fputs("| ", PAGER); + mysql_field_seek(result, 0); + for (uint off= 0; off < mysql_num_fields(result); off++) + { + const char *buffer; + uint data_length; + uint field_max_length; + uint extra_padding; + + if (off) + (void) tee_fputs(" ", PAGER); + + if (cur[off] == NULL) + { + buffer= "NULL"; + data_length= 4; + } + else + { + buffer= cur[off]; + data_length= (uint) lengths[off]; + } + + MYSQL_FIELD *field= mysql_fetch_field(result); + field_max_length= field->max_length; + + /* + How many text cells on the screen will this string span? If it contains + multibyte characters, then the number of characters we occupy on screen + will be fewer than the number of bytes we occupy in memory. + + We need to find how much screen real-estate we will occupy to know how + many extra padding-characters we should send with the printing function. + */ + size_t visible_length= charset_info->numcells(buffer, buffer + data_length); + extra_padding= (uint) (data_length - visible_length); + + if (opt_binhex && is_binary_field(field)) + print_as_hex(PAGER, cur[off], lengths[off], field_max_length); + else if (field_max_length > MAX_COLUMN_LENGTH) + tee_print_sized_data(buffer, data_length, MAX_COLUMN_LENGTH+extra_padding, FALSE); + else + { + if (num_flag[off] != 0) /* if it is numeric, we right-justify it */ + tee_print_sized_data(buffer, data_length, field_max_length+extra_padding, TRUE); + else + tee_print_sized_data(buffer, data_length, field_max_length+extra_padding, FALSE); + } + tee_fputs(" |", PAGER); + } + (void) tee_fputs("\n", PAGER); + } + tee_puts((char*) separator.ptr(), PAGER); + my_afree((uchar*) num_flag); +} + +/** + Return the length of a field after it would be rendered into text. + + This doesn't know or care about multibyte characters. Assume we're + using such a charset. We can't know that all of the upcoming rows + for this column will have bytes that each render into some fraction + of a character. It's at least possible that a row has bytes that + all render into one character each, and so the maximum length is + still the number of bytes. (Assumption 1: This can't be better + because we can never know the number of characters that the DB is + going to send -- only the number of bytes. 2: Chars <= Bytes.) + + @param field Pointer to a field to be inspected + + @returns number of character positions to be used, at most +*/ +static int get_field_disp_length(MYSQL_FIELD *field) +{ + uint length= column_names ? field->name_length : 0; + + if (quick) + length= MY_MAX(length, field->length); + else + length= MY_MAX(length, field->max_length); + + if (length < 4 && !IS_NOT_NULL(field->flags)) + length= 4; /* Room for "NULL" */ + + return length; +} + +/** + For a new result, return the max number of characters that any + upcoming row may return. + + @param result Pointer to the result to judge + + @returns The max number of characters in any row of this result +*/ + +static int get_result_width(MYSQL_RES *result) +{ + unsigned int len= 0; + MYSQL_FIELD *field; + MYSQL_FIELD_OFFSET offset; + +#ifndef DBUG_OFF + offset= mysql_field_tell(result); + DBUG_ASSERT(offset == 0); +#else + offset= 0; +#endif + + while ((field= mysql_fetch_field(result)) != NULL) + len+= get_field_disp_length(field) + 3; /* plus bar, space, & final space */ + + (void) mysql_field_seek(result, offset); + + return len + 1; /* plus final bar. */ +} + +static void +tee_print_sized_data(const char *data, unsigned int data_length, unsigned int total_bytes_to_send, bool right_justified) +{ + /* + For '\0's print ASCII spaces instead, as '\0' is eaten by (at + least my) console driver, and that messes up the pretty table + grid. (The \0 is also the reason we can't use fprintf() .) + */ + unsigned int i; + const char *p; + + if (right_justified) + for (i= data_length; i < total_bytes_to_send; i++) + tee_putc((int)' ', PAGER); + + for (i= 0, p= data; i < data_length; i+= 1, p+= 1) + { + if (*p == '\0') + tee_putc((int)' ', PAGER); + else + tee_putc((int)*p, PAGER); + } + + if (! right_justified) + for (i= data_length; i < total_bytes_to_send; i++) + tee_putc((int)' ', PAGER); +} + + + +static void +print_table_data_html(MYSQL_RES *result) +{ + MYSQL_ROW cur; + MYSQL_FIELD *field; + + mysql_field_seek(result,0); + (void) tee_fputs("<TABLE BORDER=1>", PAGER); + if (column_names) + { + (void) tee_fputs("<TR>", PAGER); + while((field = mysql_fetch_field(result))) + { + tee_fputs("<TH>", PAGER); + if (field->name && field->name[0]) + xmlencode_print(field->name, field->name_length); + else + tee_fputs(field->name ? " " : "NULL", PAGER); + tee_fputs("</TH>", PAGER); + } + (void) tee_fputs("</TR>", PAGER); + } + while ((cur = mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths=mysql_fetch_lengths(result); + field= mysql_fetch_fields(result); + (void) tee_fputs("<TR>", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + (void) tee_fputs("<TD>", PAGER); + if (opt_binhex && is_binary_field(&field[i])) + print_as_hex(PAGER, cur[i], lengths[i], lengths[i]); + else + xmlencode_print(cur[i], lengths[i]); + (void) tee_fputs("</TD>", PAGER); + } + (void) tee_fputs("</TR>", PAGER); + } + (void) tee_fputs("</TABLE>", PAGER); +} + + +static void +print_table_data_xml(MYSQL_RES *result) +{ + MYSQL_ROW cur; + MYSQL_FIELD *fields; + + mysql_field_seek(result,0); + + tee_fputs("<?xml version=\"1.0\"?>\n\n<resultset statement=\"", PAGER); + xmlencode_print(glob_buffer.ptr(), (int)strlen(glob_buffer.ptr())); + tee_fputs("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">", + PAGER); + + fields = mysql_fetch_fields(result); + while ((cur = mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths=mysql_fetch_lengths(result); + (void) tee_fputs("\n <row>\n", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + tee_fprintf(PAGER, "\t<field name=\""); + xmlencode_print(fields[i].name, (uint) strlen(fields[i].name)); + if (cur[i]) + { + tee_fprintf(PAGER, "\">"); + if (opt_binhex && is_binary_field(&fields[i])) + print_as_hex(PAGER, cur[i], lengths[i], lengths[i]); + else + xmlencode_print(cur[i], lengths[i]); + tee_fprintf(PAGER, "</field>\n"); + } + else + tee_fprintf(PAGER, "\" xsi:nil=\"true\" />\n"); + } + (void) tee_fputs(" </row>\n", PAGER); + } + (void) tee_fputs("</resultset>\n", PAGER); +} + + +static void +print_table_data_vertically(MYSQL_RES *result) +{ + MYSQL_ROW cur; + uint max_length=0; + MYSQL_FIELD *field; + + while ((field = mysql_fetch_field(result))) + { + uint length= field->name_length; + if (length > max_length) + max_length= length; + field->max_length=length; + } + + mysql_field_seek(result,0); + for (uint row_count=1; (cur= mysql_fetch_row(result)); row_count++) + { + if (interrupted_query) + break; + mysql_field_seek(result,0); + tee_fprintf(PAGER, + "*************************** %d. row ***************************\n", row_count); + + ulong *lengths= mysql_fetch_lengths(result); + + for (uint off=0; off < mysql_num_fields(result); off++) + { + field= mysql_fetch_field(result); + if (column_names) + tee_fprintf(PAGER, "%*s: ",(int) max_length,field->name); + if (cur[off]) + { + unsigned int i; + const char *p; + if (opt_binhex && is_binary_field(field)) + fprintf(PAGER, "0x"); + for (i= 0, p= cur[off]; i < lengths[off]; i+= 1, p+= 1) + { + if (opt_binhex && is_binary_field(field)) + fprintf(PAGER, "%02X", *((uchar*)p)); + else + { + if (*p == '\0') + tee_putc((int)' ', PAGER); + else + tee_putc((int)*p, PAGER); + } + } + tee_putc('\n', PAGER); + } + else + tee_fprintf(PAGER, "NULL\n"); + } + } +} + +/* print_warnings should be called right after executing a statement */ + +static void print_warnings() +{ + const char *query; + MYSQL_RES *result; + MYSQL_ROW cur; + my_ulonglong num_rows; + + /* Save current error before calling "show warnings" */ + uint error= mysql_errno(&mysql); + + /* Get the warnings */ + query= "show warnings"; + mysql_real_query_for_lazy(query, strlen(query)); + mysql_store_result_for_lazy(&result); + + /* Bail out when no warnings */ + if (!result || !(num_rows= mysql_num_rows(result))) + goto end; + + cur= mysql_fetch_row(result); + + /* + Don't print a duplicate of the current error. It is possible for SHOW + WARNINGS to return multiple errors with the same code, but different + messages. To be safe, skip printing the duplicate only if it is the only + warning. + */ + if (!cur || (num_rows == 1 && error == (uint) strtoul(cur[1], NULL, 10))) + goto end; + + /* Print the warnings */ + init_pager(); + do + { + tee_fprintf(PAGER, "%s (Code %s): %s\n", cur[0], cur[1], cur[2]); + } while ((cur= mysql_fetch_row(result))); + end_pager(); + +end: + mysql_free_result(result); +} + + +static const char *array_value(const char **array, char key) +{ + for (; *array; array+= 2) + if (**array == key) + return array[1]; + return 0; +} + + +static void +xmlencode_print(const char *src, uint length) +{ + if (!src) + tee_fputs("NULL", PAGER); + else + { + for (const char *p = src; length; p++, length--) + { + const char *t; + if ((t = array_value(xmlmeta, *p))) + tee_fputs(t, PAGER); + else + tee_putc(*p, PAGER); + } + } +} + + +static void +safe_put_field(const char *pos,ulong length) +{ + if (!pos) + tee_fputs("NULL", PAGER); + else + { + if (opt_raw_data) + { + unsigned long i; + /* Can't use tee_fputs(), it stops with NUL characters. */ + for (i= 0; i < length; i++, pos++) + tee_putc(*pos, PAGER); + } + else for (const char *end=pos+length ; pos != end ; pos++) + { +#ifdef USE_MB + int l; + if (charset_info->use_mb() && + (l = my_ismbchar(charset_info, pos, end))) + { + while (l--) + tee_putc(*pos++, PAGER); + pos--; + continue; + } +#endif + if (!*pos) + tee_fputs("\\0", PAGER); // This makes everything hard + else if (*pos == '\t') + tee_fputs("\\t", PAGER); // This would destroy tab format + else if (*pos == '\n') + tee_fputs("\\n", PAGER); // This too + else if (*pos == '\\') + tee_fputs("\\\\", PAGER); + else + tee_putc(*pos, PAGER); + } + } +} + + +static void +print_tab_data(MYSQL_RES *result) +{ + MYSQL_ROW cur; + MYSQL_FIELD *field; + ulong *lengths; + + if (opt_silent < 2 && column_names) + { + int first=0; + while ((field = mysql_fetch_field(result))) + { + if (first++) + (void) tee_fputs("\t", PAGER); + (void) tee_fputs(field->name, PAGER); + } + (void) tee_fputs("\n", PAGER); + } + while ((cur = mysql_fetch_row(result))) + { + lengths=mysql_fetch_lengths(result); + field= mysql_fetch_fields(result); + if (opt_binhex && is_binary_field(&field[0])) + print_as_hex(PAGER, cur[0], lengths[0], lengths[0]); + else + safe_put_field(cur[0],lengths[0]); + + for (uint off=1 ; off < mysql_num_fields(result); off++) + { + (void) tee_fputs("\t", PAGER); + if (opt_binhex && field && is_binary_field(&field[off])) + print_as_hex(PAGER, cur[off], lengths[off], lengths[off]); + else + safe_put_field(cur[off], lengths[off]); + } + (void) tee_fputs("\n", PAGER); + } +} + +static int +com_tee(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + char file_name[FN_REFLEN], *end, *param; + + if (status.batch) + return 0; + while (my_isspace(charset_info,*line)) + line++; + if (!(param = strchr(line, ' '))) // if outfile wasn't given, use the default + { + if (!strlen(outfile)) + { + printf("No previous outfile available, you must give a filename!\n"); + return 0; + } + else if (opt_outfile) + { + tee_fprintf(stdout, "Currently logging to file '%s'\n", outfile); + return 0; + } + else + param = outfile; //resume using the old outfile + } + + /* eliminate the spaces before the parameters */ + while (my_isspace(charset_info,*param)) + param++; + end= strmake_buf(file_name, param); + /* remove end space from command line */ + while (end > file_name && (my_isspace(charset_info,end[-1]) || + my_iscntrl(charset_info,end[-1]))) + end--; + end[0]= 0; + if (end == file_name) + { + printf("No outfile specified!\n"); + return 0; + } + init_tee(file_name); + return 0; +} + + +static int +com_notee(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + if (opt_outfile) + end_tee(); + tee_fprintf(stdout, "Outfile disabled.\n"); + return 0; +} + +/* + Sorry, this command is not available in Windows. +*/ + +#ifdef USE_POPEN +static int +com_pager(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + char pager_name[FN_REFLEN], *end, *param; + + if (status.batch) + return 0; + /* Skip spaces in front of the pager command */ + while (my_isspace(charset_info, *line)) + line++; + /* Skip the pager command */ + param= strchr(line, ' '); + /* Skip the spaces between the command and the argument */ + while (param && my_isspace(charset_info, *param)) + param++; + if (!param || !strlen(param)) // if pager was not given, use the default + { + if (!default_pager_set) + { + tee_fprintf(stdout, "Default pager wasn't set, using stdout.\n"); + opt_nopager=1; + strmov(pager, "stdout"); + PAGER= stdout; + return 0; + } + strmov(pager, default_pager); + } + else + { + end= strmake_buf(pager_name, param); + while (end > pager_name && (my_isspace(charset_info,end[-1]) || + my_iscntrl(charset_info,end[-1]))) + end--; + end[0]=0; + strmov(pager, pager_name); + strmov(default_pager, pager_name); + } + opt_nopager=0; + tee_fprintf(stdout, "PAGER set to '%s'\n", pager); + return 0; +} + + +static int +com_nopager(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + strmov(pager, "stdout"); + opt_nopager=1; + PAGER= stdout; + tee_fprintf(stdout, "PAGER set to stdout\n"); + return 0; +} +#endif + +#ifdef USE_POPEN +static int +com_edit(String *buffer,char *line __attribute__((unused))) +{ + char filename[FN_REFLEN],buff[160]; + int fd,tmp,error; + const char *editor; + MY_STAT stat_arg; + + if ((fd= create_temp_file(filename,NullS,"sql", 0, MYF(MY_WME))) < 0) + goto err; + if (buffer->is_empty() && !old_buffer.is_empty()) + (void) my_write(fd,(uchar*) old_buffer.ptr(),old_buffer.length(), + MYF(MY_WME)); + else + (void) my_write(fd,(uchar*) buffer->ptr(),buffer->length(),MYF(MY_WME)); + (void) my_close(fd,MYF(0)); + + if (!(editor = (char *)getenv("EDITOR")) && + !(editor = (char *)getenv("VISUAL"))) + editor = IF_WIN("notepad","vi"); + strxmov(buff,editor," ",filename,NullS); + if ((error= system(buff))) + { + char errmsg[100]; + sprintf(errmsg, "Command '%.40s' failed", buff); + put_info(errmsg, INFO_ERROR, 0, NullS); + goto err; + } + + if (!my_stat(filename,&stat_arg,MYF(MY_WME))) + goto err; + if ((fd = my_open(filename,O_RDONLY, MYF(MY_WME))) < 0) + goto err; + (void) buffer->alloc((uint) stat_arg.st_size); + if ((tmp=(int)my_read(fd,(uchar*) buffer->ptr(),buffer->alloced_length(),MYF(0))) >= 0) + buffer->length((uint) tmp); + else + buffer->length(0); + (void) my_close(fd,MYF(0)); + (void) my_delete(filename,MYF(MY_WME)); +err: + return 0; +} +#endif + + +/* If arg is given, exit without errors. This happens on command 'quit' */ + +static int +com_quit(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + status.exit_status=0; + return 1; +} + +static int +com_rehash(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ +#ifdef HAVE_READLINE + build_completion_hash(1, 0); +#endif + return 0; +} + + +#ifdef USE_POPEN +static int +com_shell(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + char *shell_cmd; + + /* Skip space from line begin */ + while (my_isspace(charset_info, *line)) + line++; + if (!(shell_cmd = strchr(line, ' '))) + { + put_info("Usage: \\! shell-command", INFO_ERROR); + return -1; + } + /* + The output of the shell command does not + get directed to the pager or the outfile + */ + if (system(shell_cmd) == -1) + { + put_info(strerror(errno), INFO_ERROR, errno); + return -1; + } + return 0; +} +#endif + + +static int +com_print(String *buffer,char *line __attribute__((unused))) +{ + tee_puts("--------------", stdout); + (void) tee_fputs(buffer->c_ptr(), stdout); + if (!buffer->length() || (*buffer)[buffer->length()-1] != '\n') + tee_putc('\n', stdout); + tee_puts("--------------\n", stdout); + return 0; /* If empty buffer */ +} + + /* ARGSUSED */ +static int +com_connect(String *buffer, char *line) +{ + char *tmp, buff[256]; + my_bool save_rehash= opt_rehash; + int error; + + bzero(buff, sizeof(buff)); + if (buffer) + { + /* + Two null bytes are needed in the end of buff to allow + get_arg to find end of string the second time it's called. + */ + tmp= strmake(buff, line, sizeof(buff)-2); +#ifdef EXTRA_DEBUG + tmp[1]= 0; +#endif + tmp= get_arg(buff, GET); + if (tmp && *tmp) + { + my_free(current_db); + current_db= my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME)); + tmp= get_arg(buff, GET_NEXT); + if (tmp) + { + my_free(current_host); + current_host=my_strdup(PSI_NOT_INSTRUMENTED, tmp,MYF(MY_WME)); + } + } + else + { + /* Quick re-connect */ + opt_rehash= 0; /* purecov: tested */ + } + buffer->length(0); // command used + } + else + opt_rehash= 0; + error=sql_connect(current_host,current_db,current_user,opt_password,0); + opt_rehash= save_rehash; + + if (connected) + { + sprintf(buff,"Connection id: %lu",mysql_thread_id(&mysql)); + put_info(buff,INFO_INFO); + sprintf(buff,"Current database: %.128s\n", + current_db ? current_db : "*** NONE ***"); + put_info(buff,INFO_INFO); + } + return error; +} + + +static int com_source(String *buffer __attribute__((unused)), + char *line) +{ + char source_name[FN_REFLEN], *end, *param; + LINE_BUFFER *line_buff; + int error; + STATUS old_status; + FILE *sql_file; + my_bool save_ignore_errors; + + /* Skip space from file name */ + while (my_isspace(charset_info,*line)) + line++; + if (!(param = strchr(line, ' '))) // Skip command name + return put_info("Usage: \\. <filename> | source <filename>", + INFO_ERROR, 0); + while (my_isspace(charset_info,*param)) + param++; + end=strmake_buf(source_name, param); + while (end > source_name && (my_isspace(charset_info,end[-1]) || + my_iscntrl(charset_info,end[-1]))) + end--; + end[0]=0; + unpack_filename(source_name,source_name); + /* open file name */ + if (!(sql_file = my_fopen(source_name, O_RDONLY | O_BINARY,MYF(0)))) + { + char buff[FN_REFLEN+60]; + sprintf(buff,"Failed to open file '%s', error: %d", source_name,errno); + return put_info(buff, INFO_ERROR, 0); + } + + if (!(line_buff= batch_readline_init(MAX_BATCH_BUFFER_SIZE, sql_file))) + { + my_fclose(sql_file,MYF(0)); + return put_info("Can't initialize batch_readline", INFO_ERROR, 0); + } + + /* Save old status */ + old_status=status; + save_ignore_errors= ignore_errors; + bfill((char*) &status,sizeof(status),(char) 0); + + status.batch=old_status.batch; // Run in batch mode + status.line_buff=line_buff; + status.file_name=source_name; + glob_buffer.length(0); // Empty command buffer + ignore_errors= !batch_abort_on_error; + in_com_source= 1; + error= read_and_execute(false); + ignore_errors= save_ignore_errors; + status=old_status; // Continue as before + in_com_source= aborted= 0; + my_fclose(sql_file,MYF(0)); + batch_readline_end(line_buff); + /* + If we got an error during source operation, don't abort the client + if ignore_errors is set + */ + if (error && ignore_errors) + error= -1; // Ignore error + return error; +} + + + /* ARGSUSED */ +static int +com_delimiter(String *buffer __attribute__((unused)), char *line) +{ + char buff[256], *tmp; + + strmake_buf(buff, line); + tmp= get_arg(buff, GET); + + if (!tmp || !*tmp) + { + put_info("DELIMITER must be followed by a 'delimiter' character or string", + INFO_ERROR); + return 0; + } + else + { + if (strstr(tmp, "\\")) + { + put_info("DELIMITER cannot contain a backslash character", INFO_ERROR); + return 0; + } + } + strmake_buf(delimiter, tmp); + delimiter_length= (int)strlen(delimiter); + delimiter_str= delimiter; + return 0; +} + + /* ARGSUSED */ +static int +com_use(String *buffer __attribute__((unused)), char *line) +{ + char *tmp, buff[FN_REFLEN + 1]; + int select_db; + + bzero(buff, sizeof(buff)); + strmake_buf(buff, line); + tmp= get_arg(buff, GET); + if (!tmp || !*tmp) + { + put_info("USE must be followed by a database name", INFO_ERROR); + return 0; + } + /* + We need to recheck the current database, because it may change + under our feet, for example if DROP DATABASE or RENAME DATABASE + (latter one not yet available by the time the comment was written) + */ + get_current_db(); + + if (!current_db || cmp_database(charset_info, current_db,tmp)) + { + if (one_database) + { + skip_updates= 1; + select_db= 0; // don't do mysql_select_db() + } + else + select_db= 2; // do mysql_select_db() and build_completion_hash() + } + else + { + /* + USE to the current db specified. + We do need to send mysql_select_db() to make server + update database level privileges, which might + change since last USE (see bug#10979). + For performance purposes, we'll skip rebuilding of completion hash. + */ + skip_updates= 0; + select_db= 1; // do only mysql_select_db(), without completion + } + + if (select_db) + { + /* + reconnect once if connection is down or if connection was found to + be down during query + */ + if (!connected && reconnect()) + return opt_reconnect ? -1 : 1; // Fatal error + if (mysql_select_db(&mysql,tmp)) + { + if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR) + return put_error(&mysql); + + if (reconnect()) + return opt_reconnect ? -1 : 1; // Fatal error + if (mysql_select_db(&mysql,tmp)) + return put_error(&mysql); + } + my_free(current_db); + current_db=my_strdup(PSI_NOT_INSTRUMENTED, tmp,MYF(MY_WME)); +#ifdef HAVE_READLINE + if (select_db > 1) + build_completion_hash(opt_rehash, 1); +#endif + } + + put_info("Database changed",INFO_INFO); + return 0; +} + +static int +com_warnings(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + show_warnings = 1; + put_info("Show warnings enabled.",INFO_INFO); + return 0; +} + +static int +com_nowarnings(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + show_warnings = 0; + put_info("Show warnings disabled.",INFO_INFO); + return 0; +} + +/* + Gets argument from a command on the command line. If mode is not GET_NEXT, + skips the command and returns the first argument. The line is modified by + adding zero to the end of the argument. If mode is GET_NEXT, then the + function searches for end of string first, after found, returns the next + argument and adds zero to the end. If you ever wish to use this feature, + remember to initialize all items in the array to zero first. +*/ + +static char *get_arg(char *line, get_arg_mode mode) +{ + char *ptr, *start; + bool short_cmd= false; + char qtype= 0; + + ptr= line; + if (mode == GET_NEXT) + { + for (; *ptr; ptr++) ; + if (*(ptr + 1)) + ptr++; + } + else + { + /* skip leading white spaces */ + while (my_isspace(charset_info, *ptr)) + ptr++; + if ((short_cmd= *ptr == '\\')) // short command was used + ptr+= 2; + else + while (*ptr &&!my_isspace(charset_info, *ptr)) // skip command + ptr++; + } + if (!*ptr) + return NullS; + while (my_isspace(charset_info, *ptr)) + ptr++; + if (*ptr == '\'' || *ptr == '\"' || *ptr == '`') + { + qtype= *ptr; + ptr++; + } + for (start=ptr ; *ptr; ptr++) + { + /* if short_cmd use historical rules (only backslash) otherwise SQL rules */ + if (short_cmd + ? (*ptr == '\\' && ptr[1]) // escaped character + : (*ptr == '\\' && ptr[1] && qtype != '`') || // escaped character + (qtype && *ptr == qtype && ptr[1] == qtype)) // quote + { + // Remove (or skip) the backslash (or a second quote) + if (mode != CHECK) + strmov_overlapp(ptr, ptr+1); + else + ptr++; + } + else if (*ptr == (qtype ? qtype : ' ')) + { + qtype= 0; + if (mode != CHECK) + *ptr= 0; + break; + } + } + return ptr != start && !qtype ? start : NullS; +} + + +/** + An example of mysql_authentication_dialog_ask callback. + + The C function with the name "mysql_authentication_dialog_ask", if exists, + will be used by the "dialog" client authentication plugin when user + input is needed. This function should be of mysql_authentication_dialog_ask_t + type. If the function does not exists, a built-in implementation will be + used. + + @param mysql mysql + @param type type of the input + 1 - normal string input + 2 - password string + @param prompt prompt + @param buf a buffer to store the use input + @param buf_len the length of the buffer + + @retval a pointer to the user input string. + It may be equal to 'buf' or to 'mysql->password'. + In all other cases it is assumed to be an allocated + string, and the "dialog" plugin will free() it. +*/ + +extern "C" +#ifdef _MSC_VER +__declspec(dllexport) +#endif +char *mysql_authentication_dialog_ask(MYSQL *mysql, int type, + const char *prompt, + char *buf, int buf_len) +{ + char *s=buf; + + fputs("[mariadb] ", stdout); + fputs(prompt, stdout); + fputs(" ", stdout); + + if (type == 2) /* password */ + { + s= my_get_tty_password(""); + strnmov(buf, s, buf_len); + buf[buf_len-1]= 0; + my_free(s); + } + else + { + if (!fgets(buf, buf_len-1, stdin)) + buf[0]= 0; + else if (buf[0] && (s= strend(buf))[-1] == '\n') + s[-1]= 0; + } + + return buf; +} + +static int +sql_real_connect(char *host,char *database,char *user,char *password, + uint silent) +{ + const char *charset_name; + + if (connected) + { + connected= 0; + mysql_close(&mysql); + } + mysql_init(&mysql); + if (opt_init_command) + mysql_options(&mysql, MYSQL_INIT_COMMAND, opt_init_command); + if (opt_connect_timeout) + { + uint timeout=opt_connect_timeout; + mysql_options(&mysql,MYSQL_OPT_CONNECT_TIMEOUT, + (char*) &timeout); + } + if (opt_compress) + mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); + if (using_opt_local_infile) + mysql_options(&mysql,MYSQL_OPT_LOCAL_INFILE, (char*) &opt_local_infile); + if (safe_updates) + { + char init_command[100]; + sprintf(init_command, + "SET SQL_SAFE_UPDATES=1,SQL_SELECT_LIMIT=%lu,MAX_JOIN_SIZE=%lu", + select_limit,max_join_size); + mysql_options(&mysql, MYSQL_INIT_COMMAND, init_command); + } + if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); + + my_bool can_handle_expired= opt_connect_expired_password || !status.batch; + mysql_options(&mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired); + + if (!do_connect(&mysql, host, user, password, database, + connect_flag | CLIENT_MULTI_STATEMENTS)) + { + if (!silent || + (mysql_errno(&mysql) != CR_CONN_HOST_ERROR && + mysql_errno(&mysql) != CR_CONNECTION_ERROR)) + { + (void) put_error(&mysql); + (void) fflush(stdout); + return ignore_errors ? -1 : 1; // Abort + } + return -1; // Retryable + } + + charset_name= IF_EMBEDDED(mysql.charset->coll_name.str, + mysql.charset->name); + charset_info= get_charset_by_name(charset_name, MYF(MY_UTF8_IS_UTF8MB3)); + if (!charset_info) + { + char buff[128]; + my_snprintf(buff, sizeof(buff)-1, + "Unknown default character set %s", charset_name); + put_info(buff, INFO_ERROR); + return 1; + } + adjust_console_codepage(charset_info->cs_name.str); + connected=1; +#ifndef EMBEDDED_LIBRARY + mysql_options(&mysql, MYSQL_OPT_RECONNECT, &debug_info_flag); + + /* + CLIENT_PROGRESS_OBSOLETE is set only if we requested it in + mysql_real_connect() and the server also supports it +*/ + if (mysql.client_flag & CLIENT_PROGRESS_OBSOLETE) + mysql_options(&mysql, MYSQL_PROGRESS_CALLBACK, (void*) report_progress); +#else + { + my_bool reconnect= 1; + mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect); + } +#endif +#ifdef HAVE_READLINE + build_completion_hash(opt_rehash, 1); +#endif + return 0; +} + + +static int +sql_connect(char *host,char *database,char *user,char *password,uint silent) +{ + bool message=0; + uint count=0; + int error; + for (;;) + { + if ((error=sql_real_connect(host,database,user,password,wait_flag)) >= 0) + { + if (count) + { + tee_fputs("\n", stderr); + (void) fflush(stderr); + } + return error; + } + if (!wait_flag) + return ignore_errors ? -1 : 1; + if (!message && !silent) + { + message=1; + tee_fputs("Waiting",stderr); (void) fflush(stderr); + } + (void) sleep(wait_time); + if (!silent) + { + putc('.',stderr); (void) fflush(stderr); + count++; + } + } +} + + + +static int +com_status(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + const char *status_str; + char buff[40]; + ulonglong id; + MYSQL_RES *UNINIT_VAR(result); + + if (mysql_real_query_for_lazy( + C_STRING_WITH_LEN("select DATABASE(), USER() limit 1"))) + return 0; + + tee_puts("--------------", stdout); + usage(1); /* Print version */ + tee_fprintf(stdout, "\nConnection id:\t\t%lu\n",mysql_thread_id(&mysql)); + /* + Don't remove "limit 1", + it is protection against SQL_SELECT_LIMIT=0 + */ + if (!mysql_store_result_for_lazy(&result)) + { + MYSQL_ROW cur=mysql_fetch_row(result); + if (cur) + { + tee_fprintf(stdout, "Current database:\t%s\n", cur[0] ? cur[0] : ""); + tee_fprintf(stdout, "Current user:\t\t%s\n", cur[1]); + } + mysql_free_result(result); + } + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if ((status_str= mysql_get_ssl_cipher(&mysql))) + tee_fprintf(stdout, "SSL:\t\t\tCipher in use is %s\n", + status_str); + else +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + tee_puts("SSL:\t\t\tNot in use", stdout); + + if (skip_updates) + { + my_vidattr(A_BOLD); + tee_fprintf(stdout, "\nAll updates ignored to this database\n"); + my_vidattr(A_NORMAL); + } +#ifdef USE_POPEN + tee_fprintf(stdout, "Current pager:\t\t%s\n", pager); + tee_fprintf(stdout, "Using outfile:\t\t'%s'\n", opt_outfile ? outfile : ""); +#endif + tee_fprintf(stdout, "Using delimiter:\t%s\n", delimiter); + tee_fprintf(stdout, "Server:\t\t\t%s\n", mysql_get_server_name(&mysql)); + tee_fprintf(stdout, "Server version:\t\t%s\n", server_version_string(&mysql)); + tee_fprintf(stdout, "Protocol version:\t%d\n", mysql_get_proto_info(&mysql)); + tee_fprintf(stdout, "Connection:\t\t%s\n", mysql_get_host_info(&mysql)); + if ((id= mysql_insert_id(&mysql))) + tee_fprintf(stdout, "Insert id:\t\t%s\n", llstr(id, buff)); + + /* "limit 1" is protection against SQL_SELECT_LIMIT=0 */ + if (mysql_real_query_for_lazy(C_STRING_WITH_LEN( + "select @@character_set_client, @@character_set_connection, " + "@@character_set_server, @@character_set_database limit 1"))) + { + if (mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) + return 0; + } + if (!mysql_store_result_for_lazy(&result)) + { + MYSQL_ROW cur=mysql_fetch_row(result); + if (cur) + { + tee_fprintf(stdout, "Server characterset:\t%s\n", cur[2] ? cur[2] : ""); + tee_fprintf(stdout, "Db characterset:\t%s\n", cur[3] ? cur[3] : ""); + tee_fprintf(stdout, "Client characterset:\t%s\n", cur[0] ? cur[0] : ""); + tee_fprintf(stdout, "Conn. characterset:\t%s\n", cur[1] ? cur[1] : ""); + } + mysql_free_result(result); + } + else + { + /* Probably pre-4.1 server */ + tee_fprintf(stdout, "Client characterset:\t%s\n", charset_info->cs_name.str); + tee_fprintf(stdout, "Server characterset:\t%s\n", + mysql_character_set_name(&mysql)); + } + +#ifndef EMBEDDED_LIBRARY + if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket) + tee_fprintf(stdout, "TCP port:\t\t%d\n", mysql.port); + else + tee_fprintf(stdout, "UNIX socket:\t\t%s\n", mysql.unix_socket); + if (mysql.net.compress) + tee_fprintf(stdout, "Protocol:\t\tCompressed\n"); +#endif + + const char *pos; + if ((status_str= mysql_stat(&mysql)) && !mysql_error(&mysql)[0] && + (pos= strchr(status_str,' '))) + { + ulong sec; + /* print label */ + tee_fprintf(stdout, "%.*s\t\t\t", (int) (pos-status_str), status_str); + if ((status_str= str2int(pos,10,0,LONG_MAX,(long*) &sec))) + { + nice_time((double) sec,buff,0); + tee_puts(buff, stdout); /* print nice time */ + while (*status_str == ' ') + status_str++; /* to next info */ + tee_putc('\n', stdout); + tee_puts(status_str, stdout); + } + } + if (safe_updates) + { + my_vidattr(A_BOLD); + tee_fprintf(stdout, "\nNote that you are running in safe_update_mode:\n"); + my_vidattr(A_NORMAL); + tee_fprintf(stdout, "\ +UPDATEs and DELETEs that don't use a key in the WHERE clause are not allowed.\n\ +(One can force an UPDATE/DELETE by adding LIMIT # at the end of the command.)\n\ +SELECT has an automatic 'LIMIT %lu' if LIMIT is not used.\n\ +Max number of examined row combination in a join is set to: %lu\n\n", +select_limit, max_join_size); + } + tee_puts("--------------\n", stdout); + return 0; +} + +static const char * +server_version_string(MYSQL *con) +{ + /* Only one thread calls this, so no synchronization is needed */ + if (server_version == NULL) + { + MYSQL_RES *result; + + /* "limit 1" is protection against SQL_SELECT_LIMIT=0 */ + if (!mysql_query(con, "select @@version_comment limit 1") && + (result = mysql_use_result(con))) + { + MYSQL_ROW cur = mysql_fetch_row(result); + if (cur && cur[0]) + { + /* version, space, comment, \0 */ + size_t len= strlen(mysql_get_server_info(con)) + strlen(cur[0]) + 2; + + if ((server_version= (char *) my_malloc(PSI_NOT_INSTRUMENTED, len, MYF(MY_WME)))) + { + char *bufp; + bufp = strmov(server_version, mysql_get_server_info(con)); + bufp = strmov(bufp, " "); + (void) strmov(bufp, cur[0]); + } + } + mysql_free_result(result); + } + + /* + If for some reason we didn't get a version_comment, we'll + keep things simple. + */ + + if (server_version == NULL) + server_version= my_strdup(PSI_NOT_INSTRUMENTED, mysql_get_server_info(con), MYF(MY_WME)); + } + + return server_version ? server_version : ""; +} + +static int +put_info(const char *str,INFO_TYPE info_type, uint error, const char *sqlstate) +{ + FILE *file= (info_type == INFO_ERROR ? stderr : stdout); + static int inited=0; + + if (status.batch) + { + if (info_type == INFO_ERROR) + { + (void) fflush(file); + fprintf(file,"ERROR"); + if (error) + { + if (sqlstate) + (void) fprintf(file," %d (%s)",error, sqlstate); + else + (void) fprintf(file," %d",error); + } + if (status.query_start_line && line_numbers) + { + (void) fprintf(file," at line %lu",status.query_start_line); + if (status.file_name) + (void) fprintf(file," in file: '%s'", status.file_name); + } + (void) fprintf(file,": %s\n",str); + (void) fflush(file); + if (!ignore_errors) + return 1; + } + else if (info_type == INFO_RESULT && verbose > 1) + tee_puts(str, file); + if (unbuffered) + fflush(file); + return info_type == INFO_ERROR ? -1 : 0; + } + if (!opt_silent || info_type == INFO_ERROR) + { + if (!inited) + { +#ifdef HAVE_SETUPTERM + int errret; + have_curses= setupterm((char *)0, 1, &errret) != ERR; +#endif + inited=1; + } + if (info_type == INFO_ERROR) + { + if (!opt_nobeep) + { +#ifdef _WIN32 + MessageBeep(MB_ICONWARNING); +#else + putchar('\a'); /* This should make a bell */ +#endif + } + my_vidattr(A_STANDOUT); + if (error) + { + if (sqlstate) + (void) tee_fprintf(file, "ERROR %d (%s)", error, sqlstate); + else + (void) tee_fprintf(file, "ERROR %d", error); + } + else + tee_fputs("ERROR", file); + if (status.query_start_line && line_numbers) + { + (void) fprintf(file," at line %lu",status.query_start_line); + if (status.file_name) + (void) fprintf(file," in file: '%s'", status.file_name); + } + tee_fputs(": ", file); + } + else + my_vidattr(A_BOLD); + (void) tee_puts(str, file); + my_vidattr(A_NORMAL); + } + if (unbuffered) + fflush(file); + return info_type == INFO_ERROR ? (ignore_errors ? -1 : 1): 0; +} + + +static int +put_error(MYSQL *con) +{ + return put_info(mysql_error(con), INFO_ERROR, mysql_errno(con), + mysql_sqlstate(con)); +} + + +static void remove_cntrl(String &buffer) +{ + char *start,*end; + end=(start=(char*) buffer.ptr())+buffer.length(); + while (start < end && !my_isgraph(charset_info,end[-1])) + end--; + buffer.length((uint) (end-start)); +} + + +void tee_fprintf(FILE *file, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + (void) vfprintf(file, fmt, args); + va_end(args); + + if (opt_outfile) + { + va_start(args, fmt); + (void) vfprintf(OUTFILE, fmt, args); + va_end(args); + } +} + + +void tee_fputs(const char *s, FILE *file) +{ + fputs(s, file); + if (opt_outfile) + fputs(s, OUTFILE); +} + + +void tee_puts(const char *s, FILE *file) +{ + fputs(s, file); + fputc('\n', file); + if (opt_outfile) + { + fputs(s, OUTFILE); + fputc('\n', OUTFILE); + } +} + +void tee_putc(int c, FILE *file) +{ + putc(c, file); + if (opt_outfile) + putc(c, OUTFILE); +} + + +/** + Write as many as 52+1 bytes to buff, in the form of a legible duration of time. + + len("4294967296 days, 23 hours, 59 minutes, 60.000 seconds") -> 53 +*/ +static void nice_time(double sec,char *buff,bool part_second) +{ + ulong tmp; + if (sec >= 3600.0*24) + { + tmp=(ulong) floor(sec/(3600.0*24)); + sec-=3600.0*24*tmp; + buff=int10_to_str((long) tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " days " : " day "); + } + if (sec >= 3600.0) + { + tmp=(ulong) floor(sec/3600.0); + sec-=3600.0*tmp; + buff=int10_to_str((long) tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " hours " : " hour "); + } + if (sec >= 60.0) + { + tmp=(ulong) floor(sec/60.0); + sec-=60.0*tmp; + buff=int10_to_str((long) tmp, buff, 10); + buff=strmov(buff," min "); + } + if (part_second) + sprintf(buff,"%.3f sec",sec); + else + sprintf(buff,"%d sec",(int) sec); +} + + +static void end_timer(ulonglong start_time, char *buff) +{ + double sec; + + buff[0]=' '; + buff[1]='('; + sec= (microsecond_interval_timer() - start_time) / (double) (1000 * 1000); + nice_time(sec, buff + 2, 1); + strmov(strend(buff),")"); +} + +static const char *construct_prompt() +{ + processed_prompt.free(); // Erase the old prompt + time_t lclock = time(NULL); // Get the date struct + struct tm *t = localtime(&lclock); + + /* parse through the settings for the prompt */ + for (char *c = current_prompt; *c ; c++) + { + if (*c != PROMPT_CHAR) + processed_prompt.append(*c); + else + { + switch (*++c) { + case '\0': + c--; // stop it from going beyond if ends with % + break; + case 'c': + add_int_to_prompt(++prompt_counter); + break; + case 'v': + { + const char *info= (connected ? + mysql_get_server_info(&mysql) : + "not_connected"); + processed_prompt.append(info, strlen(info)); + break; + } + case 'd': + { + const char *db= current_db ? current_db : "(none)"; + processed_prompt.append(db, strlen(db)); + break; + } + case 'N': + { + const char *name= (connected ? + mysql_get_server_name(&mysql) : + "unknown"); + processed_prompt.append(name, strlen(name)); + break; + } + case 'h': + case 'H': + { + const char *prompt; + prompt= connected ? mysql_get_host_info(&mysql) : "not_connected"; + if (strstr(prompt, "Localhost") || strstr(prompt, "localhost ")) + { + if (*c == 'h') + processed_prompt.append(STRING_WITH_LEN("localhost")); + else + { + static char hostname[FN_REFLEN]; + static size_t hostname_length; + if (hostname_length) + processed_prompt.append(hostname, hostname_length); + else if (gethostname(hostname, sizeof(hostname)) == 0) + { + hostname_length= strlen(hostname); + processed_prompt.append(hostname, hostname_length); + } + else + processed_prompt.append(STRING_WITH_LEN("gethostname(2) failed")); + } + } + else + { + const char *end=strcend(prompt,' '); + processed_prompt.append(prompt, (uint) (end-prompt)); + } + break; + } + case 'p': + { +#ifndef EMBEDDED_LIBRARY + if (!connected) + { + processed_prompt.append(STRING_WITH_LEN("not_connected")); + break; + } + + const char *host_info = mysql_get_host_info(&mysql); + if (strstr(host_info, "memory")) + { + processed_prompt.append( mysql.host, strlen(mysql.host)); + } + else if (strstr(host_info,"TCP/IP") || + !mysql.unix_socket) + add_int_to_prompt(mysql.port); + else + { + char *pos= strrchr(mysql.unix_socket,'/'); + const char *tmp= pos ? pos+1 : mysql.unix_socket; + processed_prompt.append(tmp, strlen(tmp)); + } +#endif + } + break; + case 'U': + { + const char *name; + if (!full_username) + init_username(); + name= (full_username ? full_username : + (current_user ? current_user : "(unknown)")); + processed_prompt.append(name, strlen(name)); + break; + } + case 'u': + { + const char *name; + if (!full_username) + init_username(); + name= (part_username ? part_username : + (current_user ? current_user : "(unknown)")); + processed_prompt.append(name, strlen(name)); + break; + } + case PROMPT_CHAR: + processed_prompt.append(PROMPT_CHAR); + break; + case 'n': + processed_prompt.append('\n'); + break; + case ' ': + case '_': + processed_prompt.append(' '); + break; + case 'R': + if (t->tm_hour < 10) + processed_prompt.append('0'); + add_int_to_prompt(t->tm_hour); + break; + case 'r': + int getHour; + getHour = t->tm_hour % 12; + if (getHour == 0) + getHour=12; + if (getHour < 10) + processed_prompt.append('0'); + add_int_to_prompt(getHour); + break; + case 'm': + if (t->tm_min < 10) + processed_prompt.append('0'); + add_int_to_prompt(t->tm_min); + break; + case 'y': + int getYear; + getYear = t->tm_year % 100; + if (getYear < 10) + processed_prompt.append('0'); + add_int_to_prompt(getYear); + break; + case 'Y': + add_int_to_prompt(t->tm_year+1900); + break; + case 'D': + { + char* dateTime; + const char *tmp; + dateTime = ctime(&lclock); + tmp= strtok(dateTime,"\n"); + processed_prompt.append(tmp, strlen(tmp)); + break; + } + case 's': + if (t->tm_sec < 10) + processed_prompt.append('0'); + add_int_to_prompt(t->tm_sec); + break; + case 'w': + { + const char *name= day_names[t->tm_wday]; + processed_prompt.append(name, strlen(name)); + break; + } + case 'P': + processed_prompt.append(t->tm_hour < 12 ? "am" : "pm", 2); + break; + case 'o': + add_int_to_prompt(t->tm_mon+1); + break; + case 'O': + { + const char *name= month_names[t->tm_mon]; + processed_prompt.append(name, strlen(name)); + break; + } + case '\'': + processed_prompt.append('\''); + break; + case '"': + processed_prompt.append('"'); + break; + case 'S': + processed_prompt.append(';'); + break; + case 't': + processed_prompt.append('\t'); + break; + case 'l': + processed_prompt.append(delimiter_str, strlen(delimiter_str)); + break; + default: + processed_prompt.append(*c); + } + } + } + processed_prompt.append('\0'); + return processed_prompt.ptr(); +} + + +static void add_int_to_prompt(int toadd) +{ + char buffer[16]; + size_t length= (size_t) (int10_to_str(toadd,buffer,10) - buffer); + processed_prompt.append(buffer, length); +} + +static void init_username() +{ + my_free(full_username); + my_free(part_username); + + MYSQL_RES *UNINIT_VAR(result); + if (!mysql_query(&mysql,"select USER()") && + (result=mysql_use_result(&mysql))) + { + MYSQL_ROW cur=mysql_fetch_row(result); + full_username=my_strdup(PSI_NOT_INSTRUMENTED, cur[0],MYF(MY_WME)); + part_username=my_strdup(PSI_NOT_INSTRUMENTED, strtok(cur[0],"@"),MYF(MY_WME)); + (void) mysql_fetch_row(result); // Read eof + mysql_free_result(result); + } +} + +static int com_prompt(String *buffer __attribute__((unused)), + char *line) +{ + char *ptr=strchr(line, ' '); + prompt_counter = 0; + my_free(current_prompt); + current_prompt=my_strdup(PSI_NOT_INSTRUMENTED, ptr ? ptr+1 : default_prompt,MYF(MY_WME)); + if (!ptr) + tee_fprintf(stdout, "Returning to default PROMPT of %s\n", default_prompt); + else + tee_fprintf(stdout, "PROMPT set to '%s'\n", current_prompt); + return 0; +} + +#ifndef EMBEDDED_LIBRARY +static void report_progress(const MYSQL *mysql, uint stage, uint max_stage, + double progress, const char *proc_info, + uint proc_info_length) +{ + uint length= printf("Stage: %d of %d '%.*s' %6.3g%% of stage done", + stage, max_stage, proc_info_length, proc_info, + progress); + if (length < last_progress_report_length) + printf("%*s", last_progress_report_length - length, ""); + putc('\r', stdout); + fflush(stdout); + last_progress_report_length= length; +} + +static void report_progress_end() +{ + if (last_progress_report_length) + { + printf("%*s\r", last_progress_report_length, ""); + last_progress_report_length= 0; + } +} +#else +static void report_progress_end() +{ +} +#endif |