summaryrefslogtreecommitdiff
path: root/client/mysql.cc
diff options
context:
space:
mode:
Diffstat (limited to 'client/mysql.cc')
-rw-r--r--client/mysql.cc2060
1 files changed, 2060 insertions, 0 deletions
diff --git a/client/mysql.cc b/client/mysql.cc
new file mode 100644
index 00000000000..2636b49cd83
--- /dev/null
+++ b/client/mysql.cc
@@ -0,0 +1,2060 @@
+/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
+
+ 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; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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>
+ *
+ **/
+
+#include <global.h>
+#include <my_sys.h>
+#include <m_string.h>
+#include <m_ctype.h>
+#include "mysql.h"
+#include "errmsg.h"
+#include <my_dir.h>
+#ifndef __GNU_LIBRARY__
+#define __GNU_LIBRARY__ // Skipp warnings in getopt.h
+#endif
+#include <getopt.h>
+#include "my_readline.h"
+#include <signal.h>
+
+gptr sql_alloc(unsigned size); // Don't use mysqld alloc for theese
+void sql_element_free(void *ptr);
+#include "sql_string.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
+
+#undef bcmp // Fix problem with new readline
+#undef bzero
+#ifdef __WIN__
+#include <conio.h>
+#else
+#include <readline/readline.h>
+#define HAVE_READLINE
+#endif
+ //int vidattr(long unsigned int attrs); // Was missing in sun curses
+}
+
+#if !defined(HAVE_VIDATTR)
+#undef vidattr
+#define vidattr(A) {} // Can't get this to work
+#endif
+
+#ifdef __WIN__
+#define cmp_database(A,B) my_strcasecmp((A),(B))
+#else
+#define cmp_database(A,B) strcmp((A),(B))
+#endif
+
+#include "completion_hash.h"
+
+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;
+
+enum enum_info_type { INFO_INFO,INFO_ERROR,INFO_RESULT};
+typedef enum enum_info_type INFO_TYPE;
+
+static MYSQL mysql; /* The connection */
+static bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0,
+ connected=0,opt_raw_data=0,unbuffered=0,output_tables=0,
+ no_rehash=0,skip_updates=0,safe_updates=0,one_database=0,
+ opt_compress=0,
+ vertical=0,skip_line_numbers=0,skip_column_names=0,opt_html=0,
+ no_named_cmds=0;
+static uint verbose=0,opt_silent=0,opt_mysql_port=0;
+static my_string opt_mysql_unix_port=0;
+static int connect_flag=CLIENT_INTERACTIVE;
+static char *current_host,*current_db,*current_user=0,*opt_password=0,
+ *default_charset;
+static char *histfile;
+static String glob_buffer,old_buffer;
+static STATUS status;
+static ulong select_limit,max_join_size;
+
+#include "sslopt-vars.h"
+
+#ifndef DBUG_OFF
+const char *default_dbug_option="d:t:o,/tmp/mysql.trace";
+#endif
+
+/* The names of functions that actually do the manipulation. */
+static int get_options(int argc,char **argv);
+static int com_quit(String *str,char*),
+ com_go(String *str,char*), com_ego(String *str,char*),
+ com_edit(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*);
+
+static int read_lines(bool execute_commands);
+static int sql_connect(char *host,char *database,char *user,char *password,
+ uint silent);
+static int put_info(const char *str,INFO_TYPE info,uint error=0);
+static void safe_put_field(const char *pos,ulong length);
+
+/* 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[] = {
+ { "help", 'h',com_help, 0,"Display this text" },
+ { "?", 'h',com_help, 0,"Synonym for `help'" },
+ { "clear",'c',com_clear,0,"Clear command"},
+ { "connect",'r',com_connect,1,
+ "Reconnect to the server. Optional arguments are db and host" },
+ { "edit", 'e',com_edit, 0,"Edit command with $EDITOR"},
+ { "exit", 'q', com_quit, 0,"Exit mysql. Same as quit"},
+ { "go", 'g',com_go, 0,"Send command to mysql server" },
+ { "ego", 'G',com_ego, 0,
+ "Send command to mysql server; Display result vertically"},
+ { "print",'p',com_print,0,"Print current command" },
+ { "quit", 'q',com_quit, 0,"Quit mysql" },
+ { "rehash", '#', com_rehash, 0, "Rebuild completion hash" },
+ { "source", '.', com_source, 1,
+ "Execute a SQL script file. Takes a file name as an argument"},
+
+ { "status",'s',com_status,0,"Get status information from the server"},
+ { "use",'u',com_use,1,
+ "Use another database. Takes database name as argument" },
+
+ { "create table",0,0,0,""}, /* Get bash expansion for some commmands */
+ { "create database",0,0,0,""},
+ { "drop",0,0,0,""},
+ { "select",0,0,0,""},
+ { "insert",0,0,0,""},
+ { "replace",0,0,0,""},
+ { "update",0,0,0,""},
+ { "delete",0,0,0,""},
+ { "explain",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,""},
+ { (char *)NULL, 0,0,0,""},
+};
+
+static const char *load_default_groups[]= { "mysql","client",0 };
+
+#ifdef HAVE_READLINE
+extern "C" void add_history(char *command); /* From readline directory */
+extern "C" int read_history(char *command);
+extern "C" int write_history(char *command);
+static void initialize_readline (char *name);
+#endif
+
+static COMMANDS *find_command (char *name,char cmd_name);
+static bool add_line(String &buffer,char *line,char *in_string);
+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_tab_data(MYSQL_RES *result);
+static void print_table_data_vertically(MYSQL_RES *result);
+static ulong start_timer(void);
+static void end_timer(ulong start_time,char *buff);
+static void mysql_end_timer(ulong start_time,char *buff);
+static void nice_time(double sec,char *buff,bool part_second);
+static sig_handler mysql_end(int sig);
+
+
+int main(int argc,char *argv[])
+{
+ char buff[80];
+
+ MY_INIT(argv[0]);
+ DBUG_ENTER("main");
+ DBUG_PROCESS(argv[0]);
+
+ if (!isatty(0) || !isatty(1))
+ {
+ status.batch=1; opt_silent=1;
+ ignore_errors=0;
+ }
+ else
+ status.add_to_history=1;
+ status.exit_status=1;
+ load_defaults("my",load_default_groups,&argc,&argv);
+ if (get_options(argc,(char **) argv))
+ {
+ my_end(0);
+ exit(1);
+ }
+ free_defaults(argv);
+ if (status.batch && !status.line_buff &&
+ !(status.line_buff=batch_readline_init(max_allowed_packet+512,stdin)))
+ exit(1);
+ glob_buffer.realloc(512);
+ completion_hash_init(&ht,50);
+ if (sql_connect(current_host,current_db,current_user,opt_password,
+ opt_silent))
+ {
+ if (connected)
+ mysql_close(&mysql);
+ glob_buffer.free();
+ old_buffer.free();
+ batch_readline_end(status.line_buff);
+ my_end(0);
+ exit(1);
+ }
+ if (!status.batch)
+ ignore_errors=1; // Don't abort monitor
+ signal(SIGINT, mysql_end); // Catch SIGINT to clean up
+
+ /*
+ ** Run in interactive mode like the ingres/postgres monitor
+ */
+
+ put_info("Welcome to the MySQL monitor. Commands end with ; or \\g.",
+ INFO_INFO);
+ sprintf((char*) glob_buffer.ptr(),
+ "Your MySQL connection id is %ld to server version: %s\n",
+ mysql_thread_id(&mysql),mysql_get_server_info(&mysql));
+ put_info((char*) glob_buffer.ptr(),INFO_INFO);
+
+#ifdef HAVE_READLINE
+ initialize_readline(my_progname);
+ if (!status.batch && !quick && !opt_html)
+ {
+ /*read-history from file, default ~/.mysql_history*/
+ if (getenv("MYSQL_HISTFILE"))
+ histfile=my_strdup(getenv("MYSQL_HISTFILE"),MYF(MY_WME));
+ else if (getenv("HOME"))
+ {
+ histfile=(char*) my_malloc(strlen(getenv("HOME"))
+ + strlen("/.mysql_history")+2,
+ MYF(MY_WME));
+ if (histfile)
+ sprintf(histfile,"%s/.mysql_history",getenv("HOME"));
+ }
+ if (histfile)
+ {
+ if (verbose)
+ printf("Reading history-file %s\n",histfile);
+ read_history(histfile);
+ }
+ }
+#endif
+ sprintf(buff, "Type '%s' for help.\n", no_named_cmds ? "\\h" : "help");
+ put_info(buff,INFO_INFO);
+ status.exit_status=read_lines(1); // read lines and execute them
+ mysql_end(0);
+#ifndef _lint
+ DBUG_RETURN(0); // Keep compiler happy
+#endif
+}
+
+sig_handler mysql_end(int sig)
+{
+ if (connected)
+ mysql_close(&mysql);
+#ifdef HAVE_READLINE
+ if (!status.batch && !quick && ! opt_html)
+ {
+ /* write-history */
+ if (verbose)
+ printf("Writing history-file %s\n",histfile);
+ write_history(histfile);
+ }
+ batch_readline_end(status.line_buff);
+ completion_hash_free(&ht);
+#endif
+ put_info(sig ? "Aborted" : "Bye", INFO_RESULT);
+ glob_buffer.free();
+ old_buffer.free();
+ my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR));
+ my_free(opt_mysql_unix_port,MYF(MY_ALLOW_ZERO_PTR));
+ my_free(histfile,MYF(MY_ALLOW_ZERO_PTR));
+ my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
+ my_free(current_host,MYF(MY_ALLOW_ZERO_PTR));
+ my_free(current_user,MYF(MY_ALLOW_ZERO_PTR));
+ my_end(info_flag ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
+ exit(status.exit_status);
+}
+
+enum options {OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET} ;
+
+
+static struct option long_options[] =
+{
+ {"i-am-a-dummy", no_argument, 0, 'U'},
+ {"batch", no_argument, 0, 'B'},
+ {"character-sets-dir",required_argument,0, OPT_CHARSETS_DIR},
+ {"compress", no_argument, 0, 'C'},
+#ifndef DBUG_OFF
+ {"debug", optional_argument, 0, '#'},
+#endif
+ {"database", required_argument, 0, 'D'},
+ {"debug-info", no_argument, 0, 'T'},
+ {"default-character-set", required_argument, 0, OPT_DEFAULT_CHARSET},
+ {"execute", required_argument, 0, 'e'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, '?'},
+ {"html", no_argument, 0, 'H'},
+ {"host", required_argument, 0, 'h'},
+ {"ignore-spaces", no_argument, 0, 'i'},
+ {"no-auto-rehash",no_argument, 0, 'A'},
+ {"no-named-commands", no_argument, 0, 'g'},
+ {"one-database", no_argument, 0, 'o'},
+ {"password", optional_argument, 0, 'p'},
+#ifdef __WIN__
+ {"pipe", no_argument, 0, 'W'},
+#endif
+ {"port", required_argument, 0, 'P'},
+ {"quick", no_argument, 0, 'q'},
+ {"set-variable", required_argument, 0, 'O'},
+ {"raw", no_argument, 0, 'r'},
+ {"safe-updates", no_argument, 0, 'U'},
+ {"silent", no_argument, 0, 's'},
+ {"skip-column-names",no_argument, 0, 'N'},
+ {"skip-line-numbers",no_argument, 0, 'L'},
+ {"socket", required_argument, 0, 'S'},
+#include "sslopt-longopts.h"
+ {"table", no_argument, 0, 't'},
+#ifndef DONT_ALLOW_USER_CHANGE
+ {"user", required_argument, 0, 'u'},
+#endif
+ {"unbuffered", no_argument, 0, 'n'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"vertical", no_argument, 0, 'E'},
+ {"wait", no_argument, 0, 'w'},
+ {0, 0, 0, 0}
+};
+
+
+CHANGEABLE_VAR changeable_vars[] = {
+ { "max_allowed_packet", (long*) &max_allowed_packet,24*1024L*1024L,4096,
+ 24*1024L*1024L, MALLOC_OVERHEAD,1024},
+ { "net_buffer_length",(long*) &net_buffer_length,16384,1024,24*1024*1024L,
+ MALLOC_OVERHEAD,1024},
+ { "select_limit", (long*) &select_limit, 1000L, 1, ~0L, 0, 1},
+ { "max_join_size", (long*) &max_join_size, 1000000L, 1, ~0L, 0, 1},
+ { 0, 0, 0, 0, 0, 0, 0}
+};
+
+
+static void usage(int version)
+{
+ printf("%s Ver 10.8 Distrib %s, for %s (%s)\n",
+ my_progname, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
+ if (version)
+ return;
+ puts("Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB");
+ puts("This software comes with ABSOLUTELY NO WARRANTY. This is free software,\nand you are welcome to modify and redistribute it under the GPL license\n");
+ printf("Usage: %s [OPTIONS] [database]\n", my_progname);
+ printf("\n\
+ -?, --help Display this help and exit\n\
+ -A, --no-auto-rehash No automatic rehashing. One has to use 'rehash' to\n\
+ get table and field completion. This gives a quicker\n\
+ start of mysql and disables rehashing on reconnect.\n\
+ -B, --batch Print results with a tab as separator, each row on\n\
+ a new line. Doesn't use history file\n\
+ --character-sets-dir=...\n\
+ Directory where character sets are\n\
+ -C, --compress Use compression in server/client protocol\n");
+#ifndef DBUG_OFF
+ printf("\
+ -#, --debug[=...] Debug log. Default is '%s'\n",default_dbug_option);
+#endif
+ printf("\
+ -D, --database=.. Database to use\n\
+ --default-character-set=...\n\
+ Set the default character set\n\
+ -e, --execute=... Execute command and quit.(Output like with --batch)\n\
+ -E, --vertical Print the output of a query (rows) vertically\n\
+ -f, --force Continue even if we get an sql error.\n\
+ -g, --no-named-commands\n\
+ Named commands are disabled. Use \\* form only\n\
+ -i, --ignore-space Ignore space after function names\n\
+ -h, --host=... Connect to host\n\
+ -H, --html Produce HTML output\n\
+ -L, --skip-line-numbers Don't write line number for errors\n\
+ -n, --unbuffered Flush buffer after each query\n\
+ -N, --skip-column-names Don't write column names in results\n\
+ -O, --set-variable var=option\n\
+ Give a variable an value. --help lists variables\n\
+ -o, --one-database Only update the default database. This is useful\n\
+ for skipping updates to other database in the update\n\
+ log.\n\
+ -p[password], --password[=...]\n\
+ Password to use when connecting to server\n\
+ If password is not given it's asked from the tty.\n");
+#ifdef __WIN__
+ puts(" -W, --pipe Use named pipes to connect to server");
+#endif
+ printf("\n\
+ -P --port=... Port number to use for connection\n\
+ -q, --quick Don't cache result, print it row by row. This may\n\
+ slow down the server if the output is suspended.\n\
+ Doesn't use history file\n\
+ -r, --raw Write fields without conversion. Used with --batch\n\
+ -s, --silent Be more silent.\n\
+ -S --socket=... Socket file to use for connection\n");
+#include "sslopt-usage.h"
+ printf("\
+ -t --table Output in table format\n\
+ -T, --debug-info Print some debug info at exit\n");
+#ifndef DONT_ALLOW_USER_CHANGE
+ printf("\
+ -u, --user=# User for login if not current user\n");
+#endif
+ printf("\
+ -U, --safe-updates[=#], --i-am-a-dummy[=#]\n\
+ Only allow UPDATE and DELETE that uses keys\n\
+ -v, --verbose Write more (-v -v -v gives the table output format)\n\
+ -V, --version Output version information and exit\n\
+ -w, --wait Wait and retry if connection is down\n");
+ print_defaults("my",load_default_groups);
+
+ printf("\nPossible variables for option --set-variable (-O) are:\n");
+ for (uint i=0 ; changeable_vars[i].name ; i++)
+ printf("%-20s current value: %lu\n",
+ changeable_vars[i].name,
+ (ulong) *changeable_vars[i].varptr);
+}
+
+
+static int get_options(int argc, char **argv)
+{
+ int c,option_index=0;
+ bool tty_password=0;
+
+ set_all_changeable_vars(changeable_vars);
+ while ((c=getopt_long(argc,argv,"?ABCD:LfgHinNoqrstTUvVwWEe:h:O:P:S:u:#::p::",
+ long_options, &option_index)) != EOF)
+ {
+ switch(c) {
+ case OPT_DEFAULT_CHARSET:
+ default_charset= optarg;
+ break;
+ case OPT_CHARSETS_DIR:
+ charsets_dir= optarg;
+ break;
+ case 'D':
+ my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
+ current_db=my_strdup(optarg,MYF(MY_WME));
+ break;
+ case 'e':
+ status.batch=1;
+ status.add_to_history=0;
+ batch_readline_end(status.line_buff); // If multiple -e
+ if (!(status.line_buff=batch_readline_command(optarg)))
+ return 1;
+ ignore_errors=0;
+ break;
+ case 'f':
+ ignore_errors=1;
+ break;
+ case 'h':
+ my_free(current_host,MYF(MY_ALLOW_ZERO_PTR));
+ current_host=my_strdup(optarg,MYF(MY_WME));
+ break;
+#ifndef DONT_ALLOW_USER_CHANGE
+ case 'u':
+ my_free(current_user,MYF(MY_ALLOW_ZERO_PTR));
+ current_user= my_strdup(optarg,MYF(MY_WME));
+ break;
+#endif
+ case 'U':
+ if (!optarg)
+ safe_updates=1;
+ else
+ safe_updates=atoi(optarg) != 0;
+ break;
+ case 'o':
+ one_database=skip_updates=1;
+ break;
+ case 'O':
+ if (set_changeable_var(optarg, changeable_vars))
+ {
+ usage(0);
+ return(1);
+ }
+ break;
+ case 'p':
+ if (optarg)
+ {
+ my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR));
+ opt_password=my_strdup(optarg,MYF(MY_FAE));
+ while (*optarg) *optarg++= 'x'; // Destroy argument
+ }
+ else
+ tty_password=1;
+ break;
+ case 't':
+ output_tables=1;
+ break;
+ case 'r':
+ opt_raw_data=1;
+ break;
+ case '#':
+ DBUG_PUSH(optarg ? optarg : default_dbug_option);
+ info_flag=1;
+ break;
+ case 'q': quick=1; break;
+ case 's': opt_silent++; break;
+ case 'T': info_flag=1; break;
+ case 'n': unbuffered=1; break;
+ case 'v': verbose++; break;
+ case 'E': vertical=1; break;
+ case 'w': wait_flag=1; break;
+ case 'A': no_rehash=1; break;
+ case 'g': no_named_cmds=1; break;
+ case 'H': opt_html=1; break;
+ case 'i': connect_flag|= CLIENT_IGNORE_SPACE; break;
+ case 'B':
+ if (!status.batch)
+ {
+ status.batch=1;
+ status.add_to_history=0;
+ opt_silent++; // more silent
+ }
+ break;
+ case 'C':
+ opt_compress=1;
+ break;
+ case 'L':
+ skip_line_numbers=1;
+ break;
+ case 'N':
+ skip_column_names=1;
+ break;
+ case 'P':
+ opt_mysql_port= (unsigned int) atoi(optarg);
+ break;
+ case 'S':
+ my_free(opt_mysql_unix_port,MYF(MY_ALLOW_ZERO_PTR));
+ opt_mysql_unix_port= my_strdup(optarg,MYF(0));
+ break;
+ case 'W':
+#ifdef __WIN__
+ opt_mysql_unix_port=MYSQL_NAMEDPIPE;
+#endif
+ break;
+ case 'V': usage(1); exit(0);
+ case 'I':
+ case '?':
+ usage(0);
+ exit(0);
+#include "sslopt-case.h"
+ default:
+ fprintf(stderr,"illegal option: -%c\n",opterr);
+ usage(0);
+ exit(1);
+ }
+ }
+ if (default_charset)
+ {
+ if (set_default_charset_by_name(default_charset, MYF(MY_WME)))
+ exit(1);
+ }
+ argc-=optind;
+ argv+=optind;
+ if (argc > 1)
+ {
+ usage(0);
+ exit(1);
+ }
+ if (argc == 1)
+ {
+ my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
+ current_db= my_strdup(*argv,MYF(MY_WME));
+ }
+ if (!current_host)
+ { /* If we don't have a hostname have a look at MYSQL_HOST */
+ char *tmp=(char *) getenv("MYSQL_HOST");
+ if (tmp)
+ current_host = my_strdup(tmp,MYF(MY_WME));
+ }
+ if (tty_password)
+ opt_password=get_tty_password(NullS);
+ return(0);
+}
+
+
+static int read_lines(bool execute_commands)
+{
+#ifdef __WIN__
+ char linebuffer[254];
+#endif
+ char *line;
+ char in_string=0;
+ ulong line_number=0;
+ COMMANDS *com;
+ status.exit_status=1;
+
+ for (;;)
+ {
+ if (status.batch || !execute_commands)
+ {
+ line=batch_readline(status.line_buff);
+ line_number++;
+ if (!glob_buffer.length())
+ status.query_start_line=line_number;
+ }
+ else
+#ifdef __WIN__
+ {
+ printf(glob_buffer.is_empty() ? "mysql> " :
+ !in_string ? " -> " :
+ in_string == '\'' ?
+ " '> " : " \"> ");
+ linebuffer[0]=(char) sizeof(linebuffer);
+ line=_cgets(linebuffer);
+ }
+#else
+ line=readline((char*) (glob_buffer.is_empty() ? "mysql> " :
+ !in_string ? " -> " :
+ in_string == '\'' ?
+ " '> " : " \"> "));
+#endif
+ if (!line) // End of file
+ {
+ status.exit_status=0;
+ break;
+ }
+ if (!in_string && (line[0] == '#' ||
+ (line[0] == '-' && line[1] == '-') ||
+ line[0] == 0))
+ continue; // Skipp comment lines
+
+ /* Check if line is a mysql command line */
+ /* (We want to allow help, print and clear anywhere at line start */
+ if (execute_commands && !no_named_cmds && !in_string &&
+ (com=find_command(line,0)))
+ {
+ if ((*com->func)(&glob_buffer,line) > 0)
+ break;
+ if (glob_buffer.is_empty()) // If buffer was emptied
+ in_string=0;
+#ifdef HAVE_READLINE
+ if (status.add_to_history)
+ add_history(line);
+#endif
+ continue;
+ }
+ if (add_line(glob_buffer,line,&in_string))
+ break;
+ }
+ /* if in batch mode, send last query even if it doesn't end with \g or go */
+
+ if ((status.batch || !execute_commands) && !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;
+ }
+ }
+ return status.exit_status;
+}
+
+
+static COMMANDS *find_command (char *name,char cmd_char)
+{
+ uint len;
+ char *end;
+
+ if (!name)
+ {
+ len=0;
+ end=0;
+ }
+ else
+ {
+ while (isspace(*name))
+ name++;
+ if (strchr(name,';') || strstr(name,"\\g"))
+ return ((COMMANDS *) 0);
+ if ((end=strcont(name," \t")))
+ {
+ len=(uint) (end - name);
+ while (isspace(*end))
+ end++;
+ if (!*end)
+ end=0; // no arguments to function
+ }
+ else
+ len=strlen(name);
+ }
+
+ for (uint i= 0; commands[i].name; i++)
+ {
+ if (commands[i].func &&
+ ((name && !my_casecmp(name,commands[i].name,len) &&
+ !commands[i].name[len] &&
+ (!end || (end && commands[i].takes_params))) ||
+ !name && commands[i].cmd_char == cmd_char))
+ return (&commands[i]);
+ }
+ return ((COMMANDS *) 0);
+}
+
+
+static bool add_line(String &buffer,char *line,char *in_string)
+{
+ uchar inchar;
+ char buff[80],*pos,*out;
+ COMMANDS *com;
+
+ if (!line[0] && buffer.is_empty())
+ return 0;
+#ifdef HAVE_READLINE
+ if (status.add_to_history && line[0])
+ add_history(line);
+#endif
+#ifdef USE_MB
+ char *strend=line+strlen(line);
+#endif
+
+ for (pos=out=line ; (inchar= (uchar) *pos) ; pos++)
+ {
+ if (isspace(inchar) && out == line && buffer.is_empty())
+ continue;
+#ifdef USE_MB
+ int l;
+/* if ((l = ismbchar(pos, pos+MBMAXLEN))) { Wei He: I think it's wrong! */
+ if (use_mb(default_charset_info) &&
+ (l = my_ismbchar(default_charset_info, pos, strend))) {
+ while (l--)
+ *out++ = *pos++;
+ pos--;
+ continue;
+ }
+#endif
+ if (inchar == '\\')
+ { // mSQL or postgreSQL style command ?
+ if (!(inchar = (uchar) *++pos))
+ break; // readline adds one '\'
+ if (*in_string || inchar == 'N')
+ { // Don't allow commands in string
+ *out++='\\';
+ *out++= (char) inchar;
+ continue;
+ }
+ if ((com=find_command(NullS,(char) inchar)))
+ {
+ const String tmp(line,(uint) (out-line));
+ buffer.append(tmp);
+ if ((*com->func)(&buffer,pos-1) > 0)
+ return 1; // Quit
+ if (com->takes_params)
+ {
+ for (pos++ ; *pos && *pos != ';' ; pos++) ; // Remove parameters
+ if (!*pos)
+ pos--;
+ }
+ out=line;
+ }
+ else
+ {
+ sprintf(buff,"Unknown command '\\%c'.",inchar);
+ if (put_info(buff,INFO_ERROR) > 0)
+ return 1;
+ *out++='\\';
+ *out++=(char) inchar;
+ continue;
+ }
+ }
+ else if (inchar == ';' && !*in_string)
+ { // ';' is end of command
+ if (out != line)
+ buffer.append(line,(uint) (out-line)); // Add this line
+ if ((com=find_command(buffer.c_ptr(),0)))
+ {
+ if ((*com->func)(&buffer,buffer.c_ptr()) > 0)
+ return 1; // Quit
+ }
+ else
+ {
+ int error=com_go(&buffer,0);
+ if (error)
+ {
+ return error < 0 ? 0 : 1; // < 0 is not fatal
+ }
+ }
+ buffer.length(0);
+ out=line;
+ }
+ else if (!*in_string && (inchar == '#' ||
+ inchar == '-' && pos[1] == '-' &&
+ isspace(pos[2])))
+ break; // comment to end of line
+ else
+ { // Add found char to buffer
+ if (inchar == *in_string)
+ *in_string=0;
+ else if (!*in_string && (inchar == '\'' || inchar == '"'))
+ *in_string=(char) inchar;
+ *out++ = (char) inchar;
+ }
+ }
+ if (out != line || !buffer.is_empty())
+ {
+ *out++='\n';
+ uint length=(uint) (out-line);
+ if (buffer.length() + length >= buffer.alloced_length())
+ buffer.realloc(buffer.length()+length+IO_SIZE);
+ if (buffer.append(line,length))
+ return 1;
+ }
+ return 0;
+}
+
+/* **************************************************************** */
+/* */
+/* Interface to Readline Completion */
+/* */
+/* **************************************************************** */
+
+#ifdef HAVE_READLINE
+
+static char *new_command_generator(char *text, int);
+static char **new_mysql_completion (char *text, int start, int 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. */
+
+char **no_completion (char *text __attribute__ ((unused)),
+ char *word __attribute__ ((unused)))
+{
+ return 0; /* No filename completion */
+}
+
+static void initialize_readline (char *name)
+{
+ /* Allow conditional parsing of the ~/.inputrc file. */
+ rl_readline_name = name;
+
+ /* Tell the completer that we want a crack first. */
+ /* rl_attempted_completion_function = (CPPFunction *)mysql_completion;*/
+ rl_attempted_completion_function = (CPPFunction *) new_mysql_completion;
+ rl_completion_entry_function=(Function *) no_completion;
+}
+
+/* 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 (char *text,
+ int start __attribute__((unused)),
+ int end __attribute__((unused)))
+{
+ if (!status.batch && !quick)
+ return completion_matches(text, (CPFunction*) new_command_generator);
+ else
+ return (char**) 0;
+}
+
+static char *new_command_generator(char *text,int state)
+{
+ static int textlen;
+ char *ptr;
+ static Bucket *b;
+ static entry *e;
+ static uint i;
+
+ if (!state) {
+ textlen=strlen(text);
+ }
+
+ if (textlen>0) { /* lookup in the hash */
+ if (!state) {
+ uint len;
+
+ b = find_all_matches(&ht,text,strlen(text),&len);
+ if (!b) {
+ return NullS;
+ }
+ e = b->pData;
+ }
+
+ while (e) {
+ ptr= strdup(e->str);
+ e = e->pNext;
+ return ptr;
+ }
+ } else { /* traverse the entire hash, ugly but works */
+
+ if (!state) {
+ i=0;
+ /* find the first used bucket */
+ while (i<ht.nTableSize) {
+ if (ht.arBuckets[i]) {
+ b = ht.arBuckets[i];
+ e = b->pData;
+ break;
+ }
+ i++;
+ }
+ }
+ ptr= NullS;
+ while (e && !ptr) { /* find valid entry in bucket */
+ if (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) {
+ i++;
+ while (i<ht.nTableSize) {
+ if (ht.arBuckets[i]) {
+ b = ht.arBuckets[i];
+ e = b->pData;
+ break;
+ }
+ i++;
+ }
+ } else {
+ e = b->pData;
+ }
+ }
+ }
+ if (ptr) {
+ return ptr;
+ }
+ }
+ return NullS;
+}
+
+
+/* Build up the completion hash */
+
+static void build_completion_hash(bool skip_rehash,bool write_info)
+{
+ COMMANDS *cmd=commands;
+ static MYSQL_RES *databases=0,*tables=0,*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)
+ DBUG_VOID_RETURN; // We don't need completion in batches
+
+ completion_hash_clean(&ht);
+ if (tables)
+ {
+ mysql_free_result(tables);
+ tables=0;
+ }
+ if (databases) {
+ mysql_free_result(databases);
+ databases=0;
+ }
+
+ /* hash SQL commands */
+ while (cmd->name) {
+ add_word(&ht,(char*) cmd->name);
+ cmd++;
+ }
+ if (skip_rehash)
+ DBUG_VOID_RETURN;
+
+ /* 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)))
+ add_word(&ht,(char*) database_row[0]);
+ }
+ }
+ /* 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)
+ {
+ printf("\
+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)))
+ {
+ if (!completion_hash_exists(&ht,(char*) table_row[0],
+ strlen((const char*) table_row[0])))
+ add_word(&ht,table_row[0]);
+ }
+ }
+ }
+ if (field_names) {
+ for (i=0; field_names[i]; i++) {
+ for (j=0; field_names[i][j]; j++) {
+ my_free(field_names[i][j],MYF(0));
+ }
+ my_free((gptr) field_names[i],MYF(0));
+ }
+ my_free((gptr) field_names,MYF(0));
+ }
+ field_names=0;
+
+ /* hash all field names, both with the table prefix and without it */
+ if (!tables) { /* no tables */
+ DBUG_VOID_RETURN;
+ }
+ mysql_data_seek(tables,0);
+ field_names = (char ***) my_malloc(sizeof(char **) *
+ (uint) (mysql_num_rows(tables)+1),
+ MYF(MY_WME));
+ if (!field_names)
+ DBUG_VOID_RETURN;
+ field_names[mysql_num_rows(tables)]='\0';
+ 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);
+ field_names[i] = (char **) my_malloc(sizeof(char *)*(num_fields*2+1),
+ MYF(0));
+ if (!field_names[i])
+ {
+ continue;
+ }
+ field_names[i][num_fields*2]='\0';
+ j=0;
+ while ((sql_field=mysql_fetch_field(fields)))
+ {
+ sprintf(buf,"%s.%s",table_row[0],sql_field->name);
+ field_names[i][j] = my_strdup(buf,MYF(0));
+ add_word(&ht,field_names[i][j]);
+ field_names[i][num_fields+j] = my_strdup(sql_field->name,MYF(0));
+ if (!completion_hash_exists(&ht,field_names[i][num_fields+j],
+ strlen(field_names[i][num_fields+j])))
+ add_word(&ht,field_names[i][num_fields+j]);
+ j++;
+ }
+ }
+ else
+ printf("Didn't find any fields in table '%s'\n",table_row[0]);
+ i++;
+ }
+ DBUG_VOID_RETURN;
+}
+
+
+ /* for gnu readline */
+
+#ifndef HAVE_INDEX
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern char *index(const char *,pchar c),*rindex(const char *,pchar);
+
+char *index(const char *s,pchar c)
+{
+ for (;;)
+ {
+ if (*s == (char) c) return (char*) s;
+ if (!*s++) return NullS;
+ }
+}
+
+char *rindex(const char *s,pchar c)
+{
+ reg3 char *t;
+
+ t = NullS;
+ do if (*s == (char) c) t = (char*) s; while (*s++);
+ return (char*) t;
+}
+#ifdef __cplusplus
+}
+#endif
+#endif
+#endif /* HAVE_READLINE */
+
+static int reconnect(void)
+{
+ if (!status.batch)
+ {
+ put_info("No connection. Trying to reconnect...",INFO_INFO);
+ (void) com_connect((String *) 0, 0);
+ if(!no_rehash) com_rehash(NULL, NULL);
+ }
+ if (!connected)
+ return put_info("Can't connect to the server\n",INFO_ERROR);
+ return 0;
+}
+
+
+/***************************************************************************
+ The different commands
+***************************************************************************/
+
+static int
+com_help (String *buffer __attribute__((unused)),
+ char *line __attribute__((unused)))
+{
+ reg1 int i;
+
+ put_info("\nMySQL commands:",INFO_INFO);
+ for (i = 0; commands[i].name; i++)
+ {
+ if (commands[i].func)
+ printf("%s\t(\\%c)\t%s\n", commands[i].name,commands[i].cmd_char,
+ commands[i].doc);
+ }
+ if (connected)
+ printf("\nConnection id: %ld (Can be used with mysqladmin kill)\n\n",
+ mysql_thread_id(&mysql));
+ else
+ printf("Not connected! Reconnect with 'connect'!\n\n");
+ return 0;
+}
+
+
+ /* ARGSUSED */
+static int
+com_clear(String *buffer,char *line __attribute__((unused)))
+{
+ buffer->length(0);
+ 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[160],time_buff[32];
+ MYSQL_RES *result;
+ ulong timer;
+ uint error=0;
+
+ if (!status.batch)
+ {
+ old_buffer= *buffer; // Save for edit command
+ old_buffer.copy();
+ }
+
+ /* Remove garbage for nicer messages */
+ LINT_INIT(buff[0]);
+ remove_cntrl(*buffer);
+
+ if (buffer->is_empty())
+ {
+ if (status.batch) // Ignore empty quries
+ return 0;
+ return put_info("No query specified\n",INFO_ERROR);
+
+ }
+ if (!connected && reconnect())
+ {
+ buffer->length(0); // Remove query on error
+ return status.batch ? 1 : -1; // Fatal error
+ }
+ if (verbose)
+ (void) com_print(buffer,0);
+
+ if (skip_updates &&
+ (buffer->length() < 4 || my_sortcmp(buffer->ptr(),"SET ",4)))
+ {
+ (void) put_info("Ignoring query to other database",INFO_INFO);
+ return 0;
+ }
+
+ timer=start_timer();
+ for (uint retry=0;; retry++)
+ {
+ if (!mysql_real_query(&mysql,buffer->ptr(),buffer->length()))
+ break;
+ error=put_info(mysql_error(&mysql),INFO_ERROR, mysql_errno(&mysql));
+ if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || status.batch)
+ {
+ buffer->length(0); // Remove query on error
+ return error;
+ }
+ if (reconnect())
+ {
+ buffer->length(0); // Remove query on error
+ return error;
+ }
+ }
+ error=0;
+ buffer->length(0);
+
+ if (quick)
+ {
+ if (!(result=mysql_use_result(&mysql)) && mysql_field_count(&mysql))
+ {
+ return put_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+ }
+ }
+ else
+ {
+ if (!(result=mysql_store_result(&mysql)))
+ {
+ if (mysql_error(&mysql)[0])
+ {
+ return put_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+ }
+ }
+ }
+
+ if (verbose >= 3 || !opt_silent)
+ mysql_end_timer(timer,time_buff);
+ else
+ time_buff[0]=0;
+ if (result)
+ {
+ if (!mysql_num_rows(result) && ! quick)
+ {
+ sprintf(buff,"Empty set%s",time_buff);
+ }
+ else
+ {
+ if (opt_html)
+ print_table_data_html(result);
+ else if (vertical)
+ 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%s",
+ (long) mysql_num_rows(result),
+ (long) mysql_num_rows(result) == 1 ? "row" : "rows",
+ time_buff);
+ }
+ }
+ else if (mysql_affected_rows(&mysql) == ~(ulonglong) 0)
+ sprintf(buff,"Query OK%s",time_buff);
+ else
+ sprintf(buff,"Query OK, %ld %s affected%s",
+ (long) mysql_affected_rows(&mysql),
+ (long) mysql_affected_rows(&mysql) == 1 ? "row" : "rows",
+ 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_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+ else if (unbuffered)
+ fflush(stdout);
+ mysql_free_result(result);
+ return error; /* New command follows */
+}
+
+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 void
+print_table_data(MYSQL_RES *result)
+{
+ String separator(256);
+ MYSQL_ROW cur;
+ MYSQL_FIELD *field;
+ bool *num_flag;
+
+ num_flag=(bool*) my_alloca(sizeof(bool)*mysql_num_fields(result));
+ separator.copy("+",1);
+ while ((field = mysql_fetch_field(result)))
+ {
+ uint length=skip_column_names ? 0 : strlen(field->name);
+ if (quick)
+ length=max(length,field->length);
+ else
+ length=max(length,field->max_length);
+ if (length < 4 && !IS_NOT_NULL(field->flags))
+ length=4; // Room for "NULL"
+ field->max_length=length+1;
+ separator.fill(separator.length()+length+2,'-');
+ separator.append('+');
+ }
+ puts(separator.c_ptr());
+
+ if (!skip_column_names)
+ {
+ mysql_field_seek(result,0);
+ (void) fputs("|",stdout);
+ for (uint off=0; (field = mysql_fetch_field(result)) ; off++)
+ {
+ printf(" %-*s|",field->max_length,field->name);
+ num_flag[off]= IS_NUM(field->type);
+ }
+ (void) fputc('\n',stdout);
+ puts(separator.c_ptr());
+ }
+
+ while ((cur = mysql_fetch_row(result)))
+ {
+ (void) fputs("|",stdout);
+ mysql_field_seek(result,0);
+ for (uint off=0 ; off < mysql_num_fields(result); off++)
+ {
+ field = mysql_fetch_field(result);
+ uint length=field->max_length;
+ printf(num_flag[off] ? "%*s |" : " %-*s|",
+ length,cur[off] ? (char*) cur[off] : "NULL");
+ }
+ (void) fputc('\n',stdout);
+ }
+ puts(separator.c_ptr());
+ my_afree((gptr) num_flag);
+}
+
+static void
+print_table_data_html(MYSQL_RES *result)
+{
+ MYSQL_ROW cur;
+ MYSQL_FIELD *field;
+
+ mysql_field_seek(result,0);
+ printf("<TABLE BORDER=1>\n");
+ printf("<TR>\n");
+ if (!skip_column_names)
+ {
+ while((field = mysql_fetch_field(result)))
+ {
+ printf("<TH>%s</TH>",field->name ? (field->name[0] ? field->name:" &nbsp; "):"NULL");
+ }
+ puts("\n</TR>");
+ }
+ while ((cur = mysql_fetch_row(result)))
+ {
+ puts("<TR>");
+ for (uint i=0; i < mysql_num_fields(result); i++)
+ {
+ ulong *lengths=mysql_fetch_lengths(result);
+ fputs("<TD>",stdout);
+ safe_put_field(cur[i],lengths[i]);
+ fputs("</TD>",stdout);
+ }
+ puts("\n</TR>");
+ }
+ puts("</TABLE>");
+}
+
+
+
+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=strlen(field->name);
+ 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++)
+ {
+ mysql_field_seek(result,0);
+ printf("*************************** %d. row ***************************\n",
+ row_count);
+ for (uint off=0; off < mysql_num_fields(result); off++)
+ {
+ field= mysql_fetch_field(result);
+ printf("%*s: ",(int) max_length,field->name);
+ printf("%s\n",cur[off] ? (char*) cur[off] : "NULL");
+ }
+ }
+}
+
+
+static void
+safe_put_field(const char *pos,ulong length)
+{
+ if (!pos)
+ fputs("NULL",stdout);
+ else
+ {
+ if (opt_raw_data)
+ fputs(pos,stdout);
+ else for (const char *end=pos+length ; pos != end ; pos++)
+ {
+#ifdef USE_MB
+ int l;
+ if (use_mb(default_charset_info) &&
+ (l = my_ismbchar(default_charset_info, pos, end))) {
+ while (l--)
+ putchar(*pos++);
+ pos--;
+ continue;
+ }
+#endif
+ if (!*pos)
+ fputs("\\0",stdout); // This makes everything hard
+ else if (*pos == '\t')
+ fputs("\\t",stdout); // This would destroy tab format
+ else if (*pos == '\n')
+ fputs("\\n",stdout); // This too
+ else if (*pos == '\\')
+ fputs("\\\\",stdout);
+ else
+ putchar(*pos);
+ }
+ }
+}
+
+
+static void
+print_tab_data(MYSQL_RES *result)
+{
+ MYSQL_ROW cur;
+ MYSQL_FIELD *field;
+ ulong *lengths;
+
+ if (opt_silent < 2 && !skip_column_names)
+ {
+ int first=0;
+ while ((field = mysql_fetch_field(result)))
+ {
+ if (first++)
+ (void) fputc('\t',stdout);
+ (void) fputs(field->name,stdout);
+ }
+ (void) fputc('\n',stdout);
+ }
+ while ((cur = mysql_fetch_row(result)))
+ {
+ lengths=mysql_fetch_lengths(result);
+ safe_put_field(cur[0],lengths[0]);
+ for (uint off=1 ; off < mysql_num_fields(result); off++)
+ {
+ (void) fputc('\t',stdout);
+ safe_put_field(cur[off],lengths[off]);
+ }
+ (void) fputc('\n',stdout);
+ }
+}
+
+
+static int
+com_edit(String *buffer,char *line __attribute__((unused)))
+{
+#ifdef __WIN__
+ put_info("Sorry, you can't send the result to an editor in Win32",
+ INFO_ERROR);
+#else
+ char *filename,buff[160];
+ int fd,tmp;
+ const char *editor;
+
+ filename = my_tempnam(NullS,"sql",MYF(MY_WME));
+ if ((fd = my_create(filename,0,O_CREAT | O_WRONLY, MYF(MY_WME))) < 0)
+ goto err;
+ if (buffer->is_empty() && !old_buffer.is_empty())
+ (void) my_write(fd,(byte*) old_buffer.ptr(),old_buffer.length(),
+ MYF(MY_WME));
+ else
+ (void) my_write(fd,(byte*) buffer->ptr(),buffer->length(),MYF(MY_WME));
+ (void) my_close(fd,MYF(0));
+
+ if (!(editor = (char *)getenv("EDITOR")) &&
+ !(editor = (char *)getenv("VISUAL")))
+ editor = "vi";
+ strxmov(buff,editor," ",filename,NullS);
+ (void) system(buff);
+
+ MY_STAT stat_arg;
+ 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=read(fd,(char*) buffer->ptr(),buffer->alloced_length())) >= 0L)
+ buffer->length((uint) tmp);
+ else
+ buffer->length(0);
+ (void) my_close(fd,MYF(0));
+ (void) my_delete(filename,MYF(MY_WME));
+err:
+ free(filename);
+#endif
+ return 0;
+}
+
+/* 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(0,0);
+#endif
+ return 0;
+}
+
+static int
+com_print(String *buffer,char *line __attribute__((unused)))
+{
+ puts("--------------");
+ (void) fputs(buffer->c_ptr(),stdout);
+ if (!buffer->length() || (*buffer)[buffer->length()-1] != '\n')
+ putchar('\n');
+ puts("--------------\n");
+ return 0; /* If empty buffer */
+}
+
+ /* ARGSUSED */
+static int
+com_connect(String *buffer, char *line)
+{
+ char *tmp,buff[256];
+ bool save_rehash=no_rehash;
+ int error;
+
+ if (buffer)
+ {
+ while (isspace(*line))
+ line++;
+ strnmov(buff,line,sizeof(buff)-1); // Don't destroy history
+ if (buff[0] == '\\') // Short command
+ buff[1]=' ';
+ tmp=(char *) strtok(buff," \t"); // Skipp connect command
+ if (tmp && (tmp=(char *) strtok(NullS," \t;")))
+ {
+ my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
+ current_db=my_strdup(tmp,MYF(MY_WME));
+ if ((tmp=(char *) strtok(NullS," \t;")))
+ {
+ my_free(current_host,MYF(MY_ALLOW_ZERO_PTR));
+ current_host=my_strdup(tmp,MYF(MY_WME));
+ }
+ }
+ else
+ no_rehash=1; // Quick re-connect
+ buffer->length(0); // command used
+ }
+ else
+ no_rehash=1;
+ error=sql_connect(current_host,current_db,current_user,opt_password,0);
+ no_rehash=save_rehash;
+
+ if (connected)
+ {
+ sprintf(buff,"Connection id: %ld",mysql_thread_id(&mysql));
+ put_info(buff,INFO_INFO);
+ sprintf(buff,"Current database: %s\n",
+ current_db ? current_db : "*** NO ONE ***");
+ put_info(buff,INFO_INFO);
+ }
+ return error;
+}
+
+
+static int com_source(String *buffer, char *line)
+{
+ char source_name[FN_REFLEN], *end, *param;
+ LINE_BUFFER *line_buff;
+ int error;
+ STATUS old_status;
+ FILE *sql_file;
+
+ /* Skip space from file name */
+ while (isspace(*line))
+ line++;
+ if (!(param = strchr(line, ' '))) // Skipp command name
+ return put_info("Usage: \\. <filename> | source <filename>", INFO_ERROR, 0);
+ while (isspace(*param))
+ param++;
+ end=strmake(source_name,param,sizeof(source_name)-1);
+ while (end > source_name && (isspace(end[-1]) || iscntrl(end[-1])))
+ end--;
+ end[0]=0;
+ /* open file name */
+ if (!(sql_file = my_fopen(source_name, O_RDONLY,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_allowed_packet+512,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;
+ 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
+ error=read_lines(0); // Read lines from file
+ status=old_status; // Continue as before
+ my_fclose(sql_file,MYF(0));
+ batch_readline_end(line_buff);
+ return error;
+}
+
+
+ /* ARGSUSED */
+static int
+com_use(String *buffer __attribute__((unused)), char *line)
+{
+ char *tmp;
+ char buff[256];
+
+ while (isspace(*line))
+ line++;
+ strnmov(buff,line,sizeof(buff)-1); // Don't destroy history
+ if (buff[0] == '\\') // Short command
+ buff[1]=' ';
+ tmp=(char *) strtok(buff," \t;"); // Skipp connect command
+ if (!tmp || !(tmp=(char *) strtok(NullS," \t;")))
+ {
+ put_info("USE must be followed by a database name",INFO_ERROR);
+ return 0;
+ }
+ if (!current_db || cmp_database(current_db,tmp))
+ {
+ if (one_database)
+ skip_updates=1;
+ else
+ {
+ /*
+ reconnect once if connection is down or if connection was found to
+ be down during query
+ */
+ if (!connected && reconnect())
+ return status.batch ? 1 : -1; // Fatal error
+ if (mysql_select_db(&mysql,tmp))
+ {
+ if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR)
+ return put_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+
+ if (reconnect())
+ return status.batch ? 1 : -1; // Fatal error
+ if (mysql_select_db(&mysql,tmp))
+ return put_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+ }
+#ifdef HAVE_READLINE
+ build_completion_hash(no_rehash,1);
+#endif
+ my_free(current_db,MYF(MY_ALLOW_ZERO_PTR));
+ current_db=my_strdup(tmp,MYF(MY_WME));
+ }
+ }
+ else
+ skip_updates=0;
+ put_info("Database changed",INFO_INFO);
+ return 0;
+}
+
+
+static int
+sql_real_connect(char *host,char *database,char *user,char *password,
+ uint silent)
+{
+ if (connected)
+ { /* if old is open, close it first */
+ mysql_close(&mysql);
+ connected= 0;
+ }
+ mysql_init(&mysql);
+ if (opt_compress)
+ mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS);
+#ifdef HAVE_OPENSSL
+ if (opt_use_ssl)
+ mysql_ssl_set(&mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca,
+ opt_ssl_capath);
+#endif
+ if (safe_updates)
+ {
+ char init_command[100];
+ sprintf(init_command,
+ "SET SQL_SAFE_UPDATES=1,SQL_SELECT_LIMIT=%lu,SQL_MAX_JOIN_SIZE=%lu",
+ select_limit,max_join_size);
+ mysql_options(&mysql, MYSQL_INIT_COMMAND, init_command);
+ }
+ if (!mysql_real_connect(&mysql,host,user,password,
+ database,opt_mysql_port,opt_mysql_unix_port,
+ connect_flag))
+ {
+ if (!silent ||
+ (mysql_errno(&mysql) != CR_CONN_HOST_ERROR &&
+ mysql_errno(&mysql) != CR_CONNECTION_ERROR))
+ {
+ put_info(mysql_error(&mysql),INFO_ERROR,mysql_errno(&mysql));
+ (void) fflush(stdout);
+ return ignore_errors ? -1 : 1; // Abort
+ }
+ return -1; // Retryable
+ }
+ connected=1;
+ mysql.reconnect=info_flag ? 1 : 0; // We want to know if this happens
+#ifdef HAVE_READLINE
+ build_completion_hash(no_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)
+ {
+ fputs("\n",stderr);
+ (void) fflush(stderr);
+ }
+ return error;
+ }
+ if (!wait_flag)
+ return ignore_errors ? -1 : 1;
+ if (!message && !silent)
+ {
+ message=1;
+ fputs("Waiting",stderr); (void) fflush(stderr);
+ }
+ (void) sleep(5);
+ if (!silent)
+ {
+ putc('.',stderr); (void) fflush(stderr);
+ count++;
+ }
+ }
+}
+
+
+
+static int
+com_status(String *buffer __attribute__((unused)),
+ char *line __attribute__((unused)))
+{
+ char *status;
+ puts("--------------");
+ usage(1); /* Print version */
+ if (connected)
+ {
+ MYSQL_RES *result;
+ LINT_INIT(result);
+ printf("\nConnection id:\t\t%ld\n",mysql_thread_id(&mysql));
+ if (!mysql_query(&mysql,"select DATABASE(),USER()") &&
+ (result=mysql_use_result(&mysql)))
+ {
+ MYSQL_ROW cur=mysql_fetch_row(result);
+ printf("Current database:\t%s\n",cur[0]);
+ printf("Current user:\t\t%s\n",cur[1]);
+ (void) mysql_fetch_row(result); // Read eof
+ }
+ }
+ else
+ {
+ vidattr(A_BOLD);
+ printf("\nNo connection\n");
+ vidattr(A_NORMAL);
+ return 0;
+ }
+ if (skip_updates)
+ {
+ vidattr(A_BOLD);
+ printf("\nAll updates ignored to this database\n");
+ vidattr(A_NORMAL);
+ }
+ printf("Server version\t\t%s\n", mysql_get_server_info(&mysql));
+ printf("Protocol version\t%d\n", mysql_get_proto_info(&mysql));
+ printf("Connection\t\t%s\n", mysql_get_host_info(&mysql));
+ printf("Language\t\t%s\n", mysql.charset->name);
+ if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket)
+ printf("TCP port\t\t%d\n", mysql.port);
+ else
+ printf("UNIX socket\t\t%s\n", mysql.unix_socket);
+ if ((status=mysql_stat(&mysql)) && !mysql_error(&mysql)[0])
+ {
+ char *pos,buff[40];
+ ulong sec;
+ pos=strchr(status,' ');
+ *pos++=0;
+ printf("%s\t\t\t",status); /* print label */
+ if ((status=str2int(pos,10,0,LONG_MAX,(long*) &sec)))
+ {
+ nice_time((double) sec,buff,0);
+ puts(buff); /* print nice time */
+ while (*status == ' ') status++; /* to next info */
+ }
+ if (status)
+ {
+ putchar('\n');
+ puts(status);
+ }
+ }
+ if (safe_updates)
+ {
+ vidattr(A_BOLD);
+ printf("\nNote that we are running in safe_update_mode:\n");
+ vidattr(A_NORMAL);
+ printf("\
+UPDATE and DELETE that doesn't use a key in the WHERE clause are not allowed\n\
+(One can force 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);
+ }
+ puts("--------------\n");
+ return 0;
+}
+
+
+static int
+put_info(const char *str,INFO_TYPE info_type,uint error)
+{
+ static int inited=0;
+
+ if (status.batch)
+ {
+ if (info_type == INFO_ERROR)
+ {
+ (void) fflush(stdout);
+ fprintf(stderr,"ERROR");
+ if (error)
+ (void) fprintf(stderr," %d",error);
+ if (status.query_start_line && ! skip_line_numbers)
+ {
+ (void) fprintf(stderr," at line %lu",status.query_start_line);
+ if (status.file_name)
+ (void) fprintf(stderr," in file: '%s'", status.file_name);
+ }
+ (void) fprintf(stderr,": %s\n",str);
+ (void) fflush(stderr);
+ if (!ignore_errors)
+ return 1;
+ }
+ else if (info_type == INFO_RESULT && verbose > 1)
+ puts(str);
+ if (unbuffered)
+ fflush(stdout);
+ return info_type == INFO_ERROR ? -1 : 0;
+ }
+ if (!opt_silent || info_type == INFO_ERROR)
+ {
+ if (!inited)
+ {
+ inited=1;
+#ifdef HAVE_SETUPTERM
+ (void) setupterm((char *)0, 1, (int *) 0);
+#endif
+ }
+ if (info_type == INFO_ERROR)
+ {
+ putchar('\007'); /* This should make a bell */
+ vidattr(A_STANDOUT);
+ if (error)
+ (void) fprintf(stderr,"ERROR %d: ",error);
+ else
+ fputs("ERROR: ",stdout);
+ }
+ else
+ vidattr(A_BOLD);
+ (void) puts(str);
+ vidattr(A_NORMAL);
+ }
+ if (unbuffered)
+ fflush(stdout);
+ return info_type == INFO_ERROR ? -1 : 0;
+}
+
+static void remove_cntrl(String &buffer)
+{
+ char *start,*end;
+ end=(start=(char*) buffer.ptr())+buffer.length();
+ while (start < end && !isgraph(end[-1]))
+ end--;
+ buffer.length((uint) (end-start));
+}
+
+
+#ifdef __WIN__
+#include <time.h>
+#else
+#include <sys/times.h>
+#undef CLOCKS_PER_SEC
+#define CLOCKS_PER_SEC (sysconf(_SC_CLK_TCK))
+#endif
+
+static ulong start_timer(void)
+{
+#ifdef __WIN__
+ return clock();
+#else
+ struct tms tms_tmp;
+ return times(&tms_tmp);
+#endif
+}
+
+
+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=int2str((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=int2str((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=int2str((long) tmp,buff,10);
+ buff=strmov(buff," min ");
+ }
+ if (part_second)
+ sprintf(buff,"%.2f sec",sec);
+ else
+ sprintf(buff,"%d sec",(int) sec);
+}
+
+
+static void end_timer(ulong start_time,char *buff)
+{
+ nice_time((double) (start_timer() - start_time) /
+ CLOCKS_PER_SEC,buff,1);
+}
+
+
+static void mysql_end_timer(ulong start_time,char *buff)
+{
+ buff[0]=' ';
+ buff[1]='(';
+ end_timer(start_time,buff+2);
+ strmov(strend(buff),")");
+}
+
+/* Keep sql_string library happy */
+
+gptr sql_alloc(unsigned int Size)
+{
+ return my_malloc(Size,MYF(MY_WME));
+}
+
+void sql_element_free(void *ptr)
+{
+ my_free((gptr) ptr,MYF(0));
+}