diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/Makefile.am | 46 | ||||
-rw-r--r-- | client/client_priv.h | 9 | ||||
-rw-r--r-- | client/mysql.cc | 504 | ||||
-rw-r--r-- | client/mysql_upgrade.c | 402 | ||||
-rw-r--r-- | client/mysqladmin.cc | 11 | ||||
-rw-r--r-- | client/mysqlbinlog.cc | 646 | ||||
-rw-r--r-- | client/mysqlcheck.c | 24 | ||||
-rw-r--r-- | client/mysqldump.c | 2058 | ||||
-rw-r--r-- | client/mysqlimport.c | 5 | ||||
-rw-r--r-- | client/mysqlshow.c | 142 | ||||
-rw-r--r-- | client/mysqltest.c | 3318 | ||||
-rw-r--r-- | client/sql_string.cc | 649 | ||||
-rw-r--r-- | client/sql_string.h | 127 |
13 files changed, 5509 insertions, 2432 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index c0569d5fa6f..5787905fd35 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -16,34 +16,60 @@ # This file is public domain and comes with NO WARRANTY of any kind +if HAVE_YASSL + yassl_dummy_link_fix= $(top_srcdir)/extra/yassl/src/dummy.cpp +else + yassl_dummy_link_fix= +endif #AUTOMAKE_OPTIONS = nostdinc -INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ - -I$(top_srcdir)/regex $(openssl_includes) +INCLUDES = -I$(top_builddir)/include \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/regex \ + $(openssl_includes) LIBS = @CLIENT_LIBS@ LDADD= @CLIENT_EXTRA_LDFLAGS@ \ $(top_builddir)/libmysql/libmysqlclient.la bin_PROGRAMS = mysql mysqladmin mysqlcheck mysqlshow \ - mysqldump mysqlimport mysqltest mysqlbinlog mysqlmanagerc mysqlmanager-pwgen + mysqldump mysqlimport mysqltest mysqlbinlog \ + mysql_upgrade \ + mysqltestmanagerc mysqltestmanager-pwgen noinst_HEADERS = sql_string.h completion_hash.h my_readline.h \ client_priv.h mysql_SOURCES = mysql.cc readline.cc sql_string.cc completion_hash.cc mysqladmin_SOURCES = mysqladmin.cc mysql_LDADD = @readline_link@ @TERMCAP_LIB@ $(LDADD) $(CXXLDFLAGS) -mysqlbinlog_LDADD = $(LDADD) $(CXXLDFLAGS) -mysqltest_SOURCES= mysqltest.c $(top_srcdir)/mysys/my_getsystime.c +mysqltest_SOURCES= mysqltest.c $(top_srcdir)/mysys/my_getsystime.c \ + $(yassl_dummy_link_fix) mysqltest_LDADD = $(top_builddir)/regex/libregex.a $(LDADD) -mysqlbinlog_SOURCES = mysqlbinlog.cc ../mysys/mf_tempdir.c -mysqlmanagerc_SOURCES = mysqlmanagerc.c -sql_src=log_event.h log_event.cc +mysqlbinlog_SOURCES = mysqlbinlog.cc $(top_srcdir)/mysys/mf_tempdir.c $(top_srcdir)/mysys/my_new.cc +mysqlbinlog_LDADD = $(LDADD) $(CXXLDFLAGS) +mysqltestmanager_pwgen_SOURCES = mysqlmanager-pwgen.c +mysqltestmanagerc_SOURCES= mysqlmanagerc.c $(yassl_dummy_link_fix) +mysqlcheck_SOURCES= mysqlcheck.c $(yassl_dummy_link_fix) +mysqlshow_SOURCES= mysqlshow.c $(yassl_dummy_link_fix) +mysqldump_SOURCES= mysqldump.c my_user.c $(yassl_dummy_link_fix) +mysqlimport_SOURCES= mysqlimport.c $(yassl_dummy_link_fix) +mysql_upgrade_SOURCES= mysql_upgrade.c $(yassl_dummy_link_fix) +sql_src=log_event.h mysql_priv.h log_event.cc my_decimal.h my_decimal.cc +strings_src=decimal.c # Fix for mit-threads -DEFS = -DUNDEF_THREADS_HACK +DEFS = -DUNDEF_THREADS_HACK \ + -DDEFAULT_MYSQL_HOME="\"$(prefix)\"" \ + -DDATADIR="\"$(localstatedir)\"" link_sources: for f in $(sql_src) ; do \ rm -f $$f; \ @LN_CP_F@ $(top_srcdir)/sql/$$f $$f; \ - done; + done; \ + for f in $(strings_src) ; do \ + rm -f $(srcdir)/$$f; \ + @LN_CP_F@ $(top_srcdir)/strings/$$f $$f; \ + done; \ + rm -f $(srcdir)/my_user.c; \ + @LN_CP_F@ $(top_srcdir)/sql-common/my_user.c my_user.c; + # Don't update the files from bitkeeper %::SCCS/s.% diff --git a/client/client_priv.h b/client/client_priv.h index 37ed407de68..9e011144836 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -45,10 +45,11 @@ enum options_client OPT_COMPATIBLE, OPT_RECONNECT, OPT_DELIMITER, OPT_SECURE_AUTH, OPT_OPEN_FILES_LIMIT, OPT_SET_CHARSET, OPT_CREATE_OPTIONS, OPT_START_POSITION, OPT_STOP_POSITION, OPT_START_DATETIME, OPT_STOP_DATETIME, - OPT_SIGINT_IGNORE, OPT_HEXBLOB, OPT_ORDER_BY_PRIMARY + OPT_SIGINT_IGNORE, OPT_HEXBLOB, OPT_ORDER_BY_PRIMARY, OPT_COUNT, #ifdef HAVE_NDBCLUSTER_DB - ,OPT_NDBCLUSTER,OPT_NDB_CONNECTSTRING + OPT_NDBCLUSTER, OPT_NDB_CONNECTSTRING, #endif - ,OPT_IGNORE_TABLE,OPT_INSERT_IGNORE,OPT_DROP_DATABASE, - OPT_AUTO_CLOSE + OPT_TRIGGERS, + OPT_IGNORE_TABLE,OPT_INSERT_IGNORE,OPT_SHOW_WARNINGS,OPT_DROP_DATABASE, + OPT_TZ_UTC, OPT_AUTO_CLOSE, OPT_SSL_VERIFY_SERVER_CERT }; diff --git a/client/mysql.cc b/client/mysql.cc index 09818ae27b3..94b43d030e8 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -44,7 +44,7 @@ #include <locale.h> #endif -const char *VER= "14.7"; +const char *VER= "14.12"; /* Don't try to make a nice table if the data is too big */ #define MAX_COLUMN_LENGTH 1024 @@ -135,7 +135,8 @@ static my_bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0, opt_xml=0,opt_nopager=1, opt_outfile=0, named_cmds= 0, tty_password= 0, opt_nobeep=0, opt_reconnect=1, default_charset_used= 0, opt_secure_auth= 0, - default_pager_set= 0, opt_sigint_ignore= 0; + default_pager_set= 0, opt_sigint_ignore= 0, + show_warnings = 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 my_string opt_mysql_unix_port=0; @@ -144,6 +145,7 @@ static char *current_host,*current_db,*current_user=0,*opt_password=0, *current_prompt=0, *delimiter_str= 0, *default_charset= (char*) MYSQL_DEFAULT_CHARSET_NAME; 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; @@ -154,6 +156,8 @@ static char mysql_charsets_dir[FN_REFLEN+1]; static const char *xmlmeta[] = { "&", "&", "<", "<", + ">", ">", + "\"", """, 0, 0 }; static const char *day_names[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; @@ -181,6 +185,7 @@ 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); static int com_quit(String *str,char*), @@ -191,14 +196,15 @@ static int com_quit(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_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_lines(bool execute_commands); +static int read_and_execute(bool interactive); 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, @@ -232,7 +238,7 @@ static COMMANDS commands[] = { { "connect",'r', com_connect,1, "Reconnect to the server. Optional arguments are db and host." }, { "delimiter", 'd', com_delimiter, 1, - "Set query delimiter. " }, + "Set statement delimiter. NOTE: Takes the rest of the line as new delimiter." }, #ifdef USE_POPEN { "edit", 'e', com_edit, 0, "Edit command with $EDITOR."}, #endif @@ -263,8 +269,12 @@ static COMMANDS commands[] = { "Set outfile [to_outfile]. Append everything into given outfile." }, { "use", 'u', com_use, 1, "Use another database. Takes database name as argument." }, - { "charset_name", 'C', com_charset, 1, - "Switch to another charset. Might be needed for processing binlog." }, + { "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, ""}, @@ -313,7 +323,7 @@ static void initialize_readline (char *name); static void fix_history(String *final_command); #endif -static COMMANDS *find_command (char *name,char cmd_name); +static COMMANDS *find_command(char *name,char cmd_name); static bool add_line(String &buffer,char *line,char *in_string, bool *ml_comment); static void remove_cntrl(String &buffer); @@ -322,6 +332,7 @@ 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 ulong start_timer(void); static void end_timer(ulong start_time,char *buff); static void mysql_end_timer(ulong start_time,char *buff); @@ -332,16 +343,15 @@ static sig_handler mysql_end(int sig); int main(int argc,char *argv[]) { char buff[80]; - char *defaults, *extra_defaults; - char *emb_argv[3]; - int emb_argc= 1; + char *defaults, *extra_defaults, *group_suffix; + char *emb_argv[4]; + int emb_argc; - emb_argv[0]= argv[0]; - get_defaults_files(argc, argv, &defaults, &extra_defaults); - if (defaults) - emb_argv[emb_argc++]= defaults; - if (extra_defaults) - emb_argv[emb_argc++]= extra_defaults; + /* Get --defaults-xxx args for mysql_server_init() */ + emb_argc= get_defaults_options(argc, argv, &defaults, &extra_defaults, + &group_suffix)+1; + memcpy((char*) emb_argv, (char*) argv, emb_argc * sizeof(*argv)); + emb_argv[emb_argc]= 0; MY_INIT(argv[0]); DBUG_ENTER("main"); @@ -425,7 +435,7 @@ int main(int argc,char *argv[]) put_info((char*) glob_buffer.ptr(),INFO_INFO); #ifdef HAVE_READLINE - initialize_readline(my_progname); + initialize_readline((char*) my_progname); if (!status.batch && !quick && !opt_html && !opt_xml) { /* read-history from file, default ~/.mysql_history*/ @@ -438,12 +448,27 @@ int main(int argc,char *argv[]) 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, MYF(MY_ALLOW_ZERO_PTR)); + histfile= 0; + } } if (histfile) { if (verbose) tee_fprintf(stdout, "Reading history-file %s\n",histfile); read_history(histfile); + if (!(histfile_tmp= (char*) my_malloc((uint) 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 @@ -454,7 +479,7 @@ int main(int argc,char *argv[]) "Type 'help [[%]function name[%]]' to get help on usage of function.\n"); #endif put_info(buff,INFO_INFO); - status.exit_status=read_lines(1); // read lines and execute them + status.exit_status= read_and_execute(!status.batch); if (opt_outfile) end_tee(); mysql_end(0); @@ -467,12 +492,13 @@ sig_handler mysql_end(int sig) { mysql_close(&mysql); #ifdef HAVE_READLINE - if (!status.batch && !quick && !opt_html && !opt_xml) + if (!status.batch && !quick && !opt_html && !opt_xml && histfile) { /* write-history */ if (verbose) tee_fprintf(stdout, "Writing history-file %s\n",histfile); - write_history(histfile); + if (!write_history(histfile_tmp)) + my_rename(histfile_tmp, histfile, MYF(MY_WME)); } batch_readline_end(status.line_buff); completion_hash_free(&ht); @@ -487,6 +513,7 @@ sig_handler mysql_end(int sig) 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(histfile_tmp,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)); @@ -612,7 +639,7 @@ static struct my_option my_long_options[] = NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, - (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"prompt", OPT_PROMPT, "Set the mysql prompt to this value.", (gptr*) ¤t_prompt, (gptr*) ¤t_prompt, 0, GET_STR_ALLOC, @@ -687,6 +714,9 @@ static struct my_option my_long_options[] = {"secure-auth", OPT_SECURE_AUTH, "Refuse client connecting to server if it" " uses old (pre-4.1.1) protocol", (gptr*) &opt_secure_auth, (gptr*) &opt_secure_auth, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.", + (gptr*) &show_warnings, (gptr*) &show_warnings, 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} }; @@ -709,7 +739,7 @@ static void usage(int version) my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE, readline, rl_library_version); #else - printf("%s Ver %s Distrib %s, for %s (%s)", my_progname, VER, + printf("%s Ver %s Distrib %s, for %s (%s)\n", my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); #endif @@ -793,6 +823,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), case OPT_NOPAGER: printf("WARNING: option deprecated; use --disable-pager instead.\n"); opt_nopager= 1; + break; case OPT_MYSQL_PROTOCOL: { if ((opt_protocol= find_type(argument, &sql_protocol_typelib,0)) <= 0) @@ -938,7 +969,7 @@ static int get_options(int argc, char **argv) return(0); } -static int read_lines(bool execute_commands) +static int read_and_execute(bool interactive) { #if defined(OS2) || defined(__NETWARE__) char linebuffer[254]; @@ -958,7 +989,7 @@ static int read_lines(bool execute_commands) for (;;) { - if (status.batch || !execute_commands) + if (!interactive) { line=batch_readline(status.line_buff); line_number++; @@ -991,11 +1022,12 @@ static int read_lines(bool execute_commands) #elif defined(__WIN__) if (!tmpbuf.is_alloced()) tmpbuf.alloc(65535); + tmpbuf.length(0); buffer.length(0); unsigned long clen; do { - line= my_cgets((char *) tmpbuf.ptr(), tmpbuf.alloced_length()-1, &clen); + line= my_cgets((char*)tmpbuf.ptr(), tmpbuf.alloced_length()-1, &clen); buffer.append(line, clen); /* if we got buffer fully filled than there is a chance that @@ -1051,7 +1083,7 @@ static int read_lines(bool execute_commands) Check if line is a mysql command line (We want to allow help, print and clear anywhere at line start */ - if (execute_commands && (named_cmds || glob_buffer.is_empty()) + if ((named_cmds || glob_buffer.is_empty()) && !ml_comment && !in_string && (com=find_command(line,0))) { if ((*com->func)(&glob_buffer,line) > 0) @@ -1059,7 +1091,7 @@ static int read_lines(bool execute_commands) if (glob_buffer.is_empty()) // If buffer was emptied in_string=0; #ifdef HAVE_READLINE - if (status.add_to_history && not_in_history(line)) + if (interactive && status.add_to_history && not_in_history(line)) add_history(line); #endif continue; @@ -1069,7 +1101,7 @@ static int read_lines(bool execute_commands) } /* 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) + if (!interactive && !status.exit_status) { remove_cntrl(glob_buffer); if (!glob_buffer.is_empty()) @@ -1091,10 +1123,12 @@ static int read_lines(bool execute_commands) } -static COMMANDS *find_command (char *name,char cmd_char) +static COMMANDS *find_command(char *name,char cmd_char) { uint len; char *end; + DBUG_ENTER("find_command"); + DBUG_PRINT("enter",("name: '%s' char: %d", name ? name : "NULL", cmd_char)); if (!name) { @@ -1105,8 +1139,18 @@ static COMMANDS *find_command (char *name,char cmd_char) { while (my_isspace(charset_info,*name)) name++; - if (strstr(name, delimiter) || strstr(name, "\\g")) - return ((COMMANDS *) 0); + /* + 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 (strstr(name, "\\g") || (strstr(name, delimiter) && + !(strlen(name) >= 9 && + !my_strnncoll(charset_info, + (uchar*) name, 9, + (const uchar*) "delimiter", + 9)))) + DBUG_RETURN((COMMANDS *) 0); if ((end=strcont(name," \t"))) { len=(uint) (end - name); @@ -1122,15 +1166,18 @@ static COMMANDS *find_command (char *name,char cmd_char) for (uint i= 0; commands[i].name; i++) { if (commands[i].func && - ((name && + ((name && !my_strnncoll(charset_info,(uchar*)name,len, (uchar*)commands[i].name,len) && !commands[i].name[len] && (!end || (end && commands[i].takes_params))) || !name && commands[i].cmd_char == cmd_char)) - return (&commands[i]); + { + DBUG_PRINT("exit",("found command: %s", commands[i].name)); + DBUG_RETURN(&commands[i]); + } } - return ((COMMANDS *) 0); + DBUG_RETURN((COMMANDS *) 0); } @@ -1140,15 +1187,17 @@ static bool add_line(String &buffer,char *line,char *in_string, uchar inchar; char buff[80], *pos, *out; COMMANDS *com; + bool need_space= 0; + DBUG_ENTER("add_line"); if (!line[0] && buffer.is_empty()) - return 0; + DBUG_RETURN(0); #ifdef HAVE_READLINE if (status.add_to_history && line[0] && not_in_history(line)) add_history(line); #endif #ifdef USE_MB - char *strend=line+(uint) strlen(line); + char *end_of_line=line+(uint) strlen(line); #endif for (pos=out=line ; (inchar= (uchar) *pos) ; pos++) @@ -1157,18 +1206,18 @@ static bool add_line(String &buffer,char *line,char *in_string, buffer.is_empty()) continue; #ifdef USE_MB - int l; + int length; if (use_mb(charset_info) && - (l= my_ismbchar(charset_info, pos, strend))) + (length= my_ismbchar(charset_info, pos, end_of_line))) { if (!*ml_comment) { - while (l--) + while (length--) *out++ = *pos++; pos--; } else - pos+= l - 1; + pos+= length - 1; continue; } #endif @@ -1189,7 +1238,7 @@ static bool add_line(String &buffer,char *line,char *in_string, const String tmp(line,(uint) (out-line), charset_info); buffer.append(tmp); if ((*com->func)(&buffer,pos-1) > 0) - return 1; // Quit + DBUG_RETURN(1); // Quit if (com->takes_params) { for (pos++ ; @@ -1207,29 +1256,40 @@ static bool add_line(String &buffer,char *line,char *in_string, { sprintf(buff,"Unknown command '\\%c'.",inchar); if (put_info(buff,INFO_ERROR) > 0) - return 1; + DBUG_RETURN(1); *out++='\\'; *out++=(char) inchar; continue; } } - - else if (!*ml_comment && (*pos == *delimiter && - is_prefix(pos + 1, delimiter + 1)) && - !*in_string) + else if (!*ml_comment && !*in_string && + (*pos == *delimiter && is_prefix(pos + 1, delimiter + 1) || + buffer.length() == 0 && (out - line) >= 9 && + !my_strcasecmp(charset_info, line, "delimiter"))) { uint old_delimiter_length= delimiter_length; if (out != line) buffer.append(line, (uint) (out - line)); // Add this line if ((com= find_command(buffer.c_ptr(), 0))) { + if (com->func == com_delimiter) + { + /* + Delimiter wants the get rest of the given line as argument to + allow one to change ';' to ';;' and back + */ + char *end= strend(pos); + buffer.append(pos, (uint) (end - pos)); + /* Ensure pos will point at \0 after the pos+= below */ + pos= end - old_delimiter_length + 1; + } if ((*com->func)(&buffer, buffer.c_ptr()) > 0) - return 1; // Quit + DBUG_RETURN(1); // Quit } else { if (com_go(&buffer, 0) > 0) // < 0 is not fatal - return 1; + DBUG_RETURN(1); } buffer.length(0); out= line; @@ -1254,6 +1314,7 @@ static bool add_line(String &buffer,char *line,char *in_string, { pos++; *ml_comment= 0; + need_space= 1; } else { // Add found char to buffer @@ -1263,7 +1324,14 @@ static bool add_line(String &buffer,char *line,char *in_string, (inchar == '\'' || inchar == '"' || inchar == '`')) *in_string= (char) inchar; if (!*ml_comment) + { + if (need_space && !my_isspace(charset_info, (char)inchar)) + { + *out++= ' '; + need_space= 0; + } *out++= (char) inchar; + } } } if (out != line || !buffer.is_empty()) @@ -1273,9 +1341,9 @@ static bool add_line(String &buffer,char *line,char *in_string, if (buffer.length() + length >= buffer.alloced_length()) buffer.realloc(buffer.length()+length+IO_SIZE); if (!(*ml_comment) && buffer.append(line,length)) - return 1; + DBUG_RETURN(1); } - return 0; + DBUG_RETURN(0); } /***************************************************************** @@ -1681,11 +1749,12 @@ int mysql_real_query_for_lazy(const char *buf, int length) { for (uint retry=0;; retry++) { + int error; if (!mysql_real_query(&mysql,buf,length)) return 0; - int error= put_error(&mysql); + error= put_error(&mysql); if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || - !opt_reconnect) + !opt_reconnect) return error; if (reconnect()) return error; @@ -1967,7 +2036,7 @@ com_go(String *buffer,char *line __attribute__((unused))) time_buff[0]=0; if (result) { - if (!mysql_num_rows(result) && ! quick) + if (!mysql_num_rows(result) && ! quick && !info_flag) { strmov(buff, "Empty set"); } @@ -2022,6 +2091,13 @@ com_go(String *buffer,char *line __attribute__((unused))) if (err >= 1) error= put_error(&mysql); + if (show_warnings == 1 && warnings >= 1) /* Show warnings if any */ + { + init_pager(); + print_warnings(); + end_pager(); + } + if (!error && !status.batch && (mysql.server_status & SERVER_STATUS_DB_DROPPED)) get_current_db(); @@ -2081,6 +2157,7 @@ static void end_tee() return; } + static int com_ego(String *buffer,char *line) { @@ -2092,18 +2169,95 @@ com_ego(String *buffer,char *line) return result; } + +static const char *fieldtype2str(enum enum_field_types type) +{ + switch (type) { + case FIELD_TYPE_BIT: return "BIT"; + case FIELD_TYPE_BLOB: return "BLOB"; + case FIELD_TYPE_DATE: return "DATE"; + case FIELD_TYPE_DATETIME: return "DATETIME"; + case FIELD_TYPE_NEWDECIMAL: return "NEWDECIMAL"; + case FIELD_TYPE_DECIMAL: return "DECIMAL"; + case FIELD_TYPE_DOUBLE: return "DOUBLE"; + case FIELD_TYPE_ENUM: return "ENUM"; + case FIELD_TYPE_FLOAT: return "FLOAT"; + case FIELD_TYPE_GEOMETRY: return "GEOMETRY"; + case FIELD_TYPE_INT24: return "INT24"; + case FIELD_TYPE_LONG: return "LONG"; + case FIELD_TYPE_LONGLONG: return "LONGLONG"; + case FIELD_TYPE_LONG_BLOB: return "LONG_BLOB"; + case FIELD_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB"; + case FIELD_TYPE_NEWDATE: return "NEWDATE"; + case FIELD_TYPE_NULL: return "NULL"; + case FIELD_TYPE_SET: return "SET"; + case FIELD_TYPE_SHORT: return "SHORT"; + case FIELD_TYPE_STRING: return "STRING"; + case FIELD_TYPE_TIME: return "TIME"; + case FIELD_TYPE_TIMESTAMP: return "TIMESTAMP"; + case FIELD_TYPE_TINY: return "TINY"; + case FIELD_TYPE_TINY_BLOB: return "TINY_BLOB"; + case FIELD_TYPE_VAR_STRING: return "VAR_STRING"; + case FIELD_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); + ff2s_check_flag(UNIQUE); + ff2s_check_flag(BINCMP); +#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; + MYSQL_FIELD *field; + uint i=0; + while ((field = mysql_fetch_field(result))) { - tee_fprintf(PAGER,"Catalog: '%s'\nDatabase: '%s'\nTable: '%s'\nName: '%s'\nType: %d\nLength: %ld\nMax length: %ld\nIs_null: %d\nFlags: %u\nDecimals: %u\n\n", - field->catalog, field->db, field->table, field->name, - (int) field->type, - field->length, field->max_length, - !IS_NOT_NULL(field->flags), - field->flags, field->decimals); + tee_fprintf(PAGER, "Field %3u: `%s`\n" + "Catalog: `%s`\n" + "Database: `%s`\n" + "Table: `%s`\n" + "Org_table: `%s`\n" + "Type: %s\n" + "Collation: %s (%u)\n" + "Length: %lu\n" + "Max_length: %lu\n" + "Decimals: %u\n" + "Flags: %s\n\n", + ++i, + field->name, field->catalog, field->db, field->table, + field->org_table, fieldtype2str(field->type), + get_charset_name(field->charsetnr), field->charsetnr, + field->length, field->max_length, field->decimals, + fieldflags2str(field->flags)); } tee_puts("", PAGER); } @@ -2116,11 +2270,15 @@ print_table_data(MYSQL_RES *result) MYSQL_ROW cur; MYSQL_FIELD *field; bool *num_flag; + bool *not_null_flag; num_flag=(bool*) my_alloca(sizeof(bool)*mysql_num_fields(result)); + not_null_flag=(bool*) my_alloca(sizeof(bool)*mysql_num_fields(result)); if (info_flag) { print_field_types(result); + if (!mysql_num_rows(result)) + return; mysql_field_seek(result,0); } separator.copy("+",1,charset_info); @@ -2133,7 +2291,7 @@ print_table_data(MYSQL_RES *result) length=max(length,field->max_length); if (length < 4 && !IS_NOT_NULL(field->flags)) length=4; // Room for "NULL" - field->max_length=length+1; + field->max_length=length; separator.fill(separator.length()+length+2,'-'); separator.append('+'); } @@ -2145,10 +2303,11 @@ print_table_data(MYSQL_RES *result) (void) tee_fputs("|", PAGER); for (uint off=0; (field = mysql_fetch_field(result)) ; off++) { - tee_fprintf(PAGER, " %-*s|",(int) min(field->max_length, + tee_fprintf(PAGER, " %-*s |",(int) min(field->max_length, MAX_COLUMN_LENGTH), field->name); num_flag[off]= IS_NUM(field->type); + not_null_flag[off]= IS_NOT_NULL(field->flags); } (void) tee_fputs("\n", PAGER); tee_puts((char*) separator.ptr(), PAGER); @@ -2157,35 +2316,93 @@ print_table_data(MYSQL_RES *result) while ((cur= mysql_fetch_row(result))) { ulong *lengths= mysql_fetch_lengths(result); - (void) tee_fputs("|", PAGER); + (void) tee_fputs("| ", PAGER); mysql_field_seek(result, 0); for (uint off= 0; off < mysql_num_fields(result); off++) { - const char *str= cur[off] ? cur[off] : "NULL"; - field= mysql_fetch_field(result); - uint maxlength= field->max_length; - if (maxlength > MAX_COLUMN_LENGTH) + const char *buffer; + uint data_length; + uint field_max_length; + bool right_justified; + uint visible_length; + uint extra_padding; + + /* If this column may have a null value, use "NULL" for empty. */ + if (! not_null_flag[off] && (cur[off] == NULL)) { - tee_fputs(str, PAGER); - tee_fputs(" |", PAGER); + buffer= "NULL"; + data_length= 4; + } + else + { + buffer= cur[off]; + data_length= (uint) lengths[off]; } + + 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. + */ + visible_length= charset_info->cset->numcells(charset_info, buffer, buffer + data_length); + extra_padding= data_length - visible_length; + + if (field_max_length > MAX_COLUMN_LENGTH) + tee_print_sized_data(buffer, data_length, MAX_COLUMN_LENGTH+extra_padding, FALSE); else { - uint currlength= (uint) lengths[off]; - uint numcells= charset_info->cset->numcells(charset_info, - str, str + currlength); - tee_fprintf(PAGER, num_flag[off] ? "%*s |" : " %-*s|", - maxlength + currlength - numcells, str); + 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((gptr) num_flag); + my_afree((gptr) not_null_flag); } 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; @@ -2238,13 +2455,11 @@ print_table_data_xml(MYSQL_RES *result) (void) tee_fputs("\n <row>\n", PAGER); for (uint i=0; i < mysql_num_fields(result); i++) { - tee_fprintf(PAGER, "\t<%s>", (fields[i].name ? - (fields[i].name[0] ? fields[i].name : - " ") : "NULL")); + tee_fprintf(PAGER, "\t<field name=\""); + xmlencode_print(fields[i].name, (uint) strlen(fields[i].name)); + tee_fprintf(PAGER, "\">"); xmlencode_print(cur[i], lengths[i]); - tee_fprintf(PAGER, "</%s>\n", (fields[i].name ? - (fields[i].name[0] ? fields[i].name : - " ") : "NULL")); + tee_fprintf(PAGER, "</field>\n"); } (void) tee_fputs(" </row>\n", PAGER); } @@ -2283,13 +2498,41 @@ print_table_data_vertically(MYSQL_RES *result) } -static const char -*array_value(const char **array, char key) +/* 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; + + /* 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 (!(num_rows= mysql_num_rows(result))) + { + mysql_free_result(result); + return; + } + + /* Print the warnings */ + while ((cur= mysql_fetch_row(result))) + { + tee_fprintf(PAGER, "%s (Code %s): %s\n", cur[0], cur[1], cur[2]); + } + mysql_free_result(result); +} + + +static const char *array_value(const char **array, char key) { - int x; - for (x= 0; array[x]; x+= 2) - if (*array[x] == key) - return array[x + 1]; + for (; *array; array+= 2) + if (**array == key) + return array[1]; return 0; } @@ -2693,7 +2936,7 @@ static int com_source(String *buffer, char *line) 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 + error= read_and_execute(false); status=old_status; // Continue as before my_fclose(sql_file,MYF(0)); batch_readline_end(line_buff); @@ -2727,6 +2970,7 @@ static int com_use(String *buffer __attribute__((unused)), char *line) { char *tmp, buff[FN_REFLEN + 1]; + int select_db; bzero(buff, sizeof(buff)); strmov(buff, line); @@ -2746,38 +2990,73 @@ com_use(String *buffer __attribute__((unused)), char *line) 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 - { - /* - reconnect once if connection is down or if connection was found to - be down during query - */ - if (!connected && reconnect()) + 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 (mysql_select_db(&mysql,tmp)) + { + if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR) + return put_error(&mysql); - if (reconnect()) + if (reconnect()) return opt_reconnect ? -1 : 1; // Fatal error - if (mysql_select_db(&mysql,tmp)) - return put_error(&mysql); - } - my_free(current_db,MYF(MY_ALLOW_ZERO_PTR)); - current_db=my_strdup(tmp,MYF(MY_WME)); + if (mysql_select_db(&mysql,tmp)) + return put_error(&mysql); + } + my_free(current_db,MYF(MY_ALLOW_ZERO_PTR)); + current_db=my_strdup(tmp,MYF(MY_WME)); #ifdef HAVE_READLINE + if (select_db > 1) build_completion_hash(rehash, 1); #endif - } } - else - skip_updates= 0; + 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 get_next_arg is @@ -2867,6 +3146,8 @@ sql_real_connect(char *host,char *database,char *user,char *password, if (opt_use_ssl) 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_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); #endif if (opt_protocol) mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); @@ -2901,6 +3182,8 @@ sql_real_connect(char *host,char *database,char *user,char *password, connected=1; #ifndef EMBEDDED_LIBRARY mysql.reconnect=info_flag ? 1 : 0; // We want to know if this happens +#else + mysql.reconnect= 1; #endif #ifdef HAVE_READLINE build_completion_hash(rehash, 1); @@ -2975,10 +3258,9 @@ com_status(String *buffer __attribute__((unused)), mysql_free_result(result); } #ifdef HAVE_OPENSSL - if (mysql.net.vio && mysql.net.vio->ssl_arg && - SSL_get_cipher((SSL*) mysql.net.vio->ssl_arg)) + if ((status= mysql_get_ssl_cipher(&mysql))) tee_fprintf(stdout, "SSL:\t\t\tCipher in use is %s\n", - SSL_get_cipher((SSL*) mysql.net.vio->ssl_arg)); + status); else #endif /* HAVE_OPENSSL */ tee_puts("SSL:\t\t\tNot in use", stdout); @@ -3124,7 +3406,7 @@ put_info(const char *str,INFO_TYPE info_type, uint error, const char *sqlstate) if (info_type == INFO_ERROR) { if (!opt_nobeep) - putchar('\007'); /* This should make a bell */ + putchar('\a'); /* This should make a bell */ vidattr(A_STANDOUT); if (error) { diff --git a/client/mysql_upgrade.c b/client/mysql_upgrade.c new file mode 100644 index 00000000000..3288b627554 --- /dev/null +++ b/client/mysql_upgrade.c @@ -0,0 +1,402 @@ +/* Copyright (C) 2000 MySQL 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 */ + +#include "client_priv.h" +#include <my_dir.h> + +static my_bool opt_force= 0, opt_verbose= 0, tty_password= 0; +static char *user= (char*) "root", *basedir= 0, *datadir= 0, *opt_password= 0; +static my_bool upgrade_defaults_created= 0; +static my_string opt_mysql_port, opt_mysql_unix_port= 0; +static char *default_dbug_option= (char*) "d:t:O,/tmp/comp_err.trace"; +static my_bool info_flag= 0; + +static struct my_option my_long_options[]= +{ + {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"basedir", 'b', "Specifies the directory where MySQL is installed", + (gptr*) &basedir, + (gptr*) &basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"datadir", 'd', "Specifies the data directory", (gptr*) &datadir, + (gptr*) &datadir, 0, GET_STR, REQUIRED_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", (gptr *) & default_dbug_option, + (gptr *) & default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-info", 'T', "Print some debug info at exit.", (gptr *) & info_flag, + (gptr *) & info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', "Continue even if we get an sql-error.", + (gptr*) &opt_force, (gptr*) &opt_force, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given it's solicited on the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, + (gptr*) &opt_mysql_port, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol of connection (tcp,socket,pipe,memory).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "Socket file to use for connection.", + (gptr*) &opt_mysql_unix_port, (gptr*) &opt_mysql_unix_port, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"user", 'u', "User for login if not current user.", (gptr*) &user, + (gptr*) &user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Display more output about the process", (gptr*) &opt_verbose, + (gptr *) &opt_verbose, 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 const char *load_default_groups[]= +{ + "mysql_upgrade", "client", 0 +}; + +#include <help_end.h> + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__ ((unused)), + char *argument) +{ + switch (optid) { + case '?': + puts + ("MySQL utility script to upgrade database to the current server version"); + puts(""); + my_print_help(my_long_options); + exit(0); + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + break; + case 'f': + opt_force= TRUE; + break; + case 'p': + tty_password= 1; + if (argument) + { + char *start= argument; + my_free(opt_password, MYF(MY_ALLOW_ZERO_PTR)); + opt_password= my_strdup(argument, MYF(MY_FAE)); + while (*argument) + *argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]= 0; /* Cut length of argument */ + tty_password= 0; + } + break; + default:; + }; + return 0; +} + + +/* buffer should be not smaller than FN_REFLEN */ +static my_bool test_file_exists_res(const char *dir, const char *fname, + char *buffer, char **buf_end) +{ + MY_STAT stat_info; + + *buf_end= strxnmov(buffer, FN_REFLEN-1, dir, "/", fname, NullS); + unpack_filename(buffer, buffer); + return my_stat(buffer, &stat_info, MYF(0)) != 0; +} + + +static my_bool test_file_exists(const char *dir, const char *fname) +{ + char path[FN_REFLEN]; + char *path_end; + return test_file_exists_res(dir, fname, path, &path_end); +} + + +static int create_check_file(const char *path) +{ + File check_file= my_open(path, O_CREAT | O_WRONLY, MYF(MY_FAE | MY_WME)); + int error; + + if (check_file < 0) + return 1; + + error= my_write(check_file, + MYSQL_SERVER_VERSION, strlen(MYSQL_SERVER_VERSION), + MYF(MY_WME | MY_FNABP)); + error= my_close(check_file, MYF(MY_FAE | MY_WME)) || error; + return error; +} + + +static int create_defaults_file(const char *path, const char *our_defaults_path) +{ + uint b_read; + File our_defaults_file, defaults_file; + char buffer[512]; + char *buffer_end; + int error; + + /* check if the defaults file is needed at all */ + if (!opt_password) + return 0; + + defaults_file= my_open(path, O_BINARY | O_CREAT | O_WRONLY, + MYF(MY_FAE | MY_WME)); + + if (defaults_file < 0) + return 1; + upgrade_defaults_created= 1; + if (our_defaults_path) + { + our_defaults_file= my_open(our_defaults_path, O_RDONLY, + MYF(MY_FAE | MY_WME)); + if (our_defaults_file < 0) + return 1; + do + { + if (((b_read= my_read(our_defaults_file, buffer, + sizeof(buffer), MYF(MY_WME))) == MY_FILE_ERROR) || + my_write(defaults_file, buffer, b_read, MYF(MY_FNABP | MY_WME))) + { + error= 1; + goto close_return; + } + } while (b_read == sizeof(buffer)); + } + buffer_end= strnmov(buffer, "\n[client]", sizeof(buffer)); + if (opt_password) + buffer_end= strxnmov(buffer, sizeof(buffer), + "\npassword=", opt_password, NullS); + error= my_write(defaults_file, buffer, (int) (buffer_end - buffer), + MYF(MY_WME | MY_FNABP)); +close_return: + return my_close(defaults_file, MYF(MY_WME)) || error; +} + + +int main(int argc, char **argv) +{ + char bindir[FN_REFLEN]; + char *bindir_end, *buf_end; + char datadir_buf[FN_REFLEN]; + char mysqlcheck_line[FN_REFLEN], *mysqlcheck_end; + char check_file_name[FN_REFLEN]; + int check_file; + char fix_priv_tables_cmd[FN_REFLEN], *fix_cmd_end; + char script_line[FN_REFLEN]; + int error; + char *forced_defaults_file; + char *forced_extra_defaults; + char *defaults_group_suffix; + char upgrade_defaults_path[FN_REFLEN], *defaults_to_use= 0; + char port_socket[100], *port_socket_end; + + MY_INIT(argv[0]); +#ifdef __NETWARE__ + setscreenmode(SCR_AUTOCLOSE_ON_EXIT); +#endif + + load_defaults("my", load_default_groups, &argc, &argv); + + if ((error= handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(error); + + if (tty_password) + opt_password= get_tty_password(NullS); + + /* Check if we want to force the use a specific default file */ + get_defaults_options(argc, argv, + &forced_defaults_file, &forced_extra_defaults, + &defaults_group_suffix); + + port_socket_end= port_socket; + if (opt_mysql_port) + port_socket_end= strxnmov(port_socket, sizeof(port_socket) - 1, " --port=", + opt_mysql_port, NullS); + if (opt_mysql_unix_port) + port_socket_end= strxnmov(port_socket_end, + sizeof(port_socket) - + (int)(port_socket_end - port_socket) - 1, + " --socket=", opt_mysql_unix_port, NullS); + *port_socket_end= 0; + + if (basedir) + { + bindir_end= strmake(bindir, basedir, sizeof(bindir)-1); + } + else + { + if (test_file_exists("./share/mysql/english", "errmsg.sys") + && (test_file_exists("./bin", "mysqld") || + test_file_exists("./libexec", "mysqld"))) + { + my_getwd(bindir, sizeof(bindir), MYF(0)); + bindir_end= bindir + strlen(bindir); + } + else + { + bindir_end= strmake(bindir, DEFAULT_MYSQL_HOME, sizeof(bindir)-1); + } + } + + if (!datadir) + { + datadir= datadir_buf; + if (test_file_exists(bindir, "data/mysql")) + { + *strxnmov(datadir_buf, sizeof(datadir_buf)-1, bindir, "/data", NullS)= 0; + } + else if (test_file_exists(bindir, "var/mysql")) + { + *strxnmov(datadir_buf, sizeof(datadir_buf)-1, bindir, "/var", NullS)= 0; + } + else + datadir= (char*) DATADIR; + } + + strmake(bindir_end, "/bin", sizeof(bindir) - (int) (bindir_end - bindir)-1); + + if (!test_file_exists_res + (bindir, "mysqlcheck", mysqlcheck_line, &mysqlcheck_end)) + { + printf("Can't find program '%s'\n", mysqlcheck_line); + puts("Please restart with --basedir=mysql-install-directory"); + exit(1); + } + + if (!test_file_exists(datadir, "mysql/user.frm")) + { + puts + ("Can't find data directory. Please restart with --datadir=path-to-data-dir"); + exit(1); + } + + /* create the modified defaults file to be used by mysqlcheck */ + /* and mysql tools */ + *strxnmov(upgrade_defaults_path, sizeof(upgrade_defaults_path)-1, + datadir, "/upgrade_defaults", NullS)= 0; + unpack_filename(upgrade_defaults_path, upgrade_defaults_path); + if ((error= + create_defaults_file(upgrade_defaults_path, forced_extra_defaults))) + goto err_exit; + + defaults_to_use= upgrade_defaults_created ? + upgrade_defaults_path : forced_extra_defaults; + + if (test_file_exists_res(datadir, "mysql_upgrade_info", check_file_name, + &buf_end) && !opt_force) + { + char chf_buffer[50]; + int b_read; + check_file= my_open(check_file_name, O_RDONLY, MYF(0)); + b_read= my_read(check_file, chf_buffer, sizeof(chf_buffer)-1, MYF(0)); + chf_buffer[b_read]= 0; + my_close(check_file, MYF(0)); + if (!strcmp(chf_buffer, MYSQL_SERVER_VERSION)) + { + if (opt_verbose) + puts("mysql_upgrade already done for this version"); + goto fix_priv_tables; + } + } + + if (defaults_to_use) + { + mysqlcheck_end= strxnmov(mysqlcheck_end, + sizeof(mysqlcheck_line) - (int) (mysqlcheck_end - + mysqlcheck_line), + " --defaults-extra-file=", defaults_to_use,NullS); + } + + mysqlcheck_end= strxnmov(mysqlcheck_end, + sizeof(mysqlcheck_line) - + (int) (mysqlcheck_end - mysqlcheck_line - 1), + " --check-upgrade --all-databases --auto-repair --user=", + user, port_socket, NullS); + *mysqlcheck_end= 0; + + if (opt_verbose) + printf("Running %s\n", mysqlcheck_line); + if ((error= system(mysqlcheck_line))) + { + printf("Error executing '%s'\n", mysqlcheck_line); + goto err_exit; + } + + if ((error= create_check_file(check_file_name))) + goto err_exit; + +fix_priv_tables: + if (!test_file_exists_res(bindir, "mysql", fix_priv_tables_cmd, &fix_cmd_end)) + { + puts("Could not find MySQL command-line client (mysql)."); + puts + ("Please use --basedir to specify the directory where MySQL is installed."); + error= 1; + goto err_exit; + } + + if (!test_file_exists_res(basedir, + "support_files/mysql_fix_privilege_tables.sql", + script_line, &buf_end) + && !test_file_exists_res(basedir, "share/mysql_fix_privilege_tables.sql", + script_line, &buf_end) + && !test_file_exists_res(basedir, + "share/mysql/mysql_fix_privilege_tables.sql", + script_line, &buf_end) + && !test_file_exists_res(basedir, + "scripts/mysql_fix_privilege_tables.sql", + script_line, &buf_end) + && !test_file_exists_res("/usr/local/mysql/share/mysql", + "mysql_fix_privilege_tables.sql", script_line, + &buf_end)) + { + puts("Could not find file mysql_fix_privilege_tables.sql"); + puts + ("Please use --basedir to specify the directory where MySQL is installed"); + error= 1; + goto err_exit; + } + + if (defaults_to_use) + { + fix_cmd_end= strxnmov(fix_cmd_end, + sizeof(fix_priv_tables_cmd) - + (int) (fix_cmd_end - fix_priv_tables_cmd - 1), + " --defaults-extra-file=", defaults_to_use, NullS); + } + fix_cmd_end= strxnmov(fix_cmd_end, + sizeof(fix_priv_tables_cmd) - (int) (fix_cmd_end - + fix_priv_tables_cmd), + " --user=", user, port_socket, " mysql < ", script_line, NullS); + *fix_cmd_end= 0; + + if ((error= system(fix_priv_tables_cmd))) + { + /* Problem is that the 'Duplicate column' error */ + /* which is not a bug for the script makes 'mysql' return */ + /* an error */ + /* printf("Error executing '%s'\n", fix_priv_tables_cmd); */ + } + +err_exit: + if (upgrade_defaults_created) + my_delete(upgrade_defaults_path, MYF(0)); + my_end(info_flag ? MY_CHECK_ERROR | MY_GIVE_INFO : 0); + return error; +} /* main */ diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index 7bb3061f412..57ab4e071fb 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -161,7 +161,7 @@ static struct my_option my_long_options[] = NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"port", 'P', "Port number to use for connection.", (gptr*) &tcp_port, - (gptr*) &tcp_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, 0}, + (gptr*) &tcp_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe,memory).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"relative", 'r', @@ -340,6 +340,8 @@ int main(int argc,char *argv[]) if (opt_use_ssl) 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_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); #endif if (opt_protocol) mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); @@ -431,6 +433,7 @@ static my_bool sql_connect(MYSQL *mysql, uint wait) if (mysql_real_connect(mysql,host,user,opt_password,NullS,tcp_port, unix_port, 0)) { + mysql->reconnect= 1; if (info) { fputs("\n",stderr); @@ -568,6 +571,7 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) return -1; } mysql_close(mysql); /* Close connection to avoid error messages */ + argc=1; /* force SHUTDOWN to be the last command */ if (got_pidfile) { if (opt_verbose) @@ -726,7 +730,7 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) void (*func) (MYSQL_RES*, MYSQL_ROW, uint); new_line = 1; - if (mysql_query(mysql, "show status") || + if (mysql_query(mysql, "show /*!50002 GLOBAL */ status") || !(res = mysql_store_result(mysql))) { my_printf_error(0, "unable to show status; error: '%s'", MYF(ME_BELL), @@ -1345,6 +1349,3 @@ static my_bool wait_pidfile(char *pidfile, time_t last_modified, } DBUG_RETURN(error); } -#ifdef __GNUC__ -FIX_GCC_LINKING_PROBLEM -#endif diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 77240a2c750..518ab7cf832 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -14,10 +14,27 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* + + TODO: print the catalog (some USE catalog.db ????). + + Standalone program to read a MySQL binary log (or relay log); + can read files produced by 3.23, 4.x, 5.0 servers. + + Can read binlogs from 3.23/4.x/5.0 and relay logs from 4.x/5.0. + Should be able to read any file of these categories, even with + --start-position. + An important fact: the Format_desc event of the log is at most the 3rd event + of the log; if it is the 3rd then there is this combination: + Format_desc_of_slave, Rotate_of_master, Format_desc_of_master. +*/ + #define MYSQL_CLIENT #undef MYSQL_SERVER #include "client_priv.h" #include <my_time.h> +/* That one is necessary for defines of OPTION_NO_FOREIGN_KEY_CHECKS etc */ +#include "mysql_priv.h" #include "log_event.h" #define BIN_LOG_HEADER_SIZE 4 @@ -45,11 +62,12 @@ static const char *load_default_groups[]= { "mysqlbinlog","client",0 }; void sql_print_error(const char *format, ...); static bool one_database=0, to_last_remote_log= 0, disable_log_bin= 0; +static bool opt_hexdump= 0; static const char* database= 0; static my_bool force_opt= 0, short_form= 0, remote_opt= 0; static ulonglong offset = 0; static const char* host = 0; -static int port = MYSQL_PORT; +static int port= 0; static const char* sock= 0; static const char* user = 0; static char* pass = 0; @@ -67,6 +85,14 @@ static MYSQL* mysql = NULL; static const char* dirname_for_local_load= 0; static bool stop_passed= 0; +/* + check_header() will set the pointer below. + Why do we need here a pointer on an event instead of an event ? + This is because the event will be created (alloced) in read_log_event() + (which returns a pointer) in check_header(). +*/ +Format_description_log_event* description_event; + static int dump_local_log_entries(const char* logname); static int dump_remote_log_entries(const char* logname); static int dump_log_entries(const char* logname); @@ -79,6 +105,22 @@ class Load_log_processor { char target_dir_name[FN_REFLEN]; int target_dir_name_len; + + /* + When we see first event corresponding to some LOAD DATA statement in + binlog, we create temporary file to store data to be loaded. + We add name of this file to file_names array using its file_id as index. + If we have Create_file event (i.e. we have binary log in pre-5.0.3 + format) we also store save event object to be able which is needed to + emit LOAD DATA statement when we will meet Exec_load_data event. + If we have Begin_load_query event we simply store 0 in + File_name_record::event field. + */ + struct File_name_record + { + char *fname; + Create_file_log_event *event; + }; DYNAMIC_ARRAY file_names; /* @@ -120,7 +162,7 @@ public: int init() { - return init_dynamic_array(&file_names,sizeof(Create_file_log_event*), + return init_dynamic_array(&file_names, sizeof(File_name_record), 100,100 CALLER_INFO); } @@ -137,33 +179,91 @@ public: } void destroy() { - Create_file_log_event **ptr= (Create_file_log_event**)file_names.buffer; - Create_file_log_event **end= ptr + file_names.elements; + File_name_record *ptr= (File_name_record *)file_names.buffer; + File_name_record *end= ptr + file_names.elements; for (; ptr<end; ptr++) { - if (*ptr) + if (ptr->fname) { - my_free((char*)(*ptr)->fname,MYF(MY_WME)); - delete *ptr; - *ptr= 0; + my_free(ptr->fname, MYF(MY_WME)); + delete ptr->event; + bzero((char *)ptr, sizeof(File_name_record)); } } } + + /* + Obtain Create_file event for LOAD DATA statement by its file_id. + + SYNOPSIS + grab_event() + file_id - file_id identifiying LOAD DATA statement + + DESCRIPTION + Checks whenever we have already seen Create_file event for this file_id. + If yes then returns pointer to it and removes it from array describing + active temporary files. Since this moment caller is responsible for + freeing memory occupied by this event and associated file name. + + RETURN VALUES + Pointer to Create_file event or 0 if there was no such event + with this file_id. + */ Create_file_log_event *grab_event(uint file_id) { + File_name_record *ptr; + Create_file_log_event *res; + if (file_id >= file_names.elements) return 0; - Create_file_log_event **ptr= - (Create_file_log_event**)file_names.buffer + file_id; - Create_file_log_event *res= *ptr; - *ptr= 0; + ptr= dynamic_element(&file_names, file_id, File_name_record*); + if ((res= ptr->event)) + bzero((char *)ptr, sizeof(File_name_record)); + return res; + } + + /* + Obtain file name of temporary file for LOAD DATA statement by its file_id. + + SYNOPSIS + grab_fname() + file_id - file_id identifiying LOAD DATA statement + + DESCRIPTION + Checks whenever we have already seen Begin_load_query event for this + file_id. If yes then returns file name of corresponding temporary file. + Removes record about this file from the array of active temporary files. + Since this moment caller is responsible for freeing memory occupied by + this name. + + RETURN VALUES + String with name of temporary file or 0 if we have not seen Begin_load_query + event with this file_id. + */ + char *grab_fname(uint file_id) + { + File_name_record *ptr; + char *res= 0; + + if (file_id >= file_names.elements) + return 0; + ptr= dynamic_element(&file_names, file_id, File_name_record*); + if (!ptr->event) + { + res= ptr->fname; + bzero((char *)ptr, sizeof(File_name_record)); + } return res; } int process(Create_file_log_event *ce); + int process(Begin_load_query_log_event *ce); int process(Append_block_log_event *ae); File prepare_new_file_for_old_format(Load_log_event *le, char *filename); int load_old_format_file(NET* net, const char *server_fname, uint server_fname_len, File file); + int process_first_event(const char *bname, uint blen, const char *block, + uint block_len, uint file_id, + Create_file_log_event *ce); }; @@ -241,22 +341,42 @@ int Load_log_processor::load_old_format_file(NET* net, const char*server_fname, } -int Load_log_processor::process(Create_file_log_event *ce) +/* + Process first event in the sequence of events representing LOAD DATA + statement. + + SYNOPSIS + process_first_event() + bname - base name for temporary file to be created + blen - base name length + block - first block of data to be loaded + block_len - first block length + file_id - identifies LOAD DATA statement + ce - pointer to Create_file event object if we are processing + this type of event. + + DESCRIPTION + Creates temporary file to be used in LOAD DATA and writes first block of + data to it. Registers its file name (and optional Create_file event) + in the array of active temporary files. + + RETURN VALUES + 0 - success + non-0 - error +*/ + +int Load_log_processor::process_first_event(const char *bname, uint blen, + const char *block, uint block_len, + uint file_id, + Create_file_log_event *ce) { - const char *bname= ce->fname+dirname_length(ce->fname); - uint blen= ce->fname_len - (bname-ce->fname); uint full_len= target_dir_name_len + blen + 9 + 9 + 1; int error= 0; char *fname, *ptr; File file; - DBUG_ENTER("Load_log_processor::process"); + File_name_record rec; + DBUG_ENTER("Load_log_processor::process_first_event"); - if (set_dynamic(&file_names,(gptr)&ce,ce->file_id)) - { - sql_print_error("Could not construct local filename %s%s", - target_dir_name,bname); - DBUG_RETURN(-1); - } if (!(fname= my_malloc(full_len,MYF(MY_WME)))) DBUG_RETURN(-1); @@ -264,7 +384,7 @@ int Load_log_processor::process(Create_file_log_event *ce) ptr= fname + target_dir_name_len; memcpy(ptr,bname,blen); ptr+= blen; - ptr+= my_sprintf(ptr,(ptr,"-%x",ce->file_id)); + ptr+= my_sprintf(ptr, (ptr, "-%x", file_id)); if ((file= create_unique_file(fname,ptr)) < 0) { @@ -272,9 +392,21 @@ int Load_log_processor::process(Create_file_log_event *ce) target_dir_name,bname); DBUG_RETURN(-1); } - ce->set_fname_outside_temp_buf(fname,strlen(fname)); - if (my_write(file,(byte*) ce->block,ce->block_len,MYF(MY_WME|MY_NABP))) + rec.fname= fname; + rec.event= ce; + + if (set_dynamic(&file_names, (gptr)&rec, file_id)) + { + sql_print_error("Could not construct local filename %s%s", + target_dir_name, bname); + DBUG_RETURN(-1); + } + + if (ce) + ce->set_fname_outside_temp_buf(fname, strlen(fname)); + + if (my_write(file, (byte*)block, block_len, MYF(MY_WME|MY_NABP))) error= -1; if (my_close(file, MYF(MY_WME))) error= -1; @@ -282,19 +414,35 @@ int Load_log_processor::process(Create_file_log_event *ce) } +int Load_log_processor::process(Create_file_log_event *ce) +{ + const char *bname= ce->fname + dirname_length(ce->fname); + uint blen= ce->fname_len - (bname-ce->fname); + + return process_first_event(bname, blen, ce->block, ce->block_len, + ce->file_id, ce); +} + + +int Load_log_processor::process(Begin_load_query_log_event *blqe) +{ + return process_first_event("SQL_LOAD_MB", 11, blqe->block, blqe->block_len, + blqe->file_id, 0); +} + + int Load_log_processor::process(Append_block_log_event *ae) { DBUG_ENTER("Load_log_processor::process"); - Create_file_log_event* ce= ((ae->file_id < file_names.elements) ? - *((Create_file_log_event**)file_names.buffer + - ae->file_id) : - 0); + const char* fname= ((ae->file_id < file_names.elements) ? + dynamic_element(&file_names, ae->file_id, + File_name_record*)->fname : 0); - if (ce) + if (fname) { File file; int error= 0; - if (((file= my_open(ce->fname, + if (((file= my_open(fname, O_APPEND|O_BINARY|O_WRONLY,MYF(MY_WME))) < 0)) DBUG_RETURN(-1); if (my_write(file,(byte*)ae->block,ae->block_len,MYF(MY_WME|MY_NABP))) @@ -306,8 +454,8 @@ int Load_log_processor::process(Append_block_log_event *ae) /* There is no Create_file event (a bad binlog or a big - --position). Assuming it's a big --position, we just do nothing and - print a warning. + --start-position). Assuming it's a big --start-position, we just do + nothing and print a warning. */ fprintf(stderr,"Warning: ignoring Append_block as there is no \ Create_file event for file_id: %u\n",ae->file_id); @@ -317,30 +465,58 @@ Create_file event for file_id: %u\n",ae->file_id); Load_log_processor load_processor; + +static bool check_database(const char *log_dbname) +{ + return one_database && + (log_dbname != NULL) && + strcmp(log_dbname, database); +} + + /* + Process an event + + SYNOPSIS + process_event() + RETURN 0 ok and continue 1 error and terminate -1 ok and terminate - + TODO This function returns 0 even in some error cases. This should be changed. */ -int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format) + + + +int process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, + my_off_t pos) { char ll_buff[21]; + Log_event_type ev_type= ev->get_type_code(); DBUG_ENTER("process_event"); + print_event_info->short_form= short_form; + /* + Format events are not concerned by --offset and such, we always need to + read them to be able to process the wanted events. + */ if ((rec_count >= offset) && - ((my_time_t)(ev->when) >= start_datetime)) + ((my_time_t)(ev->when) >= start_datetime) || + (ev_type == FORMAT_DESCRIPTION_EVENT)) { - /* - We have found an event after start_datetime, from now on print - everything (in case the binlog has timestamps increasing and decreasing, - we do this to avoid cutting the middle). - */ - start_datetime= 0; - offset= 0; // print everything and protect against cycling rec_count + if (ev_type != FORMAT_DESCRIPTION_EVENT) + { + /* + We have found an event after start_datetime, from now on print + everything (in case the binlog has timestamps increasing and + decreasing, we do this to avoid cutting the middle). + */ + start_datetime= 0; + offset= 0; // print everything and protect against cycling rec_count + } if (((my_time_t)(ev->when) >= stop_datetime) || (pos >= stop_position_mot)) { @@ -349,32 +525,29 @@ int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format) } if (!short_form) fprintf(result_file, "# at %s\n",llstr(pos,ll_buff)); - - switch (ev->get_type_code()) { + + if (!opt_hexdump) + print_event_info->hexdump_from= 0; /* Disabled */ + else + print_event_info->hexdump_from= pos; + + switch (ev_type) { case QUERY_EVENT: - if (one_database) - { - const char * log_dbname = ((Query_log_event*)ev)->db; - if ((log_dbname != NULL) && (strcmp(log_dbname, database))) - goto end; - } - ev->print(result_file, short_form, last_db); + if (check_database(((Query_log_event*)ev)->db)) + goto end; + ev->print(result_file, print_event_info); break; case CREATE_FILE_EVENT: { Create_file_log_event* ce= (Create_file_log_event*)ev; - if (one_database) - { - /* - We test if this event has to be ignored. If yes, we don't save - this event; this will have the good side-effect of ignoring all - related Append_block and Exec_load. - Note that Load event from 3.23 is not tested. - */ - const char * log_dbname = ce->db; - if ((log_dbname != NULL) && (strcmp(log_dbname, database))) - goto end; // Next event - } + /* + We test if this event has to be ignored. If yes, we don't save + this event; this will have the good side-effect of ignoring all + related Append_block and Exec_load. + Note that Load event from 3.23 is not tested. + */ + if (check_database(ce->db)) + goto end; // Next event /* We print the event, but with a leading '#': this is just to inform the user of the original command; the command we want to execute @@ -382,8 +555,10 @@ int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format) filename and use LOCAL), prepared in the 'case EXEC_LOAD_EVENT' below. */ - ce->print(result_file, short_form, last_db, TRUE); - if (!old_format) + ce->print(result_file, print_event_info, TRUE); + + // If this binlog is not 3.23 ; why this test?? + if (description_event->binlog_version >= 3) { if (load_processor.process(ce)) break; // Error @@ -392,23 +567,23 @@ int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format) break; } case APPEND_BLOCK_EVENT: - ev->print(result_file, short_form, last_db); + ev->print(result_file, print_event_info); if (load_processor.process((Append_block_log_event*) ev)) break; // Error break; case EXEC_LOAD_EVENT: { - ev->print(result_file, short_form, last_db); + ev->print(result_file, print_event_info); Execute_load_log_event *exv= (Execute_load_log_event*)ev; Create_file_log_event *ce= load_processor.grab_event(exv->file_id); /* if ce is 0, it probably means that we have not seen the Create_file - event (a bad binlog, or most probably --position is after the + event (a bad binlog, or most probably --start-position is after the Create_file event). Print a warning comment. */ if (ce) { - ce->print(result_file, short_form, last_db, TRUE); + ce->print(result_file, print_event_info, TRUE); my_free((char*)ce->fname,MYF(MY_WME)); delete ce; } @@ -417,8 +592,47 @@ int process_event(char *last_db, Log_event *ev, my_off_t pos, int old_format) Create_file event for file_id: %u\n",exv->file_id); break; } + case FORMAT_DESCRIPTION_EVENT: + delete description_event; + description_event= (Format_description_log_event*) ev; + print_event_info->common_header_len= description_event->common_header_len; + ev->print(result_file, print_event_info); + /* + We don't want this event to be deleted now, so let's hide it (I + (Guilhem) should later see if this triggers a non-serious Valgrind + error). Not serious error, because we will free description_event + later. + */ + ev= 0; + break; + case BEGIN_LOAD_QUERY_EVENT: + ev->print(result_file, print_event_info); + load_processor.process((Begin_load_query_log_event*) ev); + break; + case EXECUTE_LOAD_QUERY_EVENT: + { + Execute_load_query_log_event *exlq= (Execute_load_query_log_event*)ev; + char *fname= load_processor.grab_fname(exlq->file_id); + + if (check_database(exlq->db)) + { + if (fname) + my_free(fname, MYF(MY_WME)); + goto end; + } + + if (fname) + { + exlq->print(result_file, print_event_info, fname); + my_free(fname, MYF(MY_WME)); + } + else + fprintf(stderr,"Warning: ignoring Execute_load_query as there is no \ +Begin_load_query event for file_id: %u\n", exlq->file_id); + break; + } default: - ev->print(result_file, short_form, last_db); + ev->print(result_file, print_event_info); } } @@ -437,6 +651,15 @@ static struct my_option my_long_options[] = {"autoclose", OPT_AUTO_CLOSE, "Auto close the screen on exit for Netware.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, #endif + /* + mysqlbinlog needs charsets knowledge, to be able to convert a charset + number found in binlog to a charset name (to be able to print things + like this: + SET @`a`:=_cp850 0x4DFC6C6C6572 COLLATE `cp850_general_ci`; + */ + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory where character sets are.", (gptr*) &charsets_dir, + (gptr*) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #ifndef DBUG_OFF {"debug", '#', "Output debug log.", (gptr*) &default_dbug_option, (gptr*) &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, @@ -456,6 +679,9 @@ static struct my_option my_long_options[] = 0, 0}, {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"hexdump", 'H', "Augment output with hexadecimal and ASCII event dump.", + (gptr*) &opt_hexdump, (gptr*) &opt_hexdump, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, {"host", 'h', "Get the binlog from server.", (gptr*) &host, (gptr*) &host, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"offset", 'o', "Skip the first N entries.", (gptr*) &offset, (gptr*) &offset, @@ -463,7 +689,7 @@ static struct my_option my_long_options[] = {"password", 'p', "Password to connect to remote server.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Use port to connect to the remote server.", - (gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, MYSQL_PORT, 0, 0, + (gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"position", 'j', "Deprecated. Use --start-position instead.", (gptr*) &start_position, (gptr*) &start_position, 0, GET_ULL, @@ -566,7 +792,8 @@ static void die(const char* fmt, ...) fprintf(stderr, "\n"); va_end(args); cleanup(); - my_end(0); + /* We cannot free DBUG, it is used in global destructors after exit(). */ + my_end(MY_DONT_FREE_DBUG); exit(1); } @@ -574,7 +801,7 @@ static void die(const char* fmt, ...) static void print_version() { - printf("%s Ver 3.0 for %s at %s\n", my_progname, SYSTEM_TYPE, MACHINE_TYPE); + printf("%s Ver 3.1 for %s at %s\n", my_progname, SYSTEM_TYPE, MACHINE_TYPE); NETWARE_SET_SCREEN_MODE(1); } @@ -600,7 +827,7 @@ static my_time_t convert_str_to_timestamp(const char* str) int was_cut; MYSQL_TIME l_time; long dummy_my_timezone; - bool dummy_in_dst_time_gap; + my_bool dummy_in_dst_time_gap; /* We require a total specification (date AND time) */ if (str_to_datetime(str, strlen(str), &l_time, 0, &was_cut) != MYSQL_TIMESTAMP_DATETIME || was_cut) @@ -715,6 +942,7 @@ static MYSQL* safe_connect() mysql_close(local_mysql); die("failed on connect: %s", errmsg); } + local_mysql->reconnect= 1; return local_mysql; } @@ -726,12 +954,17 @@ static int dump_log_entries(const char* logname) } -static int check_master_version(MYSQL* mysql) +/* + This is not as smart as check_header() (used for local log); it will not work + for a binlog which mixes format. TODO: fix this. +*/ +static int check_master_version(MYSQL* mysql, + Format_description_log_event + **description_event) { MYSQL_RES* res = 0; MYSQL_ROW row; const char* version; - int old_format = 0; if (mysql_query(mysql, "SELECT VERSION()") || !(res = mysql_store_result(mysql))) @@ -757,11 +990,18 @@ static int check_master_version(MYSQL* mysql) switch (*version) { case '3': - old_format = 1; + *description_event= new Format_description_log_event(1); break; case '4': + *description_event= new Format_description_log_event(3); case '5': - old_format = 0; + /* + The server is soon going to send us its Format_description log + event, unless it is a 5.0 server with 3.23 or 4.0 binlogs. + So we first assume that this is 4.0 (which is enough to read the + Format_desc event if one comes). + */ + *description_event= new Format_description_log_event(3); break; default: sql_print_error("Master reported unrecognized MySQL version '%s'", @@ -771,18 +1011,18 @@ static int check_master_version(MYSQL* mysql) return 1; } mysql_free_result(res); - return old_format; + return 0; } static int dump_remote_log_entries(const char* logname) + { char buf[128]; - char last_db[FN_REFLEN+1] = ""; + PRINT_EVENT_INFO print_event_info; ulong len; uint logname_len; NET* net; - int old_format; int error= 0; my_off_t old_off= start_position_mot; char fname[FN_REFLEN+1]; @@ -795,7 +1035,18 @@ static int dump_remote_log_entries(const char* logname) */ mysql= safe_connect(); net= &mysql->net; - old_format = check_master_version(mysql); + + if (check_master_version(mysql, &description_event)) + { + fprintf(stderr, "Could not find server version"); + DBUG_RETURN(1); + } + if (!description_event || !description_event->is_valid()) + { + fprintf(stderr, "Invalid Format_description log event; \ +could be out of memory"); + DBUG_RETURN(1); + } /* COM_BINLOG_DUMP accepts only 4 bytes for the position, so we are forced to @@ -824,6 +1075,8 @@ static int dump_remote_log_entries(const char* logname) for (;;) { const char *error_msg; + Log_event *ev; + len = net_safe_read(mysql); if (len == packet_error) { @@ -836,9 +1089,9 @@ static int dump_remote_log_entries(const char* logname) break; // end of data DBUG_PRINT("info",( "len= %u, net->read_pos[5] = %d\n", len, net->read_pos[5])); - Log_event *ev = Log_event::read_log_event((const char*) net->read_pos + 1 , - len - 1, &error_msg, old_format); - if (!ev) + if (!(ev= Log_event::read_log_event((const char*) net->read_pos + 1 , + len - 1, &error_msg, + description_event))) { fprintf(stderr, "Could not construct log event object\n"); error= 1; @@ -846,25 +1099,27 @@ static int dump_remote_log_entries(const char* logname) } Log_event_type type= ev->get_type_code(); - if (!old_format || ( type != LOAD_EVENT && type != CREATE_FILE_EVENT)) + if (description_event->binlog_version >= 3 || + (type != LOAD_EVENT && type != CREATE_FILE_EVENT)) { - if (ev->get_type_code() == ROTATE_EVENT) + /* + If this is a Rotate event, maybe it's the end of the requested binlog; + in this case we are done (stop transfer). + This is suitable for binlogs, not relay logs (but for now we don't read + relay logs remotely because the server is not able to do that). If one + day we read relay logs remotely, then we will have a problem with the + detection below: relay logs contain Rotate events which are about the + binlogs, so which would trigger the end-detection below. + */ + if (type == ROTATE_EVENT) { Rotate_log_event *rev= (Rotate_log_event *)ev; /* - mysqld is sending us all its binlogs after the requested one, but we - don't want them. If this is a fake Rotate event, and not about our log, we can stop transfer. If this a real Rotate event (so it's not about our log, it's in our log describing the next log), we print it (because it's part of our log) and then we will stop when we receive the fake one soon. - This is suitable for binlogs, not relay logs (but for now we don't - read relay logs remotely because the server is not able to do - that). If one day we read relay logs remotely, then we will have a - problem with the detection below: relay logs contain Rotate events - which are about the binlogs, so which would trigger the end-detection - below. */ if (rev->when == 0) { @@ -887,7 +1142,7 @@ static int dump_remote_log_entries(const char* logname) len= 1; // fake Rotate, so don't increment old_off } } - if ((error= process_event(last_db,ev,old_off,old_format))) + if ((error= process_event(&print_event_info, ev, old_off))) { error= ((error < 0) ? 0 : 1); goto err; @@ -899,64 +1154,136 @@ static int dump_remote_log_entries(const char* logname) const char *old_fname= le->fname; uint old_len= le->fname_len; File file; - + if ((file= load_processor.prepare_new_file_for_old_format(le,fname)) < 0) { error= 1; goto err; } - - if ((error= process_event(last_db,ev,old_off,old_format))) + + if ((error= process_event(&print_event_info, ev, old_off))) { - my_close(file,MYF(MY_WME)); + my_close(file,MYF(MY_WME)); error= ((error < 0) ? 0 : 1); goto err; } - if (load_processor.load_old_format_file(net,old_fname,old_len,file)) + error= load_processor.load_old_format_file(net,old_fname,old_len,file); + my_close(file,MYF(MY_WME)); + if (error) { - my_close(file,MYF(MY_WME)); error= 1; goto err; } - my_close(file,MYF(MY_WME)); } - /* Let's adjust offset for remote log as for local log to produce similar text. */ old_off+= len-1; } + err: mysql_close(mysql); DBUG_RETURN(error); } -static int check_header(IO_CACHE* file) +static void check_header(IO_CACHE* file, + Format_description_log_event **description_event) { byte header[BIN_LOG_HEADER_SIZE]; byte buf[PROBE_HEADER_LEN]; - int old_format=0; - DBUG_ENTER("check_header"); + my_off_t tmp_pos, pos; - my_off_t pos = my_b_tell(file); + *description_event= new Format_description_log_event(3); + pos= my_b_tell(file); my_b_seek(file, (my_off_t)0); if (my_b_read(file, header, sizeof(header))) die("Failed reading header; Probably an empty file"); if (memcmp(header, BINLOG_MAGIC, sizeof(header))) die("File is not a binary log file"); - if (!my_b_read(file, buf, sizeof(buf))) + + /* + Imagine we are running with --start-position=1000. We still need + to know the binlog format's. So we still need to find, if there is + one, the Format_desc event, or to know if this is a 3.23 + binlog. So we need to first read the first events of the log, + those around offset 4. Even if we are reading a 3.23 binlog from + the start (no --start-position): we need to know the header length + (which is 13 in 3.23, 19 in 4.x) to be able to successfully print + the first event (Start_log_event_v3). So even in this case, we + need to "probe" the first bytes of the log *before* we do a real + read_log_event(). Because read_log_event() needs to know the + header's length to work fine. + */ + for(;;) { - if (buf[4] == START_EVENT) + tmp_pos= my_b_tell(file); /* should be 4 the first time */ + if (my_b_read(file, buf, sizeof(buf))) { - uint event_len; - event_len = uint4korr(buf + EVENT_LEN_OFFSET); - old_format = (event_len < (LOG_EVENT_HEADER_LEN + START_HEADER_LEN)); + if (file->error) + die("\ +Could not read entry at offset %lu : Error in log format or read error", + tmp_pos); + /* + Otherwise this is just EOF : this log currently contains 0-2 + events. Maybe it's going to be filled in the next + milliseconds; then we are going to have a problem if this a + 3.23 log (imagine we are locally reading a 3.23 binlog which + is being written presently): we won't know it in + read_log_event() and will fail(). Similar problems could + happen with hot relay logs if --start-position is used (but a + --start-position which is posterior to the current size of the log). + These are rare problems anyway (reading a hot log + when we + read the first events there are not all there yet + when we + read a bit later there are more events + using a strange + --start-position). + */ + break; + } + else + { + DBUG_PRINT("info",("buf[4]=%d", buf[4])); + /* always test for a Start_v3, even if no --start-position */ + if (buf[4] == START_EVENT_V3) /* This is 3.23 or 4.x */ + { + if (uint4korr(buf + EVENT_LEN_OFFSET) < + (LOG_EVENT_MINIMAL_HEADER_LEN + START_V3_HEADER_LEN)) + { + /* This is 3.23 (format 1) */ + delete *description_event; + *description_event= new Format_description_log_event(1); + } + break; + } + else if (tmp_pos >= start_position) + break; + else if (buf[4] == FORMAT_DESCRIPTION_EVENT) /* This is 5.0 */ + { + my_b_seek(file, tmp_pos); /* seek back to event's start */ + if (!(*description_event= (Format_description_log_event*) + Log_event::read_log_event(file, *description_event))) + /* EOF can't be hit here normally, so it's a real error */ + die("Could not read a Format_description_log_event event \ +at offset %lu ; this could be a log format error or read error", + tmp_pos); + DBUG_PRINT("info",("Setting description_event")); + } + else if (buf[4] == ROTATE_EVENT) + { + Log_event *ev; + my_b_seek(file, tmp_pos); /* seek back to event's start */ + if (!(ev= Log_event::read_log_event(file, *description_event))) + /* EOF can't be hit here normally, so it's a real error */ + die("Could not read a Rotate_log_event event at offset %lu ;" + " this could be a log format error or read error", tmp_pos); + delete ev; + } + else + break; } } my_b_seek(file, pos); - DBUG_RETURN(old_format); } @@ -964,13 +1291,10 @@ static int dump_local_log_entries(const char* logname) { File fd = -1; IO_CACHE cache,*file= &cache; - char last_db[FN_REFLEN+1]; + PRINT_EVENT_INFO print_event_info; byte tmp_buff[BIN_LOG_HEADER_SIZE]; - bool old_format = 0; int error= 0; - last_db[0]= 0; - if (logname && logname[0] != '-') { if ((fd = my_open(logname, O_RDONLY | O_BINARY, MYF(MY_WME))) < 0) @@ -979,65 +1303,67 @@ static int dump_local_log_entries(const char* logname) MYF(MY_WME | MY_NABP))) { my_close(fd, MYF(MY_WME)); - exit(1); + return 1; } - old_format = check_header(file); + check_header(file, &description_event); } - else + else // reading from stdin; { - if (init_io_cache(file, fileno(result_file), 0, READ_CACHE, (my_off_t) 0, + if (init_io_cache(file, fileno(stdin), 0, READ_CACHE, (my_off_t) 0, 0, MYF(MY_WME | MY_NABP | MY_DONT_CHECK_FILESIZE))) return 1; - old_format = check_header(file); + check_header(file, &description_event); if (start_position) { - /* skip 'start_position' characters from stdout */ + /* skip 'start_position' characters from stdin */ byte buff[IO_SIZE]; my_off_t length,tmp; for (length= start_position_mot ; length > 0 ; length-=tmp) { tmp=min(length,sizeof(buff)); if (my_b_read(file, buff, (uint) tmp)) - { - error= 1; - goto end; - } + { + error= 1; + goto end; + } } } - file->pos_in_file= start_position_mot; - file->seek_not_done=0; } - if (!start_position) + if (!description_event || !description_event->is_valid()) + die("Invalid Format_description log event; could be out of memory"); + + if (!start_position && my_b_read(file, tmp_buff, BIN_LOG_HEADER_SIZE)) { - // Skip header - if (my_b_read(file, tmp_buff, BIN_LOG_HEADER_SIZE)) - { - error= 1; - goto end; - } + error= 1; + goto end; } - for (;;) { char llbuff[21]; my_off_t old_off = my_b_tell(file); - Log_event* ev = Log_event::read_log_event(file, old_format); + Log_event* ev = Log_event::read_log_event(file, description_event); if (!ev) { - if (file->error) + /* + if binlog wasn't closed properly ("in use" flag is set) don't complain + about a corruption, but treat it as EOF and move to the next binlog. + */ + if (description_event->flags & LOG_EVENT_BINLOG_IN_USE_F) + file->error= 0; + else if (file->error) { - fprintf(stderr, - "Could not read entry at offset %s:" - "Error in log format or read error\n", - llstr(old_off,llbuff)); - error= 1; + fprintf(stderr, + "Could not read entry at offset %s:" + "Error in log format or read error\n", + llstr(old_off,llbuff)); + error= 1; } // file->error == 0 means EOF, that's OK, we break in this case break; } - if ((error= process_event(last_db,ev,old_off,false))) + if ((error= process_event(&print_event_info, ev, old_off))) { if (error < 0) error= 0; @@ -1049,6 +1375,7 @@ end: if (fd >= 0) my_close(fd, MYF(MY_WME)); end_io_cache(file); + delete description_event; return error; } @@ -1099,6 +1426,14 @@ int main(int argc, char** argv) fprintf(result_file, "/*!32316 SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0*/;\n"); + /* + In mysqlbinlog|mysql, don't want mysql to be disconnected after each + transaction (which would be the case with GLOBAL.COMPLETION_TYPE==2). + */ + fprintf(result_file, + "/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE," + "COMPLETION_TYPE=0*/;\n"); + if (charset) fprintf(result_file, "\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" @@ -1120,6 +1455,13 @@ int main(int argc, char** argv) start_position= BIN_LOG_HEADER_SIZE; } + /* + Issue a ROLLBACK in case the last printed binlog was crashed and had half + of transaction. + */ + fprintf(result_file, + "# End of log file\nROLLBACK /* added by mysqlbinlog */;\n" + "/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;\n"); if (disable_log_bin) fprintf(result_file, "/*!32316 SET SQL_LOG_BIN=@OLD_SQL_LOG_BIN*/;\n"); @@ -1136,7 +1478,8 @@ int main(int argc, char** argv) cleanup(); free_defaults(defaults_argv); my_free_open_file_info(); - my_end(0); + /* We cannot free DBUG, it is used in global destructors after exit(). */ + my_end(MY_DONT_FREE_DBUG); exit(exit_value); DBUG_RETURN(exit_value); // Keep compilers happy } @@ -1147,9 +1490,14 @@ int main(int argc, char** argv) */ #ifdef __WIN__ +#include "my_decimal.h" +#include "decimal.c" +#include "my_decimal.cpp" #include "log_event.cpp" #else +#include "my_decimal.h" +#include "decimal.c" +#include "my_decimal.cc" #include "log_event.cc" #endif -FIX_GCC_LINKING_PROBLEM diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c index f0f7c247553..804fa14956f 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -34,7 +34,7 @@ static my_bool opt_alldbs = 0, opt_check_only_changed = 0, opt_extended = 0, opt_compress = 0, opt_databases = 0, opt_fast = 0, opt_medium_check = 0, opt_quick = 0, opt_all_in_1 = 0, opt_silent = 0, opt_auto_repair = 0, ignore_errors = 0, - tty_password = 0, opt_frm = 0; + tty_password = 0, opt_frm = 0, opt_upgrade= 0; static uint verbose = 0, opt_mysql_port=0; static my_string opt_mysql_unix_port = 0; static char *opt_password = 0, *current_user = 0, @@ -78,6 +78,9 @@ static struct my_option my_long_options[] = {"check-only-changed", 'C', "Check only tables that have changed since last check or haven't been closed properly.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"check-upgrade", 'g', + "Check tables for version-dependent changes. May be used with --auto-repair to correct tables requiring version-dependent updates.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"compress", OPT_COMPRESS, "Use compression in server/client protocol.", (gptr*) &opt_compress, (gptr*) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -122,7 +125,7 @@ static struct my_option my_long_options[] = NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, - (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe,memory).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -146,14 +149,14 @@ static struct my_option my_long_options[] = #include <sslopt-longopts.h> {"tables", OPT_TABLES, "Overrides option --databases (-B).", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, -#ifndef DONT_ALLOW_USER_CHANGE - {"user", 'u', "User for login if not current user.", (gptr*) ¤t_user, - (gptr*) ¤t_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, -#endif {"use-frm", OPT_FRM, "When used with REPAIR, get table structure from .frm file, so the table can be repaired even if .MYI header is corrupted.", (gptr*) &opt_frm, (gptr*) &opt_frm, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", (gptr*) ¤t_user, + (gptr*) ¤t_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif {"verbose", 'v', "Print info about the various stages.", 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, @@ -268,6 +271,10 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), case 'r': what_to_do = DO_REPAIR; break; + case 'g': + what_to_do= DO_CHECK; + opt_upgrade= 1; + break; case 'W': #ifdef __WIN__ opt_protocol = MYSQL_PROTOCOL_PIPE; @@ -497,6 +504,9 @@ static int process_all_tables_in_db(char *database) static int use_db(char *database) { + if (mysql_get_server_version(sock) >= 50003 && + !my_strcasecmp(&my_charset_latin1, database, "information_schema")) + return 1; if (mysql_select_db(sock, database)) { DBerror(sock, "when selecting the database"); @@ -522,6 +532,7 @@ static int handle_request_for_tables(char *tables, uint length) if (opt_medium_check) end = strmov(end, " MEDIUM"); /* Default */ if (opt_extended) end = strmov(end, " EXTENDED"); if (opt_check_only_changed) end = strmov(end, " CHANGED"); + if (opt_upgrade) end = strmov(end, " FOR UPGRADE"); break; case DO_REPAIR: op = "REPAIR"; @@ -643,6 +654,7 @@ static int dbConnect(char *host, char *user, char *passwd) DBerror(&mysql_connection, "when trying to connect"); return 1; } + mysql_connection.reconnect= 1; return 0; } /* dbConnect */ diff --git a/client/mysqldump.c b/client/mysqldump.c index 58e51b9b955..a59c6746134 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -21,7 +21,7 @@ ** AUTHOR: Igor Romanenko (igor@frog.kiev.ua) ** DATE: December 3, 1994 ** WARRANTY: None, expressed, impressed, implied -** or other +** or other ** STATUS: Public domain ** Adapted and optimized for MySQL by ** Michael Widenius, Sinisa Milivojevic, Jani Tolonen @@ -30,17 +30,18 @@ ** master/autocommit code by Brian Aker <brian@tangent.org> ** SSL by ** Andrei Errapart <andreie@no.spam.ee> -** Tõnu Samuel <tonu@please.do.not.remove.this.spam.ee> +** Tõnu Samuel <tonu@please.do.not.remove.this.spam.ee> ** XML by Gary Huntress <ghuntress@mediaone.net> 10/10/01, cleaned up ** and adapted to mysqldump 05/11/01 by Jani Tolonen ** Added --single-transaction option 06/06/2002 by Peter Zaitsev ** 10 Jun 2003: SET NAMES and --no-set-names by Alexander Barkov */ -#define DUMP_VERSION "10.9" +#define DUMP_VERSION "10.10" #include <my_global.h> #include <my_sys.h> +#include <my_user.h> #include <m_string.h> #include <m_ctype.h> #include <hash.h> @@ -70,23 +71,30 @@ /* Size of buffer for dump's select query */ #define QUERY_LENGTH 1536 +/* ignore table flags */ +#define IGNORE_NONE 0x00 /* no ignore */ +#define IGNORE_DATA 0x01 /* don't dump data for this table */ +#define IGNORE_INSERT_DELAYED 0x02 /* table doesn't support INSERT DELAYED */ + static char *add_load_option(char *ptr, const char *object, - const char *statement); + const char *statement); static ulong find_set(TYPELIB *lib, const char *x, uint length, - char **err_pos, uint *err_len); + char **err_pos, uint *err_len); +static char *alloc_query_str(ulong size); static char *field_escape(char *to,const char *from,uint length); static my_bool verbose=0,tFlag=0,dFlag=0,quick= 1, extended_insert= 1, - lock_tables=1,ignore_errors=0,flush_logs=0, - opt_drop=1,opt_keywords=0,opt_lock=1,opt_compress=0, + lock_tables=1,ignore_errors=0,flush_logs=0, + opt_drop=1,opt_keywords=0,opt_lock=1,opt_compress=0, opt_delayed=0,create_options=1,opt_quoted=0,opt_databases=0, opt_alldbs=0,opt_create_db=0,opt_lock_all_tables=0, opt_set_charset=0, - opt_autocommit=0,opt_disable_keys=1,opt_xml=0, - opt_delete_master_logs=0, tty_password=0, - opt_single_transaction=0, opt_comments= 0, opt_compact= 0, - opt_hex_blob=0, opt_order_by_primary=0, opt_ignore=0, - opt_complete_insert= 0, opt_drop_database= 0; + opt_autocommit=0,opt_disable_keys=1,opt_xml=0, + opt_delete_master_logs=0, tty_password=0, + opt_single_transaction=0, opt_comments= 0, opt_compact= 0, + opt_hex_blob=0, opt_order_by_primary=0, opt_ignore=0, + opt_complete_insert= 0, opt_drop_database= 0, + opt_dump_triggers= 0, opt_routines=0, opt_tz_utc=1; static ulong opt_max_allowed_packet, opt_net_buffer_length; static MYSQL mysql_connection,*sock=0; static my_bool insert_pat_inited=0; @@ -121,6 +129,8 @@ static const char *mysql_universal_client_charset= static char *default_charset; static CHARSET_INFO *charset_info= &my_charset_latin1; const char *default_dbug_option="d:t:o,/tmp/mysqldump.trace"; +/* have we seen any VIEWs during table scanning? */ +my_bool seen_views= 0; const char *compatible_mode_names[]= { @@ -139,7 +149,7 @@ const char *compatible_mode_names[]= (1<<10) /* ANSI */\ ) TYPELIB compatible_mode_typelib= {array_elements(compatible_mode_names) - 1, - "", compatible_mode_names, NULL}; + "", compatible_mode_names, NULL}; HASH ignore_table; @@ -206,7 +216,7 @@ static struct my_option my_long_options[] = {"default-character-set", OPT_DEFAULT_CHARSET, "Set the default character set.", (gptr*) &default_charset, (gptr*) &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"delayed-insert", OPT_DELAYED, "Insert rows with INSERT DELAYED.", + {"delayed-insert", OPT_DELAYED, "Insert rows with INSERT DELAYED; ", (gptr*) &opt_delayed, (gptr*) &opt_delayed, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"delete-master-logs", OPT_DELETE_MASTER_LOGS, @@ -266,7 +276,7 @@ static struct my_option my_long_options[] = {"lines-terminated-by", OPT_LTB, "Lines in the i.file are terminated by ...", (gptr*) &lines_terminated, (gptr*) &lines_terminated, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"lock-all-tables", 'x', "Locks all tables across all databases. This " + {"lock-all-tables", 'x', "Locks all tables across all databases. This " "is achieved by taking a global read lock for the duration of the whole " "dump. Automatically turns --single-transaction and --lock-tables off.", (gptr*) &opt_lock_all_tables, (gptr*) &opt_lock_all_tables, 0, GET_BOOL, NO_ARG, @@ -287,7 +297,7 @@ static struct my_option my_long_options[] = GET_UINT, OPT_ARG, 0, 0, MYSQL_OPT_MASTER_DATA_COMMENTED_SQL, 0, 0, 0}, {"max_allowed_packet", OPT_MAX_ALLOWED_PACKET, "", (gptr*) &opt_max_allowed_packet, (gptr*) &opt_max_allowed_packet, 0, - GET_ULONG, REQUIRED_ARG, 24*1024*1024, 4096, + GET_ULONG, REQUIRED_ARG, 24*1024*1024, 4096, (longlong) 2L*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0}, {"net_buffer_length", OPT_NET_BUFFER_LENGTH, "", (gptr*) &opt_net_buffer_length, (gptr*) &opt_net_buffer_length, 0, @@ -322,7 +332,7 @@ static struct my_option my_long_options[] = NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, - (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe,memory).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -334,6 +344,9 @@ static struct my_option my_long_options[] = {"result-file", 'r', "Direct output to a given file. This option should be used in MSDOS, because it prevents new line '\\n' from being converted to '\\r\\n' (carriage return + line feed).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"routines", 'R', "Dump stored routines (functions and procedures).", + (gptr*) &opt_routines, (gptr*) &opt_routines, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, {"set-charset", OPT_SET_CHARSET, "Add 'SET NAMES default_character_set' to the output. Enabled by default; suppress with --skip-set-charset.", (gptr*) &opt_set_charset, (gptr*) &opt_set_charset, 0, GET_BOOL, NO_ARG, 1, @@ -371,6 +384,12 @@ static struct my_option my_long_options[] = (gptr*) &path, (gptr*) &path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"tables", OPT_TABLES, "Overrides option --databases (-B).", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"triggers", OPT_TRIGGERS, "Dump triggers for each dumped table", + (gptr*) &opt_dump_triggers, (gptr*) &opt_dump_triggers, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"tz-utc", OPT_TZ_UTC, + "SET TIME_ZONE='+00:00' at top of dump to allow dumping of TIMESTAMP data when a server has data in different time zones or data is being moved between servers with different time zones.", + (gptr*) &opt_tz_utc, (gptr*) &opt_tz_utc, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, #ifndef DONT_ALLOW_USER_CHANGE {"user", 'u', "User for login if not current user.", (gptr*) ¤t_user, (gptr*) ¤t_user, 0, GET_STR, REQUIRED_ARG, @@ -384,7 +403,7 @@ static struct my_option my_long_options[] = (gptr*) &where, (gptr*) &where, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"xml", 'X', "Dump a database as well formed XML.", 0, 0, 0, GET_NO_ARG, 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} + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; static const char *load_default_groups[]= { "mysqldump","client",0 }; @@ -392,25 +411,27 @@ static const char *load_default_groups[]= { "mysqldump","client",0 }; static void safe_exit(int error); static void write_header(FILE *sql_file, char *db_name); static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, - const char *prefix,const char *name, - int string_value); + const char *prefix,const char *name, + int string_value); static int dump_selected_tables(char *db, char **table_names, int tables); static int dump_all_tables_in_db(char *db); static int init_dumping(char *); static int dump_databases(char **); static int dump_all_databases(); static char *quote_name(const char *name, char *buff, my_bool force); -static const char *check_if_ignore_table(const char *table_name); +char check_if_ignore_table(const char *table_name, char *table_type); static char *primary_key_fields(const char *table_name); +static my_bool get_view_structure(char *table, char* db); +static my_bool dump_all_views_in_db(char *database); #include <help_start.h> /* exit with message if ferror(file) - + SYNOPSIS check_io() - file - checked file + file - checked file */ void check_io(FILE *file) @@ -418,6 +439,7 @@ void check_io(FILE *file) if (ferror(file)) { fprintf(stderr, "%s: Got errno %d on write\n", my_progname, errno); + ignore_errors= 0; /* We can't ignore this error */ safe_exit(EX_EOF); } } @@ -434,7 +456,7 @@ static void short_usage_sub(void) { printf("Usage: %s [OPTIONS] database [tables]\n", my_progname); printf("OR %s [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...]\n", - my_progname); + my_progname); printf("OR %s [OPTIONS] --all-databases [OPTIONS]\n", my_progname); NETWARE_SET_SCREEN_MODE(1); } @@ -479,12 +501,12 @@ static void write_header(FILE *sql_file, char *db_name) { fprintf(sql_file, "-- MySQL dump %s\n--\n", DUMP_VERSION); fprintf(sql_file, "-- Host: %s Database: %s\n", - current_host ? current_host : "localhost", db_name ? db_name : - ""); + current_host ? current_host : "localhost", db_name ? db_name : + ""); fputs("-- ------------------------------------------------------\n", - sql_file); + sql_file); fprintf(sql_file, "-- Server version\t%s\n", - mysql_get_server_info(&mysql_connection)); + mysql_get_server_info(&mysql_connection)); } if (opt_set_charset) fprintf(sql_file, @@ -492,6 +514,13 @@ static void write_header(FILE *sql_file, char *db_name) "\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" "\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" "\n/*!40101 SET NAMES %s */;\n",default_charset); + + if (opt_tz_utc) + { + fprintf(sql_file, "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n"); + fprintf(sql_file, "/*!40103 SET TIME_ZONE='+00:00' */;\n"); + } + if (!path) { fprintf(md_result_file,"\ @@ -500,10 +529,10 @@ static void write_header(FILE *sql_file, char *db_name) "); } fprintf(sql_file, - "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='%s%s%s' */;\n" - "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n", - path?"":"NO_AUTO_VALUE_ON_ZERO",compatible_mode_normal_str[0]==0?"":",", - compatible_mode_normal_str); + "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='%s%s%s' */;\n" + "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n", + path?"":"NO_AUTO_VALUE_ON_ZERO",compatible_mode_normal_str[0]==0?"":",", + compatible_mode_normal_str); check_io(sql_file); } } /* write_header */ @@ -518,6 +547,9 @@ static void write_footer(FILE *sql_file) } else if (!opt_compact) { + if (opt_tz_utc) + fprintf(sql_file,"/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n"); + fprintf(sql_file,"\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"); if (!path) { @@ -531,7 +563,7 @@ static void write_footer(FILE *sql_file) "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"); fprintf(sql_file, - "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"); + "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"); fputs("\n", sql_file); check_io(sql_file); } @@ -545,7 +577,7 @@ static void free_table_ent(char *key) byte* get_table_key(const char *entry, uint *length, - my_bool not_used __attribute__((unused))) + my_bool not_used __attribute__((unused))) { *length= strlen(entry); return (byte*) entry; @@ -562,7 +594,7 @@ void init_table_rule_hash(HASH* h) static my_bool get_one_option(int optid, const struct my_option *opt __attribute__((unused)), - char *argument) + char *argument) { switch (optid) { #ifdef __NETWARE__ @@ -576,9 +608,9 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), char *start=argument; my_free(opt_password,MYF(MY_ALLOW_ZERO_PTR)); opt_password=my_strdup(argument,MYF(MY_FAE)); - while (*argument) *argument++= 'x'; /* Destroy argument */ + while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) - start[1]=0; /* Cut length of argument */ + start[1]=0; /* Cut length of argument */ tty_password= 0; } else @@ -586,7 +618,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), break; case 'r': if (!(md_result_file = my_fopen(argument, O_WRONLY | FILE_BINARY, - MYF(MY_WME)))) + MYF(MY_WME)))) exit(1); break; case 'W': @@ -607,7 +639,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), case 'V': print_version(); exit(0); case 'X': opt_xml = 1; - extended_insert= opt_drop= opt_lock= + extended_insert= opt_drop= opt_lock= opt_disable_keys= opt_autocommit= opt_create_db= 0; break; case 'I': @@ -660,36 +692,36 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), opt_set_charset= 0; opt_compatible_mode_str= argument; opt_compatible_mode= find_set(&compatible_mode_typelib, - argument, strlen(argument), - &err_ptr, &err_len); + argument, strlen(argument), + &err_ptr, &err_len); if (err_len) { - strmake(buff, err_ptr, min(sizeof(buff), err_len)); - fprintf(stderr, "Invalid mode to --compatible: %s\n", buff); - exit(1); + strmake(buff, err_ptr, min(sizeof(buff), err_len)); + fprintf(stderr, "Invalid mode to --compatible: %s\n", buff); + exit(1); } #if !defined(DBUG_OFF) { - uint size_for_sql_mode= 0; - const char **ptr; - for (ptr= compatible_mode_names; *ptr; ptr++) - size_for_sql_mode+= strlen(*ptr); - size_for_sql_mode+= sizeof(compatible_mode_names)-1; - DBUG_ASSERT(sizeof(compatible_mode_normal_str)>=size_for_sql_mode); + uint size_for_sql_mode= 0; + const char **ptr; + for (ptr= compatible_mode_names; *ptr; ptr++) + size_for_sql_mode+= strlen(*ptr); + size_for_sql_mode+= sizeof(compatible_mode_names)-1; + DBUG_ASSERT(sizeof(compatible_mode_normal_str)>=size_for_sql_mode); } #endif mode= opt_compatible_mode; for (i= 0, mode= opt_compatible_mode; mode; mode>>= 1, i++) { - if (mode & 1) - { - end= strmov(end, compatible_mode_names[i]); - end= strmov(end, ","); - } + if (mode & 1) + { + end= strmov(end, compatible_mode_names[i]); + end= strmov(end, ","); + } } if (end!=compatible_mode_normal_str) - end[-1]= 0; - /* + end[-1]= 0; + /* Set charset to the default compiled value if it hasn't been reset yet by --default-character-set=xxx. */ @@ -701,8 +733,8 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), { if ((opt_protocol= find_type(argument, &sql_protocol_typelib,0)) <= 0) { - fprintf(stderr, "Unknown option to protocol: %s\n", argument); - exit(1); + fprintf(stderr, "Unknown option to protocol: %s\n", argument); + exit(1); } break; } @@ -728,12 +760,12 @@ static int get_options(int *argc, char ***argv) *mysql_params->p_net_buffer_length= opt_net_buffer_length; if (opt_delayed) - opt_lock=0; /* Can't have lock with delayed */ + opt_lock=0; /* Can't have lock with delayed */ if (!path && (enclosed || opt_enclosed || escaped || lines_terminated || - fields_terminated)) + fields_terminated)) { fprintf(stderr, - "%s: You must use option --tab with --fields-...\n", my_progname); + "%s: You must use option --tab with --fields-...\n", my_progname); return(1); } @@ -745,7 +777,7 @@ static int get_options(int *argc, char ***argv) fprintf(stderr, "%s: You can't use --single-transaction and " "--lock-all-tables at the same time.\n", my_progname); return(1); - } + } if (opt_master_data) opt_lock_all_tables= !opt_single_transaction; if (opt_single_transaction || opt_lock_all_tables) @@ -758,13 +790,13 @@ static int get_options(int *argc, char ***argv) if ((opt_databases || opt_alldbs) && path) { fprintf(stderr, - "%s: --databases or --all-databases can't be used with --tab.\n", - my_progname); + "%s: --databases or --all-databases can't be used with --tab.\n", + my_progname); return(1); } if (strcmp(default_charset, charset_info->csname) && - !(charset_info= get_charset_by_csname(default_charset, - MY_CS_PRIMARY, MYF(MY_WME)))) + !(charset_info= get_charset_by_csname(default_charset, + MY_CS_PRIMARY, MYF(MY_WME)))) exit(1); if ((*argc < 1 && !opt_alldbs) || (*argc > 0 && opt_alldbs)) { @@ -778,16 +810,16 @@ static int get_options(int *argc, char ***argv) /* -** DBerror -- prints mysql error message and exits the program. +** DB_error -- prints mysql error message and exits the program. */ -static void DBerror(MYSQL *mysql, const char *when) +static void DB_error(MYSQL *mysql, const char *when) { - DBUG_ENTER("DBerror"); + DBUG_ENTER("DB_error"); my_printf_error(0,"Got error: %d: %s %s", MYF(0), - mysql_errno(mysql), mysql_error(mysql), when); + mysql_errno(mysql), mysql_error(mysql), when); safe_exit(EX_MYSQLERR); DBUG_VOID_RETURN; -} /* DBerror */ +} /* DB_error */ /* @@ -797,14 +829,15 @@ static void DBerror(MYSQL *mysql, const char *when) SYNOPSIS mysql_query_with_error_report() mysql_con connection to use - res if non zero, result will be put there with mysql_store_result + res if non zero, result will be put there with + mysql_store_result() query query to send to server RETURN VALUES 0 query sending and (if res!=0) result reading went ok 1 error */ - + static int mysql_query_with_error_report(MYSQL *mysql_con, MYSQL_RES **res, const char *query) { @@ -819,6 +852,27 @@ static int mysql_query_with_error_report(MYSQL *mysql_con, MYSQL_RES **res, return 0; } +/* + Open a new .sql file to dump the table or view into + + SYNOPSIS + open_sql_file_for_table + name name of the table or view + + RETURN VALUES + 0 Failed to open file + > 0 Handle of the open file +*/ +static FILE* open_sql_file_for_table(const char* table) +{ + FILE* res; + char filename[FN_REFLEN], tmp_path[FN_REFLEN]; + convert_dirname(tmp_path,path,NullS); + res= my_fopen(fn_format(filename, table, tmp_path, ".sql", 4), + O_WRONLY, MYF(MY_WME)); + return res; +} + static void safe_exit(int error) { @@ -850,7 +904,9 @@ static int dbConnect(char *host, char *user,char *passwd) #ifdef HAVE_OPENSSL if (opt_use_ssl) mysql_ssl_set(&mysql_connection, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, - opt_ssl_capath, opt_ssl_cipher); + opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql_connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); #endif if (opt_protocol) mysql_options(&mysql_connection,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); @@ -863,7 +919,7 @@ static int dbConnect(char *host, char *user,char *passwd) NULL,opt_mysql_port,opt_mysql_unix_port, 0))) { - DBerror(&mysql_connection, "when trying to connect"); + DB_error(&mysql_connection, "when trying to connect"); return 1; } /* @@ -877,13 +933,27 @@ static int dbConnect(char *host, char *user,char *passwd) */ sock->reconnect= 0; my_snprintf(buff, sizeof(buff), "/*!40100 SET @@SQL_MODE='%s' */", - compatible_mode_normal_str); + compatible_mode_normal_str); if (mysql_query_with_error_report(sock, 0, buff)) { mysql_close(sock); safe_exit(EX_MYSQLERR); return 1; } + /* + set time_zone to UTC to allow dumping date types between servers with + different time zone settings + */ + if (opt_tz_utc) + { + my_snprintf(buff, sizeof(buff), "/*!40103 SET TIME_ZONE='+00:00' */"); + if (mysql_query_with_error_report(sock, 0, buff)) + { + mysql_close(sock); + safe_exit(EX_MYSQLERR); + return 1; + } + } return 0; } /* dbConnect */ @@ -905,8 +975,8 @@ static void unescape(FILE *file,char *pos,uint length) DBUG_ENTER("unescape"); if (!(tmp=(char*) my_malloc(length*2+1, MYF(MY_WME)))) { - ignore_errors=0; /* Fatal error */ - safe_exit(EX_MYSQLERR); /* Force exit */ + ignore_errors=0; /* Fatal error */ + safe_exit(EX_MYSQLERR); /* Force exit */ } mysql_real_escape_string(&mysql_connection, tmp, pos, length); fputc('\'', file); @@ -930,6 +1000,22 @@ static my_bool test_if_special_chars(const char *str) +/* + quote_name(name, buff, force) + + Quotes char string, taking into account compatible mode + + Args + + name Unquoted string containing that which will be quoted + buff The buffer that contains the quoted value, also returned + force Flag to make it ignore 'test_if_special_chars' + + Returns + + buff quoted string + +*/ static char *quote_name(const char *name, char *buff, my_bool force) { char *to= buff; @@ -996,13 +1082,13 @@ static char *quote_for_like(const char *name, char *buff) /* Quote and print a string. - + SYNOPSIS print_quoted_xml() - output - output file - str - string to print - len - its length - + output - output file + str - string to print + len - its length + DESCRIPTION Quote '<' '>' '&' '\"' chars and print a string to the xml_file. */ @@ -1010,7 +1096,7 @@ static char *quote_for_like(const char *name, char *buff) static void print_quoted_xml(FILE *xml_file, const char *str, ulong len) { const char *end; - + for (end= str + len; str != end; str++) { switch (*str) { @@ -1037,15 +1123,15 @@ static void print_quoted_xml(FILE *xml_file, const char *str, ulong len) /* Print xml tag with one attribute. - + SYNOPSIS print_xml_tag1() - xml_file - output file - sbeg - line beginning - stag_atr - tag and attribute - sval - value of attribute - send - line ending - + xml_file - output file + sbeg - line beginning + stag_atr - tag and attribute + sval - value of attribute + send - line ending + DESCRIPTION Print tag with one attribute to the xml_file. Format is: sbeg<stag_atr="sval">send @@ -1055,8 +1141,8 @@ static void print_quoted_xml(FILE *xml_file, const char *str, ulong len) */ static void print_xml_tag1(FILE * xml_file, const char* sbeg, - const char* stag_atr, const char* sval, - const char* send) + const char* stag_atr, const char* sval, + const char* send) { fputs(sbeg, xml_file); fputs("<", xml_file); @@ -1074,11 +1160,11 @@ static void print_xml_tag1(FILE * xml_file, const char* sbeg, SYNOPSIS print_xml_null_tag() - xml_file - output file - sbeg - line beginning - stag_atr - tag and attribute - sval - value of attribute - send - line ending + xml_file - output file + sbeg - line beginning + stag_atr - tag and attribute + sval - value of attribute + send - line ending DESCRIPTION Print tag with one attribute to the xml_file. Format is: @@ -1108,11 +1194,11 @@ static void print_xml_null_tag(FILE * xml_file, const char* sbeg, SYNOPSIS print_xml_row() - xml_file - output file - row_name - xml tag name - tableRes - query result - row - result row - + xml_file - output file + row_name - xml tag name + tableRes - query result + row - result row + DESCRIPTION Print tag with many attribute to the xml_file. Format is: \t\t<row_name Atr1="Val1" Atr2="Val2"... /> @@ -1121,12 +1207,12 @@ static void print_xml_null_tag(FILE * xml_file, const char* sbeg, */ static void print_xml_row(FILE *xml_file, const char *row_name, - MYSQL_RES *tableRes, MYSQL_ROW *row) + MYSQL_RES *tableRes, MYSQL_ROW *row) { uint i; MYSQL_FIELD *field; ulong *lengths= mysql_fetch_lengths(tableRes); - + fprintf(xml_file, "\t\t<%s", row_name); check_io(xml_file); mysql_field_seek(tableRes, 0); @@ -1146,41 +1232,210 @@ static void print_xml_row(FILE *xml_file, const char *row_name, check_io(xml_file); } +/* + dump_routines_for_db + -- retrievs list of routines for a given db, and prints out + the CREATE PROCEDURE definition into the output (the dump). + + This function has logic to print the appropriate syntax depending on whether + this is a procedure or functions + + RETURN + 0 Success + 1 Error +*/ + +static uint dump_routines_for_db(char *db) +{ + char query_buff[512]; + const char *routine_type[]= {"FUNCTION", "PROCEDURE"}; + char db_name_buff[NAME_LEN*2+3], name_buff[NAME_LEN*2+3]; + char *routine_name; + int i; + FILE *sql_file= md_result_file; + MYSQL_RES *routine_res, *routine_list_res; + MYSQL_ROW row, routine_list_row; + DBUG_ENTER("dump_routines_for_db"); + DBUG_PRINT("enter", ("db: '%s'", db)); + + mysql_real_escape_string(sock, db_name_buff, db, strlen(db)); + + /* nice comments */ + if (opt_comments) + fprintf(sql_file, "\n--\n-- Dumping routines for database '%s'\n--\n", db); + + /* + not using "mysql_query_with_error_report" because we may have not + enough privileges to lock mysql.proc. + */ + if (lock_tables) + mysql_query(sock, "LOCK TABLES mysql.proc READ"); + + fprintf(sql_file, "DELIMITER ;;\n"); + + /* 0, retrieve and dump functions, 1, procedures */ + for (i= 0; i <= 1; i++) + { + my_snprintf(query_buff, sizeof(query_buff), + "SHOW %s STATUS WHERE Db = '%s'", + routine_type[i], db_name_buff); + + if (mysql_query_with_error_report(sock, &routine_list_res, query_buff)) + DBUG_RETURN(1); + + if (mysql_num_rows(routine_list_res)) + { + + while ((routine_list_row= mysql_fetch_row(routine_list_res))) + { + DBUG_PRINT("info", ("retrieving CREATE %s for %s", routine_type[i], + name_buff)); + routine_name= quote_name(routine_list_row[1], name_buff, 0); + my_snprintf(query_buff, sizeof(query_buff), "SHOW CREATE %s %s", + routine_type[i], routine_name); + + if (mysql_query_with_error_report(sock, &routine_res, query_buff)) + DBUG_RETURN(1); + + while ((row= mysql_fetch_row(routine_res))) + { + /* + if the user has EXECUTE privilege he see routine names, but NOT the + routine body of other routines that are not the creator of! + */ + DBUG_PRINT("info",("length of body for %s row[2] '%s' is %d", + routine_name, row[2], strlen(row[2]))); + if (strlen(row[2])) + { + char *query_str= NULL; + char *definer_begin; + + if (opt_drop) + fprintf(sql_file, "/*!50003 DROP %s IF EXISTS %s */;;\n", + routine_type[i], routine_name); + + /* + Cover DEFINER-clause in version-specific comments. + + TODO: this is definitely a BAD IDEA to parse SHOW CREATE output. + We should user INFORMATION_SCHEMA instead. The only problem is + that now INFORMATION_SCHEMA does not provide information about + routine parameters. + */ + + definer_begin= strstr(row[2], " DEFINER"); + + if (definer_begin) + { + char *definer_end= strstr(definer_begin, " PROCEDURE"); + + if (!definer_end) + definer_end= strstr(definer_begin, " FUNCTION"); + + if (definer_end) + { + char *query_str_tail; + + /* + Allocate memory for new query string: original string + from SHOW statement and version-specific comments. + */ + query_str= alloc_query_str(strlen(row[2]) + 23); + + query_str_tail= strnmov(query_str, row[2], + definer_begin - row[2]); + query_str_tail= strmov(query_str_tail, "*/ /*!50020"); + query_str_tail= strnmov(query_str_tail, definer_begin, + definer_end - definer_begin); + query_str_tail= strxmov(query_str_tail, "*/ /*!50003", + definer_end, NullS); + } + } + + /* + we need to change sql_mode only for the CREATE + PROCEDURE/FUNCTION otherwise we may need to re-quote routine_name + */; + fprintf(sql_file, "/*!50003 SET SESSION SQL_MODE=\"%s\"*/;;\n", + row[1] /* sql_mode */); + fprintf(sql_file, "/*!50003 %s */;;\n", + (query_str != NULL ? query_str : row[2])); + fprintf(sql_file, + "/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE*/" + ";;\n"); + + my_free(query_str, MYF(MY_ALLOW_ZERO_PTR)); + } + } /* end of routine printing */ + } /* end of list of routines */ + mysql_free_result(routine_res); + } + mysql_free_result(routine_list_res); + } /* end of for i (0 .. 1) */ + /* set the delimiter back to ';' */ + fprintf(sql_file, "DELIMITER ;\n"); + + if (lock_tables) + VOID(mysql_query_with_error_report(sock, 0, "UNLOCK TABLES")); + DBUG_RETURN(0); +} /* - getTableStructure -- retrievs database structure, prints out corresponding - CREATE statement and fills out insert_pat. + get_table_structure -- retrievs database structure, prints out corresponding + CREATE statement and fills out insert_pat if the table is the type we will + be dumping. + + ARGS + table - table name + db - db name + table_type - table type, e.g. "MyISAM" or "InnoDB", but also "VIEW" + ignore_flag - what we must particularly ignore - see IGNORE_ defines above RETURN number of fields in table, 0 if error */ -static uint getTableStructure(char *table, char* db) +static uint get_table_structure(char *table, char *db, char *table_type, + char *ignore_flag) { - MYSQL_RES *tableRes; - MYSQL_ROW row; - my_bool init=0; - uint numFields; - char *result_table, *opt_quoted_table; + my_bool init=0, delayed, write_data, complete_insert; + my_ulonglong num_fields; + char *result_table, *opt_quoted_table; const char *insert_option; - char name_buff[NAME_LEN+3],table_buff[NAME_LEN*2+3]; - char table_buff2[NAME_LEN*2+3]; - char query_buff[512]; + char name_buff[NAME_LEN+3],table_buff[NAME_LEN*2+3]; + char table_buff2[NAME_LEN*2+3], query_buff[512]; FILE *sql_file = md_result_file; int len; - DBUG_ENTER("getTableStructure"); - DBUG_PRINT("enter", ("db: %s, table: %s", db, table)); + MYSQL_RES *result; + MYSQL_ROW row; + + DBUG_ENTER("get_table_structure"); + DBUG_PRINT("enter", ("db: %s table: %s", db, table)); + + *ignore_flag= check_if_ignore_table(table, table_type); + + delayed= opt_delayed; + if (delayed && (*ignore_flag & IGNORE_INSERT_DELAYED)) + { + delayed= 0; + if (verbose) + fprintf(stderr, + "-- Warning: Unable to use delayed inserts for table '%s' " + "because it's of type %s\n", table, table_type); + } - if (!insert_pat_inited) + complete_insert= 0; + if ((write_data= !(*ignore_flag & IGNORE_DATA))) { - insert_pat_inited= init_dynamic_string(&insert_pat, "", 1024, 1024); + complete_insert= opt_complete_insert; + if (!insert_pat_inited) + insert_pat_inited= init_dynamic_string(&insert_pat, "", 1024, 1024); + else + dynstr_set(&insert_pat, ""); } - else - dynstr_set(&insert_pat, ""); - insert_option= ((opt_delayed && opt_ignore) ? " DELAYED IGNORE " : - opt_delayed ? " DELAYED " : - opt_ignore ? " IGNORE " : ""); + insert_option= ((delayed && opt_ignore) ? " DELAYED IGNORE " : + delayed ? " DELAYED " : opt_ignore ? " IGNORE " : ""); if (verbose) fprintf(stderr, "-- Retrieving table structure for table %s...\n", table); @@ -1189,13 +1444,14 @@ static uint getTableStructure(char *table, char* db) "SET OPTION SQL_QUOTE_SHOW_CREATE=%d", (opt_quoted || opt_keywords)); if (!create_options) - strmov(query_buff+len, "/*!40102 ,SQL_MODE=concat(@@sql_mode, _utf8 ',NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS') */"); + strmov(query_buff+len, + "/*!40102 ,SQL_MODE=concat(@@sql_mode, _utf8 ',NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS') */"); result_table= quote_name(table, table_buff, 1); opt_quoted_table= quote_name(table, table_buff2, 0); if (opt_order_by_primary) - order_by = primary_key_fields(opt_quoted_table); + order_by = primary_key_fields(result_table); if (!opt_xml && !mysql_query_with_error_report(sock, 0, query_buff)) { @@ -1204,6 +1460,7 @@ static uint getTableStructure(char *table, char* db) { /* Make an sql-file, if path was given iow. option -T was given */ char buff[20+FN_REFLEN]; + MYSQL_FIELD *field; my_snprintf(buff, sizeof(buff), "show create table %s", result_table); if (mysql_query_with_error_report(sock, 0, buff)) @@ -1214,74 +1471,181 @@ static uint getTableStructure(char *table, char* db) if (path) { - char filename[FN_REFLEN], tmp_path[FN_REFLEN]; - convert_dirname(tmp_path,path,NullS); - sql_file= my_fopen(fn_format(filename, table, tmp_path, ".sql", 4), - O_WRONLY, MYF(MY_WME)); - if (!sql_file) /* If file couldn't be opened */ + if (!(sql_file= open_sql_file_for_table(table))) { - safe_exit(EX_MYSQLERR); - DBUG_RETURN(0); + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); } write_header(sql_file, db); } if (!opt_xml && opt_comments) { + if (strcmp (table_type, "VIEW") == 0) /* view */ + fprintf(sql_file, "\n--\n-- Temporary table structure for view %s\n--\n\n", + result_table); + else fprintf(sql_file, "\n--\n-- Table structure for table %s\n--\n\n", - result_table); - check_io(sql_file); + result_table); + check_io(sql_file); } if (opt_drop) { - fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", opt_quoted_table); - check_io(sql_file); + /* + Even if the "table" is a view, we do a DROP TABLE here. The + view-specific code below fills in the DROP VIEW. + */ + fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", + opt_quoted_table); + check_io(sql_file); + } + + result= mysql_store_result(sock); + field= mysql_fetch_field_direct(result, 0); + if (strcmp(field->name, "View") == 0) + { + char *scv_buff = NULL; + + if (verbose) + fprintf(stderr, "-- It's a view, create dummy table for view\n"); + + /* save "show create" statement for later */ + if ((row= mysql_fetch_row(result)) && (scv_buff=row[1])) + scv_buff= my_strdup(scv_buff, MYF(0)); + + mysql_free_result(result); + + /* + Create a table with the same name as the view and with columns of + the same name in order to satisfy views that depend on this view. + The table will be removed when the actual view is created. + + The properties of each column, aside from the data type, are not + preserved in this temporary table, because they are not necessary. + + This will not be necessary once we can determine dependencies + between views and can simply dump them in the appropriate order. + */ + my_snprintf(query_buff, sizeof(query_buff), + "SHOW FIELDS FROM %s", result_table); + if (mysql_query_with_error_report(sock, 0, query_buff)) + { + /* + View references invalid or privileged table/col/fun (err 1356), + so we cannot create a stand-in table. Be defensive and dump + a comment with the view's 'show create' statement. (Bug #17371) + */ + + if (mysql_errno(sock) == ER_VIEW_INVALID) + fprintf(sql_file, "\n-- failed on view %s: %s\n\n", result_table, scv_buff ? scv_buff : ""); + + my_free(scv_buff, MYF(MY_ALLOW_ZERO_PTR)); + + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); + } + else + my_free(scv_buff, MYF(MY_ALLOW_ZERO_PTR)); + + if ((result= mysql_store_result(sock))) + { + if (mysql_num_rows(result)) + { + if (opt_drop) + { + /* + We have already dropped any table of the same name + above, so here we just drop the view. + */ + + fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n", + opt_quoted_table); + check_io(sql_file); + } + + fprintf(sql_file, "/*!50001 CREATE TABLE %s (\n", result_table); + /* + Get first row, following loop will prepend comma - keeps + from having to know if the row being printed is last to + determine if there should be a _trailing_ comma. + */ + row= mysql_fetch_row(result); + + fprintf(sql_file, " %s %s", quote_name(row[0], name_buff, 0), + row[1]); + + while((row= mysql_fetch_row(result))) + { + /* col name, col type */ + fprintf(sql_file, ",\n %s %s", + quote_name(row[0], name_buff, 0), row[1]); + } + fprintf(sql_file, "\n) */;\n"); + check_io(sql_file); + } + } + mysql_free_result(result); + + if (path) + my_fclose(sql_file, MYF(MY_WME)); + + seen_views= 1; + DBUG_RETURN(0); } - tableRes=mysql_store_result(sock); - row=mysql_fetch_row(tableRes); + row= mysql_fetch_row(result); fprintf(sql_file, "%s;\n", row[1]); check_io(sql_file); - mysql_free_result(tableRes); + mysql_free_result(result); } my_snprintf(query_buff, sizeof(query_buff), "show fields from %s", - result_table); - if (mysql_query_with_error_report(sock, &tableRes, query_buff)) + result_table); + if (mysql_query_with_error_report(sock, &result, query_buff)) { if (path) - my_fclose(sql_file, MYF(MY_WME)); + my_fclose(sql_file, MYF(MY_WME)); safe_exit(EX_MYSQLERR); DBUG_RETURN(0); } - dynstr_append_mem(&insert_pat, "INSERT ", 7); - dynstr_append(&insert_pat, insert_option); - dynstr_append_mem(&insert_pat, "INTO ", 5); - dynstr_append(&insert_pat, opt_quoted_table); - if (opt_complete_insert) + /* + If write_data is true, then we build up insert statements for + the table's data. Note: in subsequent lines of code, this test + will have to be performed each time we are appending to + insert_pat. + */ + if (write_data) { - dynstr_append_mem(&insert_pat, " (", 2); - } - else - { - dynstr_append_mem(&insert_pat, " VALUES ", 8); - if (!extended_insert) - dynstr_append_mem(&insert_pat, "(", 1); + dynstr_append_mem(&insert_pat, "INSERT ", 7); + dynstr_append(&insert_pat, insert_option); + dynstr_append_mem(&insert_pat, "INTO ", 5); + dynstr_append(&insert_pat, opt_quoted_table); + if (complete_insert) + { + dynstr_append_mem(&insert_pat, " (", 2); + } + else + { + dynstr_append_mem(&insert_pat, " VALUES ", 8); + if (!extended_insert) + dynstr_append_mem(&insert_pat, "(", 1); + } } - while ((row=mysql_fetch_row(tableRes))) + while ((row= mysql_fetch_row(result))) { - if (init) + if (complete_insert) { - if (opt_complete_insert) + if (init) + { dynstr_append_mem(&insert_pat, ", ", 2); - } - init=1; - if (opt_complete_insert) + } + init=1; dynstr_append(&insert_pat, quote_name(row[SHOW_FIELDNAME], name_buff, 0)); + } } - numFields = (uint) mysql_num_rows(tableRes); - mysql_free_result(tableRes); + num_fields= mysql_num_rows(result); + mysql_free_result(result); } else { @@ -1291,8 +1655,8 @@ static uint getTableStructure(char *table, char* db) my_progname, mysql_error(sock)); my_snprintf(query_buff, sizeof(query_buff), "show fields from %s", - result_table); - if (mysql_query_with_error_report(sock, &tableRes, query_buff)) + result_table); + if (mysql_query_with_error_report(sock, &result, query_buff)) { safe_exit(EX_MYSQLERR); DBUG_RETURN(0); @@ -1303,55 +1667,52 @@ static uint getTableStructure(char *table, char* db) { if (path) { - char filename[FN_REFLEN], tmp_path[FN_REFLEN]; - convert_dirname(tmp_path,path,NullS); - sql_file= my_fopen(fn_format(filename, table, tmp_path, ".sql", 4), - O_WRONLY, MYF(MY_WME)); - if (!sql_file) /* If file couldn't be opened */ + if (!(sql_file= open_sql_file_for_table(table))) { - safe_exit(EX_MYSQLERR); - DBUG_RETURN(0); + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); } write_header(sql_file, db); } if (!opt_xml && opt_comments) - fprintf(sql_file, "\n--\n-- Table structure for table %s\n--\n\n", - result_table); + fprintf(sql_file, "\n--\n-- Table structure for table %s\n--\n\n", + result_table); if (opt_drop) - fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n",result_table); + fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", result_table); if (!opt_xml) - fprintf(sql_file, "CREATE TABLE %s (\n", result_table); + fprintf(sql_file, "CREATE TABLE %s (\n", result_table); else print_xml_tag1(sql_file, "\t", "table_structure name=", table, "\n"); check_io(sql_file); } - dynstr_append_mem(&insert_pat, "INSERT ", 7); - dynstr_append(&insert_pat, insert_option); - dynstr_append_mem(&insert_pat, "INTO ", 5); - dynstr_append(&insert_pat, result_table); - if (opt_complete_insert) - { - dynstr_append_mem(&insert_pat, " (", 2); - } - else + if (write_data) { - dynstr_append_mem(&insert_pat, " VALUES ", 8); - if (!extended_insert) - dynstr_append_mem(&insert_pat, "(", 1); + dynstr_append_mem(&insert_pat, "INSERT ", 7); + dynstr_append(&insert_pat, insert_option); + dynstr_append_mem(&insert_pat, "INTO ", 5); + dynstr_append(&insert_pat, result_table); + if (opt_complete_insert) + dynstr_append_mem(&insert_pat, " (", 2); + else + { + dynstr_append_mem(&insert_pat, " VALUES ", 8); + if (!extended_insert) + dynstr_append_mem(&insert_pat, "(", 1); + } } - while ((row=mysql_fetch_row(tableRes))) + while ((row= mysql_fetch_row(result))) { - ulong *lengths=mysql_fetch_lengths(tableRes); + ulong *lengths= mysql_fetch_lengths(result); if (init) { if (!opt_xml && !tFlag) - { - fputs(",\n",sql_file); - check_io(sql_file); - } - if (opt_complete_insert) + { + fputs(",\n",sql_file); + check_io(sql_file); + } + if (complete_insert) dynstr_append_mem(&insert_pat, ", ", 2); } init=1; @@ -1360,44 +1721,52 @@ static uint getTableStructure(char *table, char* db) quote_name(row[SHOW_FIELDNAME], name_buff, 0)); if (!tFlag) { - if (opt_xml) - { - print_xml_row(sql_file, "field", tableRes, &row); - continue; - } + if (opt_xml) + { + print_xml_row(sql_file, "field", result, &row); + continue; + } if (opt_keywords) - fprintf(sql_file, " %s.%s %s", result_table, - quote_name(row[SHOW_FIELDNAME],name_buff, 0), - row[SHOW_TYPE]); + fprintf(sql_file, " %s.%s %s", result_table, + quote_name(row[SHOW_FIELDNAME],name_buff, 0), + row[SHOW_TYPE]); else - fprintf(sql_file, " %s %s", quote_name(row[SHOW_FIELDNAME], - name_buff, 0), - row[SHOW_TYPE]); + fprintf(sql_file, " %s %s", quote_name(row[SHOW_FIELDNAME], + name_buff, 0), + row[SHOW_TYPE]); if (row[SHOW_DEFAULT]) { - fputs(" DEFAULT ", sql_file); - unescape(sql_file, row[SHOW_DEFAULT], lengths[SHOW_DEFAULT]); + fputs(" DEFAULT ", sql_file); + unescape(sql_file, row[SHOW_DEFAULT], lengths[SHOW_DEFAULT]); } if (!row[SHOW_NULL][0]) - fputs(" NOT NULL", sql_file); + fputs(" NOT NULL", sql_file); if (row[SHOW_EXTRA][0]) - fprintf(sql_file, " %s",row[SHOW_EXTRA]); - check_io(sql_file); + fprintf(sql_file, " %s",row[SHOW_EXTRA]); + check_io(sql_file); } } - numFields = (uint) mysql_num_rows(tableRes); - mysql_free_result(tableRes); + num_fields= mysql_num_rows(result); + mysql_free_result(result); if (!tFlag) { /* Make an sql-file, if path was given iow. option -T was given */ char buff[20+FN_REFLEN]; uint keynr,primary_key; my_snprintf(buff, sizeof(buff), "show keys from %s", result_table); - if (mysql_query_with_error_report(sock, &tableRes, buff)) + if (mysql_query_with_error_report(sock, &result, buff)) { + if (mysql_errno(sock) == ER_WRONG_OBJECT) + { + /* it is VIEW */ + fputs("\t\t<options Comment=\"view\" />\n", sql_file); + goto continue_xml; + } + fprintf(stderr, "%s: Can't get keys for table %s (%s)\n", + my_progname, result_table, mysql_error(sock)); if (path) - my_fclose(sql_file, MYF(MY_WME)); + my_fclose(sql_file, MYF(MY_WME)); safe_exit(EX_MYSQLERR); DBUG_RETURN(0); } @@ -1405,108 +1774,106 @@ static uint getTableStructure(char *table, char* db) /* Find first which key is primary key */ keynr=0; primary_key=INT_MAX; - while ((row=mysql_fetch_row(tableRes))) + while ((row= mysql_fetch_row(result))) { if (atoi(row[3]) == 1) { - keynr++; + keynr++; #ifdef FORCE_PRIMARY_KEY - if (atoi(row[1]) == 0 && primary_key == INT_MAX) - primary_key=keynr; + if (atoi(row[1]) == 0 && primary_key == INT_MAX) + primary_key=keynr; #endif - if (!strcmp(row[2],"PRIMARY")) - { - primary_key=keynr; - break; - } + if (!strcmp(row[2],"PRIMARY")) + { + primary_key=keynr; + break; + } } } - mysql_data_seek(tableRes,0); + mysql_data_seek(result,0); keynr=0; - while ((row=mysql_fetch_row(tableRes))) + while ((row= mysql_fetch_row(result))) { - if (opt_xml) - { - print_xml_row(sql_file, "key", tableRes, &row); - continue; - } - + if (opt_xml) + { + print_xml_row(sql_file, "key", result, &row); + continue; + } + if (atoi(row[3]) == 1) { - if (keynr++) - putc(')', sql_file); - if (atoi(row[1])) /* Test if duplicate key */ - /* Duplicate allowed */ - fprintf(sql_file, ",\n KEY %s (",quote_name(row[2],name_buff,0)); - else if (keynr == primary_key) - fputs(",\n PRIMARY KEY (",sql_file); /* First UNIQUE is primary */ - else - fprintf(sql_file, ",\n UNIQUE %s (",quote_name(row[2],name_buff, - 0)); + if (keynr++) + putc(')', sql_file); + if (atoi(row[1])) /* Test if duplicate key */ + /* Duplicate allowed */ + fprintf(sql_file, ",\n KEY %s (",quote_name(row[2],name_buff,0)); + else if (keynr == primary_key) + fputs(",\n PRIMARY KEY (",sql_file); /* First UNIQUE is primary */ + else + fprintf(sql_file, ",\n UNIQUE %s (",quote_name(row[2],name_buff, + 0)); } else - putc(',', sql_file); + putc(',', sql_file); fputs(quote_name(row[4], name_buff, 0), sql_file); if (row[7]) - fprintf(sql_file, " (%s)",row[7]); /* Sub key */ - check_io(sql_file); + fprintf(sql_file, " (%s)",row[7]); /* Sub key */ + check_io(sql_file); } if (!opt_xml) { - if (keynr) - putc(')', sql_file); - fputs("\n)",sql_file); - check_io(sql_file); + if (keynr) + putc(')', sql_file); + fputs("\n)",sql_file); + check_io(sql_file); } /* Get MySQL specific create options */ if (create_options) { - char show_name_buff[FN_REFLEN]; + char show_name_buff[NAME_LEN*2+2+24]; - /* Check memory for quote_for_like() */ - DBUG_ASSERT(2*sizeof(table) < sizeof(show_name_buff)); + /* Check memory for quote_for_like() */ my_snprintf(buff, sizeof(buff), "show table status like %s", - quote_for_like(table, show_name_buff)); + quote_for_like(table, show_name_buff)); - if (mysql_query_with_error_report(sock, &tableRes, buff)) + if (mysql_query_with_error_report(sock, &result, buff)) { - if (mysql_errno(sock) != ER_PARSE_ERROR) - { /* If old MySQL version */ - if (verbose) - fprintf(stderr, - "-- Warning: Couldn't get status information for table %s (%s)\n", - result_table,mysql_error(sock)); - } + if (mysql_errno(sock) != ER_PARSE_ERROR) + { /* If old MySQL version */ + if (verbose) + fprintf(stderr, + "-- Warning: Couldn't get status information for table %s (%s)\n", + result_table,mysql_error(sock)); + } } - else if (!(row=mysql_fetch_row(tableRes))) + else if (!(row= mysql_fetch_row(result))) { - fprintf(stderr, - "Error: Couldn't read status information for table %s (%s)\n", - result_table,mysql_error(sock)); + fprintf(stderr, + "Error: Couldn't read status information for table %s (%s)\n", + result_table,mysql_error(sock)); } else { - if (opt_xml) - { - print_xml_row(sql_file, "options", tableRes, &row); - } - else - { - fputs("/*!",sql_file); - print_value(sql_file,tableRes,row,"engine=","Engine",0); - print_value(sql_file,tableRes,row,"","Create_options",0); - print_value(sql_file,tableRes,row,"comment=","Comment",1); - fputs(" */",sql_file); - check_io(sql_file); - } + if (opt_xml) + print_xml_row(sql_file, "options", result, &row); + else + { + fputs("/*!",sql_file); + print_value(sql_file,result,row,"engine=","Engine",0); + print_value(sql_file,result,row,"","Create_options",0); + print_value(sql_file,result,row,"comment=","Comment",1); + fputs(" */",sql_file); + check_io(sql_file); + } } - mysql_free_result(tableRes); /* Is always safe to free */ + mysql_free_result(result); /* Is always safe to free */ } +continue_xml: if (!opt_xml) - fputs(";\n", sql_file); + fputs(";\n", sql_file); else - fputs("\t</table_structure>\n", sql_file); + fputs("\t</table_structure>\n", sql_file); check_io(sql_file); } } @@ -1522,12 +1889,106 @@ static uint getTableStructure(char *table, char* db) write_footer(sql_file); my_fclose(sql_file, MYF(MY_WME)); } - DBUG_RETURN(numFields); -} /* getTableStructure */ + DBUG_RETURN(num_fields); +} /* get_table_structure */ + + +/* + dump_triggers_for_table + + Dumps the triggers given a table/db name. This should be called after + the tables have been dumped in case a trigger depends on the existence + of a table + +*/ + +static void dump_triggers_for_table (char *table, char *db) +{ + char *result_table; + char name_buff[NAME_LEN*4+3], table_buff[NAME_LEN*2+3]; + char query_buff[512]; + uint old_opt_compatible_mode=opt_compatible_mode; + FILE *sql_file = md_result_file; + MYSQL_RES *result; + MYSQL_ROW row; + + DBUG_ENTER("dump_triggers_for_table"); + DBUG_PRINT("enter", ("db: %s, table: %s", db, table)); + + /* Do not use ANSI_QUOTES on triggers in dump */ + opt_compatible_mode&= ~MASK_ANSI_QUOTES; + result_table= quote_name(table, table_buff, 1); + + my_snprintf(query_buff, sizeof(query_buff), + "SHOW TRIGGERS LIKE %s", + quote_for_like(table, name_buff)); + + if (mysql_query_with_error_report(sock, &result, query_buff)) + { + if (path) + my_fclose(sql_file, MYF(MY_WME)); + safe_exit(EX_MYSQLERR); + DBUG_VOID_RETURN; + } + if (mysql_num_rows(result)) + fprintf(sql_file, "\n/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;\n\ +DELIMITER ;;\n"); + while ((row= mysql_fetch_row(result))) + { + fprintf(sql_file, + "/*!50003 SET SESSION SQL_MODE=\"%s\" */;;\n" + "/*!50003 CREATE */ ", + row[6] /* sql_mode */); + + if (mysql_num_fields(result) > 7) + { + /* + mysqldump can be run against the server, that does not support definer + in triggers (there is no DEFINER column in SHOW TRIGGERS output). So, + we should check if we have this column before accessing it. + */ + + uint user_name_len; + char user_name_str[USERNAME_LENGTH + 1]; + char quoted_user_name_str[USERNAME_LENGTH * 2 + 3]; + uint host_name_len; + char host_name_str[HOSTNAME_LENGTH + 1]; + char quoted_host_name_str[HOSTNAME_LENGTH * 2 + 3]; + + parse_user(row[7], strlen(row[7]), user_name_str, &user_name_len, + host_name_str, &host_name_len); + + fprintf(sql_file, + "/*!50017 DEFINER=%s@%s */ ", + quote_name(user_name_str, quoted_user_name_str, FALSE), + quote_name(host_name_str, quoted_host_name_str, FALSE)); + } + + fprintf(sql_file, + "/*!50003 TRIGGER %s %s %s ON %s FOR EACH ROW%s%s */;;\n\n", + quote_name(row[0], name_buff, 0), /* Trigger */ + row[4], /* Timing */ + row[1], /* Event */ + result_table, + (strchr(" \t\n\r", *(row[3]))) ? "" : " ", + row[3] /* Statement */); + } + if (mysql_num_rows(result)) + fprintf(sql_file, + "DELIMITER ;\n" + "/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"); + mysql_free_result(result); + /* + make sure to set back opt_compatible mode to + original value + */ + opt_compatible_mode=old_opt_compatible_mode; + DBUG_VOID_RETURN; +} static char *add_load_option(char *ptr,const char *object, - const char *statement) + const char *statement) { if (object) { @@ -1547,10 +2008,10 @@ static char *add_load_option(char *ptr,const char *object, /* -** Allow the user to specify field terminator strings like: -** "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline) -** This is done by doubleing ' and add a end -\ if needed to avoid -** syntax errors from the SQL parser. + Allow the user to specify field terminator strings like: + "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline) + This is done by doubling ' and add a end -\ if needed to avoid + syntax errors from the SQL parser. */ static char *field_escape(char *to,const char *from,uint length) @@ -1566,7 +2027,7 @@ static char *field_escape(char *to,const char *from,uint length) else { if (*from == '\'' && !end_backslashes) - *to++= *from; /* We want a duplicate of "'" for MySQL */ + *to++= *from; /* We want a duplicate of "'" for MySQL */ end_backslashes=0; } } @@ -1583,39 +2044,92 @@ static char *alloc_query_str(ulong size) if (!(query= (char*) my_malloc(size, MYF(MY_WME)))) { - ignore_errors= 0; /* Fatal error */ - safe_exit(EX_MYSQLERR); /* Force exit */ + ignore_errors= 0; /* Fatal error */ + safe_exit(EX_MYSQLERR); /* Force exit */ } return query; } + /* -** dumpTable saves database contents as a series of INSERT statements. + + SYNOPSIS + dump_table() + + dump_table saves database contents as a series of INSERT statements. + + ARGS + table - table name + db - db name + + RETURNS + void */ -static void dumpTable(uint numFields, char *table) + +static void dump_table(char *table, char *db) { + char ignore_flag; char query_buf[QUERY_LENGTH], *end, buff[256],table_buff[NAME_LEN+3]; + char table_type[NAME_LEN]; char *result_table, table_buff2[NAME_LEN*2+3], *opt_quoted_table; char *query= query_buf; - MYSQL_RES *res; - MYSQL_FIELD *field; - MYSQL_ROW row; - ulong rownr, row_break, total_length, init_length; - const char *table_type; int error= 0; + ulong rownr, row_break, total_length, init_length; + uint num_fields; + MYSQL_RES *res; + MYSQL_FIELD *field; + MYSQL_ROW row; + DBUG_ENTER("dump_table"); - result_table= quote_name(table,table_buff, 1); - opt_quoted_table= quote_name(table, table_buff2, 0); + /* + Make sure you get the create table info before the following check for + --no-data flag below. Otherwise, the create table info won't be printed. + */ + num_fields= get_table_structure(table, db, table_type, &ignore_flag); + + /* + The "table" could be a view. If so, we don't do anything here. + */ + if (strcmp (table_type, "VIEW") == 0) + return; - /* Check table type */ - if ((table_type= check_if_ignore_table(table))) + /* Check --no-data flag */ + if (dFlag) { if (verbose) fprintf(stderr, - "-- Skipping data for table '%s' because it's of type %s\n", - table, table_type); - return; + "-- Skipping dump data for table '%s', --no-data was used\n", + table); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", + ("ignore_flag: %x num_fields: %d", (int) ignore_flag, + num_fields)); + /* + If the table type is a merge table or any type that has to be + _completely_ ignored and no data dumped + */ + if (ignore_flag & IGNORE_DATA) + { + if (verbose) + fprintf(stderr, + "-- Warning: Skipping data for table '%s' because it's of type %s\n", + table, table_type); + DBUG_VOID_RETURN; } + /* Check that there are any fields in the table */ + if (num_fields == 0) + { + if (verbose) + fprintf(stderr, + "-- Skipping dump data for table '%s', it has no fields\n", + table); + DBUG_VOID_RETURN; + } + + result_table= quote_name(table,table_buff, 1); + opt_quoted_table= quote_name(table, table_buff2, 0); if (verbose) fprintf(stderr, "-- Sending SELECT query...\n"); @@ -1626,11 +2140,11 @@ static void dumpTable(uint numFields, char *table) my_load_path(tmp_path, tmp_path, NULL); fn_format(filename, table, tmp_path, ".txt", 4); my_delete(filename, MYF(0)); /* 'INTO OUTFILE' doesn't work, if - filename wasn't deleted */ + filename wasn't deleted */ to_unix_path(filename); - my_snprintf(query, QUERY_LENGTH, - "SELECT /*!40001 SQL_NO_CACHE */ * INTO OUTFILE '%s'", - filename); + my_snprintf(query, QUERY_LENGTH, + "SELECT /*!40001 SQL_NO_CACHE */ * INTO OUTFILE '%s'", + filename); end= strend(query); if (fields_terminated || enclosed || opt_enclosed || escaped) @@ -1658,8 +2172,8 @@ static void dumpTable(uint numFields, char *table) } if (mysql_real_query(sock, query, (uint) (end - query))) { - DBerror(sock, "when executing 'SELECT INTO OUTFILE'"); - return; + DB_error(sock, "when executing 'SELECT INTO OUTFILE'"); + DBUG_VOID_RETURN; } } else @@ -1667,12 +2181,12 @@ static void dumpTable(uint numFields, char *table) if (!opt_xml && opt_comments) { fprintf(md_result_file,"\n--\n-- Dumping data for table %s\n--\n", - result_table); + result_table); check_io(md_result_file); } my_snprintf(query, QUERY_LENGTH, - "SELECT /*!40001 SQL_NO_CACHE */ * FROM %s", - result_table); + "SELECT /*!40001 SQL_NO_CACHE */ * FROM %s", + result_table); if (where || order_by) { query = alloc_query_str((ulong) (strlen(query) + 1 + @@ -1705,19 +2219,22 @@ static void dumpTable(uint numFields, char *table) check_io(md_result_file); } if (mysql_query_with_error_report(sock, 0, query)) - DBerror(sock, "when retrieving data from server"); + DB_error(sock, "when retrieving data from server"); if (quick) res=mysql_use_result(sock); else res=mysql_store_result(sock); if (!res) - DBerror(sock, "when retrieving data from server"); + { + DB_error(sock, "when retrieving data from server"); + goto err; + } if (verbose) fprintf(stderr, "-- Retrieving rows...\n"); - if (mysql_num_fields(res) != numFields) + if (mysql_num_fields(res) != num_fields) { fprintf(stderr,"%s: Error in field count for table: %s ! Aborting.\n", - my_progname, result_table); + my_progname, result_table); error= EX_CONSCHECK; goto err; } @@ -1735,7 +2252,7 @@ static void dumpTable(uint numFields, char *table) check_io(md_result_file); } - total_length= opt_net_buffer_length; /* Force row break */ + total_length= opt_net_buffer_length; /* Force row break */ row_break=0; rownr=0; init_length=(uint) insert_pat.length+4; @@ -1748,80 +2265,83 @@ static void dumpTable(uint numFields, char *table) check_io(md_result_file); } - while ((row=mysql_fetch_row(res))) + while ((row= mysql_fetch_row(res))) { uint i; - ulong *lengths=mysql_fetch_lengths(res); + ulong *lengths= mysql_fetch_lengths(res); rownr++; if (!extended_insert && !opt_xml) { - fputs(insert_pat.str,md_result_file); - check_io(md_result_file); + fputs(insert_pat.str,md_result_file); + check_io(md_result_file); } mysql_field_seek(res,0); if (opt_xml) { fputs("\t<row>\n", md_result_file); - check_io(md_result_file); + check_io(md_result_file); } for (i = 0; i < mysql_num_fields(res); i++) { int is_blob; - if (!(field = mysql_fetch_field(res))) - { - my_snprintf(query, QUERY_LENGTH, - "%s: Not enough fields from table %s! Aborting.\n", - my_progname, result_table); - fputs(query,stderr); - error= EX_CONSCHECK; - goto err; - } - - /* - 63 is my_charset_bin. If charsetnr is not 63, - we have not a BLOB but a TEXT column. - we'll dump in hex only BLOB columns. - */ + ulong length= lengths[i]; + + if (!(field = mysql_fetch_field(res))) + { + my_snprintf(query, QUERY_LENGTH, + "%s: Not enough fields from table %s! Aborting.\n", + my_progname, result_table); + fputs(query,stderr); + error= EX_CONSCHECK; + goto err; + } + + /* + 63 is my_charset_bin. If charsetnr is not 63, + we have not a BLOB but a TEXT column. + we'll dump in hex only BLOB columns. + */ is_blob= (opt_hex_blob && field->charsetnr == 63 && - (field->type == FIELD_TYPE_STRING || - field->type == FIELD_TYPE_VAR_STRING || - field->type == FIELD_TYPE_BLOB || - field->type == FIELD_TYPE_LONG_BLOB || - field->type == FIELD_TYPE_MEDIUM_BLOB || - field->type == FIELD_TYPE_TINY_BLOB)) ? 1 : 0; - if (extended_insert) - { - ulong length = lengths[i]; - if (i == 0) - dynstr_set(&extended_row,"("); - else - dynstr_append(&extended_row,","); - - if (row[i]) - { - if (length) - { - if (!IS_NUM_FIELD(field)) - { - /* - "length * 2 + 2" is OK for both HEX and non-HEX modes: - - In HEX mode we need exactly 2 bytes per character - plus 2 bytes for '0x' prefix. - - In non-HEX mode we need up to 2 bytes per character, - plus 2 bytes for leading and trailing '\'' characters. - */ - if (dynstr_realloc(&extended_row,length * 2+2)) - { - fputs("Aborting dump (out of memory)",stderr); - error= EX_EOM; - goto err; - } + (field->type == MYSQL_TYPE_BIT || + field->type == MYSQL_TYPE_STRING || + field->type == MYSQL_TYPE_VAR_STRING || + field->type == MYSQL_TYPE_VARCHAR || + field->type == MYSQL_TYPE_BLOB || + field->type == MYSQL_TYPE_LONG_BLOB || + field->type == MYSQL_TYPE_MEDIUM_BLOB || + field->type == MYSQL_TYPE_TINY_BLOB)) ? 1 : 0; + if (extended_insert) + { + if (i == 0) + dynstr_set(&extended_row,"("); + else + dynstr_append(&extended_row,","); + + if (row[i]) + { + if (length) + { + if (!IS_NUM_FIELD(field)) + { + /* + "length * 2 + 2" is OK for both HEX and non-HEX modes: + - In HEX mode we need exactly 2 bytes per character + plus 2 bytes for '0x' prefix. + - In non-HEX mode we need up to 2 bytes per character, + plus 2 bytes for leading and trailing '\'' characters. + */ + if (dynstr_realloc(&extended_row,length * 2+2)) + { + fputs("Aborting dump (out of memory)",stderr); + error= EX_EOM; + goto err; + } if (opt_hex_blob && is_blob) { dynstr_append(&extended_row, "0x"); - extended_row.length+= mysql_hex_string(extended_row.str + + extended_row.length+= mysql_hex_string(extended_row.str + extended_row.length, row[i], length); extended_row.str[extended_row.length]= '\0'; @@ -1836,94 +2356,94 @@ static void dumpTable(uint numFields, char *table) extended_row.str[extended_row.length]='\0'; dynstr_append(&extended_row,"'"); } - } - else - { - /* change any strings ("inf", "-inf", "nan") into NULL */ - char *ptr = row[i]; - if (my_isalpha(charset_info, *ptr) || (*ptr == '-' && - my_isalpha(charset_info, ptr[1]))) - dynstr_append(&extended_row, "NULL"); - else - { - if (field->type == FIELD_TYPE_DECIMAL) - { - /* add " signs around */ - dynstr_append(&extended_row, "'"); - dynstr_append(&extended_row, ptr); - dynstr_append(&extended_row, "'"); - } - else - dynstr_append(&extended_row, ptr); - } - } - } - else - dynstr_append(&extended_row,"''"); - } - else if (dynstr_append(&extended_row,"NULL")) - { - fputs("Aborting dump (out of memory)",stderr); - error= EX_EOM; - goto err; - } - } - else - { - if (i && !opt_xml) - { - fputc(',', md_result_file); - check_io(md_result_file); - } - if (row[i]) - { - if (!IS_NUM_FIELD(field)) - { - if (opt_xml) - { - print_xml_tag1(md_result_file, "\t\t", "field name=", - field->name, ""); - print_quoted_xml(md_result_file, row[i], lengths[i]); - fputs("</field>\n", md_result_file); - } - else if (opt_hex_blob && is_blob) + } + else + { + /* change any strings ("inf", "-inf", "nan") into NULL */ + char *ptr = row[i]; + if (my_isalpha(charset_info, *ptr) || (*ptr == '-' && + my_isalpha(charset_info, ptr[1]))) + dynstr_append(&extended_row, "NULL"); + else + { + if (field->type == FIELD_TYPE_DECIMAL) + { + /* add " signs around */ + dynstr_append(&extended_row, "'"); + dynstr_append(&extended_row, ptr); + dynstr_append(&extended_row, "'"); + } + else + dynstr_append(&extended_row, ptr); + } + } + } + else + dynstr_append(&extended_row,"''"); + } + else if (dynstr_append(&extended_row,"NULL")) + { + fputs("Aborting dump (out of memory)",stderr); + error= EX_EOM; + goto err; + } + } + else + { + if (i && !opt_xml) + { + fputc(',', md_result_file); + check_io(md_result_file); + } + if (row[i]) + { + if (!IS_NUM_FIELD(field)) + { + if (opt_xml) + { + print_xml_tag1(md_result_file, "\t\t", "field name=", + field->name, ""); + print_quoted_xml(md_result_file, row[i], length); + fputs("</field>\n", md_result_file); + } + else if (opt_hex_blob && is_blob && length) { /* sakaik got the idea to to provide blob's in hex notation. */ - char *ptr= row[i], *end= ptr+ lengths[i]; + char *ptr= row[i], *end= ptr + length; fputs("0x", md_result_file); for (; ptr < end ; ptr++) - fprintf(md_result_file, "%02X", *((uchar *)ptr)); + fprintf(md_result_file, "%02X", *((uchar *)ptr)); + } + else + unescape(md_result_file, row[i], length); + } + else + { + /* change any strings ("inf", "-inf", "nan") into NULL */ + char *ptr = row[i]; + if (opt_xml) + { + print_xml_tag1(md_result_file, "\t\t", "field name=", + field->name, ""); + fputs(!my_isalpha(charset_info, *ptr) ? ptr: "NULL", + md_result_file); + fputs("</field>\n", md_result_file); + } + else if (my_isalpha(charset_info, *ptr) || + (*ptr == '-' && my_isalpha(charset_info, ptr[1]))) + fputs("NULL", md_result_file); + else if (field->type == FIELD_TYPE_DECIMAL) + { + /* add " signs around */ + fputc('\'', md_result_file); + fputs(ptr, md_result_file); + fputc('\'', md_result_file); } else - unescape(md_result_file, row[i], lengths[i]); - } - else - { - /* change any strings ("inf", "-inf", "nan") into NULL */ - char *ptr = row[i]; - if (opt_xml) - { - print_xml_tag1(md_result_file, "\t\t", "field name=", - field->name, ""); - fputs(!my_isalpha(charset_info, *ptr) ? ptr: "NULL", - md_result_file); - fputs("</field>\n", md_result_file); - } - else if (my_isalpha(charset_info, *ptr) || - (*ptr == '-' && my_isalpha(charset_info, ptr[1]))) - fputs("NULL", md_result_file); - else if (field->type == FIELD_TYPE_DECIMAL) - { - /* add " signs around */ - fputc('\'', md_result_file); - fputs(ptr, md_result_file); - fputc('\'', md_result_file); - } - else - fputs(ptr, md_result_file); - } - } - else + fputs(ptr, md_result_file); + } + } + else { /* The field value is NULL */ if (!opt_xml) @@ -1933,61 +2453,61 @@ static void dumpTable(uint numFields, char *table) field->name, "\n"); } check_io(md_result_file); - } + } } if (opt_xml) { fputs("\t</row>\n", md_result_file); - check_io(md_result_file); + check_io(md_result_file); } if (extended_insert) { - ulong row_length; - dynstr_append(&extended_row,")"); + ulong row_length; + dynstr_append(&extended_row,")"); row_length = 2 + extended_row.length; if (total_length + row_length < opt_net_buffer_length) { - total_length += row_length; - fputc(',',md_result_file); /* Always row break */ - fputs(extended_row.str,md_result_file); - } + total_length += row_length; + fputc(',',md_result_file); /* Always row break */ + fputs(extended_row.str,md_result_file); + } else { - if (row_break) - fputs(";\n", md_result_file); - row_break=1; /* This is first row */ + if (row_break) + fputs(";\n", md_result_file); + row_break=1; /* This is first row */ fputs(insert_pat.str,md_result_file); fputs(extended_row.str,md_result_file); - total_length = row_length+init_length; + total_length = row_length+init_length; } - check_io(md_result_file); + check_io(md_result_file); } else if (!opt_xml) { - fputs(");\n", md_result_file); - check_io(md_result_file); + fputs(");\n", md_result_file); + check_io(md_result_file); } } /* XML - close table tag and supress regular output */ if (opt_xml) - fputs("\t</table_data>\n", md_result_file); + fputs("\t</table_data>\n", md_result_file); else if (extended_insert && row_break) - fputs(";\n", md_result_file); /* If not empty table */ + fputs(";\n", md_result_file); /* If not empty table */ fflush(md_result_file); check_io(md_result_file); if (mysql_errno(sock)) { my_snprintf(query, QUERY_LENGTH, - "%s: Error %d: %s when dumping table %s at row: %ld\n", - my_progname, - mysql_errno(sock), - mysql_error(sock), - result_table, - rownr); + "%s: Error %d: %s when dumping table %s at row: %ld\n", + my_progname, + mysql_errno(sock), + mysql_error(sock), + result_table, + rownr); fputs(query,stderr); error= EX_CONSCHECK; goto err; @@ -1997,7 +2517,7 @@ static void dumpTable(uint numFields, char *table) if (opt_disable_keys) { fprintf(md_result_file,"/*!40000 ALTER TABLE %s ENABLE KEYS */;\n", - opt_quoted_table); + opt_quoted_table); check_io(md_result_file); } if (opt_lock) @@ -2013,15 +2533,15 @@ static void dumpTable(uint numFields, char *table) mysql_free_result(res); if (query != query_buf) my_free(query, MYF(MY_ALLOW_ZERO_PTR)); - } - return; + } + DBUG_VOID_RETURN; err: if (query != query_buf) my_free(query, MYF(MY_ALLOW_ZERO_PTR)); safe_exit(error); - return; -} /* dumpTable */ + DBUG_VOID_RETURN; +} /* dump_table */ static char *getTableName(int reset) @@ -2061,6 +2581,21 @@ static int dump_all_databases() if (dump_all_tables_in_db(row[0])) result=1; } + if (seen_views) + { + if (mysql_query(sock, "SHOW DATABASES") || + !(tableres = mysql_store_result(sock))) + { + my_printf_error(0, "Error: Couldn't execute 'SHOW DATABASES': %s", + MYF(0), mysql_error(sock)); + return 1; + } + while ((row = mysql_fetch_row(tableres))) + { + if (dump_all_views_in_db(row[0])) + result=1; + } + } return result; } /* dump_all_databases */ @@ -2069,35 +2604,48 @@ static int dump_all_databases() static int dump_databases(char **db_names) { int result=0; - for ( ; *db_names ; db_names++) + char **db; + for (db= db_names ; *db ; db++) { - if (dump_all_tables_in_db(*db_names)) + if (dump_all_tables_in_db(*db)) result=1; } + if (!result && seen_views) + { + for (db= db_names ; *db ; db++) + { + if (dump_all_views_in_db(*db)) + result=1; + } + } return result; } /* dump_databases */ static int init_dumping(char *database) { + if (mysql_get_server_version(sock) >= 50003 && + !my_strcasecmp(&my_charset_latin1, database, "information_schema")) + return 1; + if (mysql_select_db(sock, database)) { - DBerror(sock, "when selecting the database"); - return 1; /* If --force */ + DB_error(sock, "when selecting the database"); + return 1; /* If --force */ } if (!path && !opt_xml) { if (opt_databases || opt_alldbs) { /* - length of table name * 2 (if name contains quotes), 2 quotes and 0 + length of table name * 2 (if name contains quotes), 2 quotes and 0 */ char quoted_database_buf[64*2+3]; char *qdatabase= quote_name(database,quoted_database_buf,opt_quoted); if (opt_comments) { - fprintf(md_result_file,"\n--\n-- Current Database: %s\n--\n", qdatabase); - check_io(md_result_file); + fprintf(md_result_file,"\n--\n-- Current Database: %s\n--\n", qdatabase); + check_io(md_result_file); } if (!opt_create_db) { @@ -2105,9 +2653,9 @@ static int init_dumping(char *database) MYSQL_ROW row; MYSQL_RES *dbinfo; - my_snprintf(qbuf, sizeof(qbuf), - "SHOW CREATE DATABASE IF NOT EXISTS %s", - qdatabase); + my_snprintf(qbuf, sizeof(qbuf), + "SHOW CREATE DATABASE IF NOT EXISTS %s", + qdatabase); if (mysql_query(sock, qbuf) || !(dbinfo = mysql_store_result(sock))) { @@ -2116,22 +2664,22 @@ static int init_dumping(char *database) fprintf(md_result_file, "\n/*!40000 DROP DATABASE IF EXISTS %s;*/\n", qdatabase); - fprintf(md_result_file, - "\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ %s;\n", - qdatabase); - } - else + fprintf(md_result_file, + "\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ %s;\n", + qdatabase); + } + else { if (opt_drop_database) fprintf(md_result_file, "\n/*!40000 DROP DATABASE IF EXISTS %s*/;\n", qdatabase); - row = mysql_fetch_row(dbinfo); - if (row[1]) - { - fprintf(md_result_file,"\n%s;\n",row[1]); + row = mysql_fetch_row(dbinfo); + if (row[1]) + { + fprintf(md_result_file,"\n%s;\n",row[1]); } - } + } } fprintf(md_result_file,"\nUSE %s;\n", qdatabase); check_io(md_result_file); @@ -2178,14 +2726,14 @@ static int dump_all_tables_in_db(char *database) dynstr_append(&query, " READ /*!32311 LOCAL */,"); } if (numrows && mysql_real_query(sock, query.str, query.length-1)) - DBerror(sock, "when using LOCK TABLES"); + DB_error(sock, "when using LOCK TABLES"); /* We shall continue here, if --force was given */ dynstr_free(&query); } if (flush_logs) { if (mysql_refresh(sock, REFRESH_LOG)) - DBerror(sock, "when doing refresh"); + DB_error(sock, "when doing refresh"); /* We shall continue here, if --force was given */ } while ((table= getTableName(0))) @@ -2193,30 +2741,90 @@ static int dump_all_tables_in_db(char *database) char *end= strmov(afterdot, table); if (include_table(hash_key, end - hash_key)) { - numrows = getTableStructure(table, database); - if (!dFlag && numrows > 0) - dumpTable(numrows,table); + dump_table(table,database); my_free(order_by, MYF(MY_ALLOW_ZERO_PTR)); order_by= 0; + if (opt_dump_triggers && ! opt_xml && + mysql_get_server_version(sock) >= 50009) + dump_triggers_for_table(table, database); + } + } + if (opt_routines && !opt_xml && + mysql_get_server_version(sock) >= 50009) + { + DBUG_PRINT("info", ("Dumping routines for database %s", database)); + dump_routines_for_db(database); + } + if (opt_xml) + { + fputs("</database>\n", md_result_file); + check_io(md_result_file); + } + if (lock_tables) + VOID(mysql_query_with_error_report(sock, 0, "UNLOCK TABLES")); + return 0; +} /* dump_all_tables_in_db */ + + +/* + dump structure of views of database + + SYNOPSIS + dump_all_views_in_db() + database database name + + RETURN + 0 OK + 1 ERROR +*/ + +static my_bool dump_all_views_in_db(char *database) +{ + char *table; + uint numrows; + char table_buff[NAME_LEN*2+3]; + + if (opt_xml) + print_xml_tag1(md_result_file, "", "database name=", database, "\n"); + if (lock_tables) + { + DYNAMIC_STRING query; + init_dynamic_string(&query, "LOCK TABLES ", 256, 1024); + for (numrows= 0 ; (table= getTableName(1)); numrows++) + { + dynstr_append(&query, quote_name(table, table_buff, 1)); + dynstr_append(&query, " READ /*!32311 LOCAL */,"); } + if (numrows && mysql_real_query(sock, query.str, query.length-1)) + DB_error(sock, "when using LOCK TABLES"); + /* We shall continue here, if --force was given */ + dynstr_free(&query); + } + if (flush_logs) + { + if (mysql_refresh(sock, REFRESH_LOG)) + DB_error(sock, "when doing refresh"); + /* We shall continue here, if --force was given */ } + while ((table= getTableName(0))) + get_view_structure(table, database); if (opt_xml) { fputs("</database>\n", md_result_file); check_io(md_result_file); } if (lock_tables) - mysql_query_with_error_report(sock, 0, "UNLOCK TABLES"); + VOID(mysql_query_with_error_report(sock, 0, "UNLOCK TABLES")); return 0; } /* dump_all_tables_in_db */ /* - get_actual_table_name -- executes a SHOW TABLES LIKE '%s' to get the actual - table name from the server for the table name given on the command line. - we do this because the table name given on the command line may be a + get_actual_table_name -- executes a SHOW TABLES LIKE '%s' to get the actual + table name from the server for the table name given on the command line. + we do this because the table name given on the command line may be a different case (e.g. T1 vs t1) - + RETURN pointer to the table name 0 if error @@ -2225,35 +2833,37 @@ static int dump_all_tables_in_db(char *database) static char *get_actual_table_name(const char *old_table_name, MEM_ROOT *root) { char *name= 0; - ulong *lengths; - MYSQL_RES *tableRes; + MYSQL_RES *table_res; MYSQL_ROW row; char query[50 + 2*NAME_LEN]; char show_name_buff[FN_REFLEN]; DBUG_ENTER("get_actual_table_name"); - DBUG_PRINT("enter", ("old_table_name: %s", old_table_name)); /* Check memory for quote_for_like() */ DBUG_ASSERT(2*sizeof(old_table_name) < sizeof(show_name_buff)); - my_snprintf(query, sizeof(query), "SHOW TABLES LIKE %s", - quote_for_like(old_table_name, show_name_buff)); + my_snprintf(query, sizeof(query), "SHOW TABLES LIKE %s", + quote_for_like(old_table_name, show_name_buff)); if (mysql_query_with_error_report(sock, 0, query)) { safe_exit(EX_MYSQLERR); } - tableRes= mysql_store_result( sock ); - if (tableRes != NULL) + if ((table_res= mysql_store_result(sock))) { - my_ulonglong numRows= mysql_num_rows(tableRes); - if (numRows > 0) + my_ulonglong num_rows= mysql_num_rows(table_res); + if (num_rows > 0) { - row= mysql_fetch_row( tableRes ); - lengths= mysql_fetch_lengths(tableRes); + ulong *lengths; + /* + Return first row + TODO: Return all matching rows + */ + row= mysql_fetch_row(table_res); + lengths= mysql_fetch_lengths(table_res); name= strmake_root(root, row[0], lengths[0]); } - mysql_free_result(tableRes); + mysql_free_result(table_res); } DBUG_PRINT("exit", ("new_table_name: %s", name)); DBUG_RETURN(name); @@ -2262,11 +2872,10 @@ static char *get_actual_table_name(const char *old_table_name, MEM_ROOT *root) static int dump_selected_tables(char *db, char **table_names, int tables) { - uint numrows; char table_buff[NAME_LEN*+3]; DYNAMIC_STRING lock_tables_query; MEM_ROOT root; - char **dump_tables, **pos; + char **dump_tables, **pos, **end; DBUG_ENTER("dump_selected_tables"); if (init_dumping(db)) @@ -2274,20 +2883,18 @@ static int dump_selected_tables(char *db, char **table_names, int tables) init_alloc_root(&root, 8192, 0); if (!(dump_tables= pos= (char**) alloc_root(&root, tables * sizeof(char *)))) - exit(EX_EOM); + exit(EX_EOM); init_dynamic_string(&lock_tables_query, "LOCK TABLES ", 256, 1024); for (; tables > 0 ; tables-- , table_names++) { - /* the table name passed on commandline may be wrong case */ if ((*pos= get_actual_table_name(*table_names, &root))) { /* Add found table name to lock_tables_query */ if (lock_tables) { - dynstr_append(&lock_tables_query, - quote_name(*pos, table_buff, 1)); + dynstr_append(&lock_tables_query, quote_name(*pos, table_buff, 1)); dynstr_append(&lock_tables_query, " READ /*!32311 LOCAL */,"); } pos++; @@ -2300,31 +2907,47 @@ static int dump_selected_tables(char *db, char **table_names, int tables) /* We shall countinue here, if --force was given */ } } + end= pos; if (lock_tables) { if (mysql_real_query(sock, lock_tables_query.str, lock_tables_query.length-1)) - DBerror(sock, "when doing LOCK TABLES"); + DB_error(sock, "when doing LOCK TABLES"); /* We shall countinue here, if --force was given */ } dynstr_free(&lock_tables_query); if (flush_logs) { if (mysql_refresh(sock, REFRESH_LOG)) - DBerror(sock, "when doing refresh"); + DB_error(sock, "when doing refresh"); /* We shall countinue here, if --force was given */ } if (opt_xml) print_xml_tag1(md_result_file, "", "database name=", db, "\n"); /* Dump each selected table */ - for (; dump_tables < pos; dump_tables++) + for (pos= dump_tables; pos < end; pos++) + { + DBUG_PRINT("info",("Dumping table %s", *pos)); + dump_table(*pos, db); + if (opt_dump_triggers && + mysql_get_server_version(sock) >= 50009) + dump_triggers_for_table(*pos, db); + } + + /* Dump each selected view */ + if (seen_views) + { + for (pos= dump_tables; pos < end; pos++) + get_view_structure(*pos, db); + } + /* obtain dump of routines (procs/functions) */ + if (opt_routines && !opt_xml && + mysql_get_server_version(sock) >= 50009) { - DBUG_PRINT("info",("Dumping table %s", *dump_tables)); - numrows= getTableStructure(*dump_tables, db); - if (!dFlag && numrows > 0) - dumpTable(numrows, *dump_tables); + DBUG_PRINT("info", ("Dumping routines for database %s", db)); + dump_routines_for_db(db); } free_root(&root, MYF(0)); my_free(order_by, MYF(MY_ALLOW_ZERO_PTR)); @@ -2335,7 +2958,7 @@ static int dump_selected_tables(char *db, char **table_names, int tables) check_io(md_result_file); } if (lock_tables) - mysql_query_with_error_report(sock, 0, "UNLOCK TABLES"); + VOID(mysql_query_with_error_report(sock, 0, "UNLOCK TABLES")); DBUG_RETURN(0); } /* dump_selected_tables */ @@ -2364,14 +2987,14 @@ static int do_show_master_status(MYSQL *mysql_con) "recovery from\n--\n\n"); fprintf(md_result_file, "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", - comment_prefix, row[0], row[1]); + comment_prefix, row[0], row[1]); check_io(md_result_file); } else if (!ignore_errors) { /* SHOW MASTER STATUS reports nothing and --force is not enabled */ - my_printf_error(0, "Error: Binlogging on server not active", - MYF(0)); + my_printf_error(0, "Error: Binlogging on server not active", + MYF(0)); mysql_free_result(master); return 1; } @@ -2391,7 +3014,7 @@ static int do_flush_tables_read_lock(MYSQL *mysql_con) and most client connections are stalled. Of course, if a second long update starts between the two FLUSHes, we have that bad stall. */ - return + return ( mysql_query_with_error_report(mysql_con, 0, "FLUSH TABLES") || mysql_query_with_error_report(mysql_con, 0, "FLUSH TABLES WITH READ LOCK") ); @@ -2433,7 +3056,7 @@ static int start_transaction(MYSQL *mysql_con, my_bool consistent_read_now) static ulong find_set(TYPELIB *lib, const char *x, uint length, - char **err_pos, uint *err_len) + char **err_pos, uint *err_len) { const char *end= x + length; ulong found= 0; @@ -2475,10 +3098,10 @@ static ulong find_set(TYPELIB *lib, const char *x, uint length, /* Print a value with a prefix on file */ static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, - const char *prefix, const char *name, - int string_value) + const char *prefix, const char *name, + int string_value) { - MYSQL_FIELD *field; + MYSQL_FIELD *field; mysql_field_seek(result, 0); for ( ; (field = mysql_fetch_field(result)) ; row++) @@ -2487,76 +3110,107 @@ static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, { if (row[0] && row[0][0] && strcmp(row[0],"0")) /* Skip default */ { - fputc(' ',file); - fputs(prefix, file); - if (string_value) - unescape(file,row[0],(uint) strlen(row[0])); - else - fputs(row[0], file); - check_io(file); - return; + fputc(' ',file); + fputs(prefix, file); + if (string_value) + unescape(file,row[0],(uint) strlen(row[0])); + else + fputs(row[0], file); + check_io(file); + return; } } } - return; /* This shouldn't happen */ + return; /* This shouldn't happen */ } /* print_value */ /* - Check if we the table is one of the table types that should be ignored: - MRG_ISAM, MRG_MYISAM SYNOPSIS + + Check if we the table is one of the table types that should be ignored: + MRG_ISAM, MRG_MYISAM, if opt_delayed, if that table supports delayed inserts. + If the table should be altogether ignored, it returns a TRUE, FALSE if it + should not be ignored. If the user has selected to use INSERT DELAYED, it + sets the value of the bool pointer supports_delayed_inserts to 0 if not + supported, 1 if it is supported. + + ARGS + check_if_ignore_table() - table_name Table name to check + table_name Table name to check + table_type Type of table GLOBAL VARIABLES - sock MySQL socket - verbose Write warning messages + sock MySQL socket + verbose Write warning messages RETURN - 0 Table should be backuped - # Type of table (that should be skipped) + char (bit value) See IGNORE_ values at top */ -static const char *check_if_ignore_table(const char *table_name) +char check_if_ignore_table(const char *table_name, char *table_type) { + char result= IGNORE_NONE; char buff[FN_REFLEN+80], show_name_buff[FN_REFLEN]; MYSQL_RES *res; MYSQL_ROW row; - const char *result= 0; + DBUG_ENTER("check_if_ignore_table"); /* Check memory for quote_for_like() */ DBUG_ASSERT(2*sizeof(table_name) < sizeof(show_name_buff)); my_snprintf(buff, sizeof(buff), "show table status like %s", - quote_for_like(table_name, show_name_buff)); + quote_for_like(table_name, show_name_buff)); if (mysql_query_with_error_report(sock, &res, buff)) { if (mysql_errno(sock) != ER_PARSE_ERROR) - { /* If old MySQL version */ + { /* If old MySQL version */ if (verbose) - fprintf(stderr, - "-- Warning: Couldn't get status information for table %s (%s)\n", - table_name,mysql_error(sock)); - return 0; /* assume table is ok */ + fprintf(stderr, + "-- Warning: Couldn't get status information for table %s (%s)\n", + table_name,mysql_error(sock)); + DBUG_RETURN(result); /* assume table is ok */ } } if (!(row= mysql_fetch_row(res))) { fprintf(stderr, - "Error: Couldn't read status information for table %s (%s)\n", - table_name, mysql_error(sock)); + "Error: Couldn't read status information for table %s (%s)\n", + table_name, mysql_error(sock)); mysql_free_result(res); - return 0; /* assume table is ok */ - } - /* Some forward-compatibility: don't dump data from a VIEW */ - if (!row[1]) - result= "VIEW"; - else if (strcmp(row[1], (result= "MRG_MyISAM")) && - strcmp(row[1], (result= "MRG_ISAM"))) - result= 0; + DBUG_RETURN(result); /* assume table is ok */ + } + if (!(row[1])) + strmake(table_type, "VIEW", NAME_LEN-1); + else + { + /* + If the table type matches any of these, we do support delayed inserts. + Note: we do not want to skip dumping this table if if is not one of + these types, but we do want to use delayed inserts in the dump if + the table type is _NOT_ one of these types + */ + strmake(table_type, row[1], NAME_LEN-1); + if (opt_delayed) + { + if (strcmp(table_type,"MyISAM") && + strcmp(table_type,"ISAM") && + strcmp(table_type,"ARCHIVE") && + strcmp(table_type,"HEAP") && + strcmp(table_type,"MEMORY")) + result= IGNORE_INSERT_DELAYED; + } + + /* + If these two types, we do want to skip dumping the table + */ + if (!dFlag && + (!strcmp(table_type,"MRG_MyISAM") || !strcmp(table_type,"MRG_ISAM"))) + result= IGNORE_DATA; + } mysql_free_result(res); - return result; + DBUG_RETURN(result); } /* @@ -2576,6 +3230,7 @@ static const char *check_if_ignore_table(const char *table_name) or if there is some failure. It is better to continue to dump the table unsorted, rather than exit without dumping the data. */ + static char *primary_key_fields(const char *table_name) { MYSQL_RES *res = NULL; @@ -2585,8 +3240,8 @@ static char *primary_key_fields(const char *table_name) uint result_length = 0; char *result = 0; - my_snprintf(show_keys_buff, sizeof(show_keys_buff), - "SHOW KEYS FROM %s", table_name); + my_snprintf(show_keys_buff, sizeof(show_keys_buff), + "SHOW KEYS FROM %s", table_name); if (mysql_query(sock, show_keys_buff) || !(res = mysql_store_result(sock))) { @@ -2612,11 +3267,13 @@ static char *primary_key_fields(const char *table_name) } /* Build the ORDER BY clause result */ - if (result_length) { + if (result_length) + { char *end; /* result (terminating \0 is already in result_length) */ result = my_malloc(result_length + 10, MYF(MY_WME)); - if (!result) { + if (!result) + { fprintf(stderr, "Error: Not enough memory to store ORDER BY clause\n"); goto cleanup; } @@ -2635,13 +3292,236 @@ cleanup: } +/* + Replace a substring + + SYNOPSIS + replace + ds_str The string to search and perform the replace in + search_str The string to search for + search_len Length of the string to search for + replace_str The string to replace with + replace_len Length of the string to replace with + + RETURN + 0 String replaced + 1 Could not find search_str in str +*/ + +static int replace(DYNAMIC_STRING *ds_str, + const char *search_str, ulong search_len, + const char *replace_str, ulong replace_len) +{ + DYNAMIC_STRING ds_tmp; + const char *start= strstr(ds_str->str, search_str); + if (!start) + return 1; + init_dynamic_string(&ds_tmp, "", + ds_str->length + replace_len, 256); + dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str); + dynstr_append_mem(&ds_tmp, replace_str, replace_len); + dynstr_append(&ds_tmp, start + search_len); + dynstr_set(ds_str, ds_tmp.str); + dynstr_free(&ds_tmp); + return 0; +} + + +/* + Getting VIEW structure + + SYNOPSIS + get_view_structure() + table view name + db db name + + RETURN + 0 OK + 1 ERROR +*/ + +static my_bool get_view_structure(char *table, char* db) +{ + MYSQL_RES *table_res; + MYSQL_ROW row; + MYSQL_FIELD *field; + char *result_table, *opt_quoted_table; + char table_buff[NAME_LEN*2+3]; + char table_buff2[NAME_LEN*2+3]; + char query[QUERY_LENGTH]; + FILE *sql_file = md_result_file; + DBUG_ENTER("get_view_structure"); + + if (tFlag) /* Don't write table creation info */ + DBUG_RETURN(0); + + if (verbose) + fprintf(stderr, "-- Retrieving view structure for table %s...\n", table); + +#ifdef NOT_REALLY_USED_YET + sprintf(insert_pat,"SET OPTION SQL_QUOTE_SHOW_CREATE=%d", + (opt_quoted || opt_keywords)); +#endif + + result_table= quote_name(table, table_buff, 1); + opt_quoted_table= quote_name(table, table_buff2, 0); + + my_snprintf(query, sizeof(query), "SHOW CREATE TABLE %s", result_table); + if (mysql_query_with_error_report(sock, &table_res, query)) + { + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); + } + + /* Check if this is a view */ + field= mysql_fetch_field_direct(table_res, 0); + if (strcmp(field->name, "View") != 0) + { + if (verbose) + fprintf(stderr, "-- It's base table, skipped\n"); + DBUG_RETURN(0); + } + + /* If requested, open separate .sql file for this view */ + if (path) + { + if (!(sql_file= open_sql_file_for_table(table))) + { + safe_exit(EX_MYSQLERR); + DBUG_RETURN(1); + } + write_header(sql_file, db); + } + + if (!opt_xml && opt_comments) + { + fprintf(sql_file, "\n--\n-- Final view structure for view %s\n--\n\n", + result_table); + check_io(sql_file); + } + if (opt_drop) + { + fprintf(sql_file, "/*!50001 DROP TABLE IF EXISTS %s*/;\n", + opt_quoted_table); + fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n", + opt_quoted_table); + check_io(sql_file); + } + + + my_snprintf(query, sizeof(query), + "SELECT CHECK_OPTION, DEFINER, SECURITY_TYPE " \ + "FROM information_schema.views " \ + "WHERE table_name=\"%s\" AND table_schema=\"%s\"", table, db); + if (mysql_query(sock, query)) + { + /* + Use the raw output from SHOW CREATE TABLE if + information_schema query fails. + */ + row= mysql_fetch_row(table_res); + fprintf(sql_file, "/*!50001 %s */;\n", row[1]); + check_io(sql_file); + mysql_free_result(table_res); + } + else + { + char *ptr; + ulong *lengths; + char search_buf[256], replace_buf[256]; + ulong search_len, replace_len; + DYNAMIC_STRING ds_view; + + /* Save the result of SHOW CREATE TABLE in ds_view */ + row= mysql_fetch_row(table_res); + lengths= mysql_fetch_lengths(table_res); + init_dynamic_string(&ds_view, row[1], lengths[1] + 1, 1024); + mysql_free_result(table_res); + + /* Get the result from "select ... information_schema" */ + if (!(table_res= mysql_store_result(sock))) + { + safe_exit(EX_MYSQLERR); + DBUG_RETURN(1); + } + row= mysql_fetch_row(table_res); + lengths= mysql_fetch_lengths(table_res); + + /* + "WITH %s CHECK OPTION" is available from 5.0.2 + Surround it with !50002 comments + */ + if (strcmp(row[0], "NONE")) + { + + ptr= search_buf; + search_len= (ulong)(strxmov(ptr, "WITH ", row[0], + " CHECK OPTION", NullS) - ptr); + ptr= replace_buf; + replace_len=(ulong)(strxmov(ptr, "*/\n/*!50002 WITH ", row[0], + " CHECK OPTION", NullS) - ptr); + replace(&ds_view, search_buf, search_len, replace_buf, replace_len); + } + + /* + "DEFINER=%s SQL SECURITY %s" is available from 5.0.13 + Surround it with !50013 comments + */ + { + uint user_name_len; + char user_name_str[USERNAME_LENGTH + 1]; + char quoted_user_name_str[USERNAME_LENGTH * 2 + 3]; + uint host_name_len; + char host_name_str[HOSTNAME_LENGTH + 1]; + char quoted_host_name_str[HOSTNAME_LENGTH * 2 + 3]; + + parse_user(row[1], lengths[1], user_name_str, &user_name_len, + host_name_str, &host_name_len); + + ptr= search_buf; + search_len= + (ulong)(strxmov(ptr, "DEFINER=", + quote_name(user_name_str, quoted_user_name_str, FALSE), + "@", + quote_name(host_name_str, quoted_host_name_str, FALSE), + " SQL SECURITY ", row[2], NullS) - ptr); + ptr= replace_buf; + replace_len= + (ulong)(strxmov(ptr, "*/\n/*!50013 DEFINER=", + quote_name(user_name_str, quoted_user_name_str, FALSE), + "@", + quote_name(host_name_str, quoted_host_name_str, FALSE), + " SQL SECURITY ", row[2], + " */\n/*!50001", NullS) - ptr); + replace(&ds_view, search_buf, search_len, replace_buf, replace_len); + } + + /* Dump view structure to file */ + fprintf(sql_file, "/*!50001 %s */;\n", ds_view.str); + check_io(sql_file); + mysql_free_result(table_res); + dynstr_free(&ds_view); + } + + /* If a separate .sql file was opened, close it now */ + if (sql_file != md_result_file) + { + fputs("\n", sql_file); + write_footer(sql_file); + my_fclose(sql_file, MYF(MY_WME)); + } + DBUG_RETURN(0); +} + + int main(int argc, char **argv) { + MY_INIT("mysqldump"); + compatible_mode_normal_str[0]= 0; default_charset= (char *)mysql_universal_client_charset; bzero((char*) &ignore_table, sizeof(ignore_table)); - MY_INIT("mysqldump"); if (get_options(&argc, &argv)) { my_end(0); diff --git a/client/mysqlimport.c b/client/mysqlimport.c index a7872740c0c..1f9b96f91be 100644 --- a/client/mysqlimport.c +++ b/client/mysqlimport.c @@ -121,7 +121,7 @@ static struct my_option my_long_options[] = NO_ARG, 0, 0, 0, 0, 0, 0}, #endif {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, - (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe,memory).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -384,6 +384,8 @@ static MYSQL *db_connect(char *host, char *database, char *user, char *passwd) if (opt_use_ssl) mysql_ssl_set(&mysql_connection, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql_connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); #endif if (opt_protocol) mysql_options(&mysql_connection,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); @@ -398,6 +400,7 @@ static MYSQL *db_connect(char *host, char *database, char *user, char *passwd) ignore_errors=0; /* NO RETURN FROM db_error */ db_error(&mysql_connection); } + mysql_connection.reconnect= 0; if (verbose) fprintf(stdout, "Selecting database %s\n", database); if (mysql_select_db(sock, database)) diff --git a/client/mysqlshow.c b/client/mysqlshow.c index 3b714c20ba7..d090495ff81 100644 --- a/client/mysqlshow.c +++ b/client/mysqlshow.c @@ -28,7 +28,8 @@ #include <sslopt-vars.h> static my_string host=0,opt_password=0,user=0; -static my_bool opt_show_keys=0,opt_compress=0,opt_status=0, tty_password=0; +static my_bool opt_show_keys= 0, opt_compress= 0, opt_count=0, opt_status= 0, + tty_password= 0, opt_table_type= 0; static uint opt_verbose=0; static char *default_charset= (char*) MYSQL_DEFAULT_CHARSET_NAME; @@ -70,8 +71,7 @@ int main(int argc, char **argv) char *pos= argv[argc-1], *to; for (to= pos ; *pos ; pos++, to++) { - switch (*pos) - { + switch (*pos) { case '*': *pos= '%'; first_argument_uses_wildcards= 1; @@ -109,6 +109,8 @@ int main(int argc, char **argv) if (opt_use_ssl) 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_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); #endif if (opt_protocol) mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); @@ -125,6 +127,7 @@ int main(int argc, char **argv) fprintf(stderr,"%s: %s\n",my_progname,mysql_error(&mysql)); exit(1); } + mysql.reconnect= 1; switch (argc) { @@ -165,6 +168,10 @@ static struct my_option my_long_options[] = {"default-character-set", OPT_DEFAULT_CHARSET, "Set the default character set.", (gptr*) &default_charset, (gptr*) &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"count", OPT_COUNT, + "Show number of rows per table (may be slow for not MyISAM tables)", + (gptr*) &opt_count, (gptr*) &opt_count, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, {"compress", 'C', "Use compression in server/client protocol.", (gptr*) &opt_compress, (gptr*) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -183,7 +190,7 @@ static struct my_option my_long_options[] = "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}, {"port", 'P', "Port number to use for connection.", (gptr*) &opt_mysql_port, - (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + (gptr*) &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #ifdef __WIN__ {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, @@ -196,6 +203,9 @@ static struct my_option my_long_options[] = "Base name of shared memory.", (gptr*) &shared_memory_base_name, (gptr*) &shared_memory_base_name, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #endif + {"show-table-type", 't', "Show table type column.", + (gptr*) &opt_table_type, (gptr*) &opt_table_type, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, {"socket", 'S', "Socket file to use for connection.", (gptr*) &opt_mysql_unix_port, (gptr*) &opt_mysql_unix_port, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -312,6 +322,14 @@ get_options(int *argc,char ***argv) if (tty_password) opt_password=get_tty_password(NullS); + if (opt_count) + { + /* + We need to set verbose to 2 as we need to change the output to include + the number-of-rows column + */ + opt_verbose= 2; + } return; } @@ -326,7 +344,7 @@ list_dbs(MYSQL *mysql,const char *wild) char query[255]; MYSQL_FIELD *field; MYSQL_RES *result; - MYSQL_ROW row, trow, rrow; + MYSQL_ROW row, rrow; if (!(result=mysql_list_dbs(mysql,wild))) { @@ -356,11 +374,6 @@ list_dbs(MYSQL *mysql,const char *wild) if (opt_verbose) { - /* - * Original code by MG16373; Slightly modified by Monty. - * Print now the count of tables and rows for each database. - */ - if (!(mysql_select_db(mysql,row[0]))) { MYSQL_RES *tresult = mysql_list_tables(mysql,(char*)NULL); @@ -370,6 +383,8 @@ list_dbs(MYSQL *mysql,const char *wild) rowcount = 0; if (opt_verbose > 1) { + /* Print the count of tables and rows for each database */ + MYSQL_ROW trow; while ((trow = mysql_fetch_row(tresult))) { sprintf(query,"SELECT COUNT(*) FROM `%s`",trow[0]); @@ -426,7 +441,7 @@ list_tables(MYSQL *mysql,const char *db,const char *table) { const char *header; uint head_length, counter = 0; - char query[255], rows[64], fields[16]; + char query[255], rows[NAME_LEN], fields[16]; MYSQL_FIELD *field; MYSQL_RES *result; MYSQL_ROW row, rrow; @@ -437,7 +452,20 @@ list_tables(MYSQL *mysql,const char *db,const char *table) mysql_error(mysql)); return 1; } - if (!(result=mysql_list_tables(mysql,table))) + if (table) + { + /* + We just hijack the 'rows' variable for a bit to store the escaped + table name + */ + mysql_real_escape_string(mysql, rows, table, (unsigned long)strlen(table)); + my_snprintf(query, sizeof(query), "show%s tables like '%s'", + opt_table_type ? " full" : "", rows); + } + else + my_snprintf(query, sizeof(query), "show%s tables", + opt_table_type ? " full" : ""); + if (mysql_query(mysql, query) || !(result= mysql_store_result(mysql))) { fprintf(stderr,"%s: Cannot list tables in %s: %s\n",my_progname,db, mysql_error(mysql)); @@ -454,19 +482,30 @@ list_tables(MYSQL *mysql,const char *db,const char *table) if (head_length < field->max_length) head_length=field->max_length; - if (!opt_verbose) - print_header(header,head_length,NullS); - else if (opt_verbose == 1) - print_header(header,head_length,"Columns",8,NullS); + if (opt_table_type) + { + if (!opt_verbose) + print_header(header,head_length,"table_type",10,NullS); + else if (opt_verbose == 1) + print_header(header,head_length,"table_type",10,"Columns",8,NullS); + else + { + print_header(header,head_length,"table_type",10,"Columns",8, + "Total Rows",10,NullS); + } + } else - print_header(header,head_length,"Columns",8, "Total Rows",10,NullS); + { + if (!opt_verbose) + print_header(header,head_length,NullS); + else if (opt_verbose == 1) + print_header(header,head_length,"Columns",8,NullS); + else + print_header(header,head_length,"Columns",8, "Total Rows",10,NullS); + } while ((row = mysql_fetch_row(result))) { - /* - * Modified by MG16373 - * Print now the count of rows for each table. - */ counter++; if (opt_verbose > 0) { @@ -486,6 +525,7 @@ list_tables(MYSQL *mysql,const char *db,const char *table) if (opt_verbose > 1) { + /* Print the count of rows for each table */ sprintf(query,"SELECT COUNT(*) FROM `%s`",row[0]); if (!(mysql_query(mysql,query))) { @@ -508,17 +548,31 @@ list_tables(MYSQL *mysql,const char *db,const char *table) strmov(rows,"N/A"); } } - if (!opt_verbose) - print_row(row[0],head_length,NullS); - else if (opt_verbose == 1) - print_row(row[0],head_length, fields,8, NullS); - else - print_row(row[0],head_length, fields,8, rows,10, NullS); + if (opt_table_type) + { + if (!opt_verbose) + print_row(row[0],head_length,row[1],10,NullS); + else if (opt_verbose == 1) + print_row(row[0],head_length,row[1],10,fields,8,NullS); + else + print_row(row[0],head_length,row[1],10,fields,8,rows,10,NullS); + } + else + { + if (!opt_verbose) + print_row(row[0],head_length,NullS); + else if (opt_verbose == 1) + print_row(row[0],head_length, fields,8, NullS); + else + print_row(row[0],head_length, fields,8, rows,10, NullS); + } } print_trailer(head_length, - (opt_verbose > 0 ? 8 : 0), - (opt_verbose > 1 ? 10 :0), + (opt_table_type ? 10 : opt_verbose > 0 ? 8 : 0), + (opt_table_type ? (opt_verbose > 0 ? 8 : 0) + : (opt_verbose > 1 ? 10 :0)), + !opt_table_type ? 0 : opt_verbose > 1 ? 10 :0, 0); if (counter && opt_verbose) @@ -536,7 +590,7 @@ list_table_status(MYSQL *mysql,const char *db,const char *wild) MYSQL_RES *result; MYSQL_ROW row; - end=strxmov(query,"show table status from ",db,NullS); + end=strxmov(query,"show table status from `",db,"`",NullS); if (wild && wild[0]) strxmov(end," like '",wild,"'",NullS); if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) @@ -562,8 +616,8 @@ list_table_status(MYSQL *mysql,const char *db,const char *wild) } /* -** list fields uses field interface as an example of how to parse -** a MYSQL FIELD + list fields uses field interface as an example of how to parse + a MYSQL FIELD */ static int @@ -573,6 +627,8 @@ list_fields(MYSQL *mysql,const char *db,const char *table, char query[1024],*end; MYSQL_RES *result; MYSQL_ROW row; + ulong rows; + LINT_INIT(rows); if (mysql_select_db(mysql,db)) { @@ -580,6 +636,21 @@ list_fields(MYSQL *mysql,const char *db,const char *table, mysql_error(mysql)); return 1; } + + if (opt_count) + { + sprintf(query,"select count(*) from `%s`", table); + if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot get record count for db: %s, table: %s: %s\n", + my_progname,db,table,mysql_error(mysql)); + return 1; + } + row= mysql_fetch_row(result); + rows= (ulong) strtoull(row[0], (char**) 0, 10); + mysql_free_result(result); + } + end=strmov(strmov(strmov(query,"show /*!32332 FULL */ columns from `"),table),"`"); if (wild && wild[0]) strxmov(end," like '",wild,"'",NullS); @@ -590,8 +661,9 @@ list_fields(MYSQL *mysql,const char *db,const char *table, return 1; } - printf("Database: %s Table: %s Rows: %lu", db,table, - (ulong) mysql->extra_info); + printf("Database: %s Table: %s", db, table); + if (opt_count) + printf(" Rows: %lu", rows); if (wild && wild[0]) printf(" Wildcard: %s",wild); putchar('\n'); @@ -625,7 +697,7 @@ list_fields(MYSQL *mysql,const char *db,const char *table, /***************************************************************************** -** General functions to print a nice ascii-table from data + General functions to print a nice ascii-table from data *****************************************************************************/ static void diff --git a/client/mysqltest.c b/client/mysqltest.c index 3f2e7d8edb6..2fc09fbc3d2 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -42,7 +42,7 @@ **********************************************************************/ -#define MTEST_VERSION "2.5" +#define MTEST_VERSION "2.6" #include <my_global.h> #include <mysql_embed.h> @@ -70,13 +70,12 @@ # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) # endif #endif -#define MAX_QUERY 131072 +/* MAX_QUERY is 256K -- there is a test in sp-big that is >128K */ +#define MAX_QUERY (256*1024) #define MAX_VAR_NAME 256 #define MAX_COLUMNS 256 -#define PAD_SIZE 128 #define MAX_CONS 128 #define MAX_INCLUDE_DEPTH 16 -#define LAZY_GUESS_BUF_SIZE 8192 #define INIT_Q_LINES 1024 #define MIN_VAR_ALLOC 32 #define BLOCK_STACK_DEPTH 32 @@ -88,14 +87,6 @@ #endif #define MAX_SERVER_ARGS 64 -/* - Sometimes in a test the client starts before - the server - to solve the problem, we try again - after some sleep if connection fails the first - time -*/ -#define CON_RETRY_SLEEP 2 -#define MAX_CON_TRIES 5 #define SLAVE_POLL_INTERVAL 300000 /* 0.3 of a sec */ #define DEFAULT_DELIMITER ";" @@ -108,7 +99,8 @@ enum {OPT_MANAGER_USER=256,OPT_MANAGER_HOST,OPT_MANAGER_PASSWD, OPT_MANAGER_PORT,OPT_MANAGER_WAIT_TIMEOUT, OPT_SKIP_SAFEMALLOC, OPT_SSL_SSL, OPT_SSL_KEY, OPT_SSL_CERT, OPT_SSL_CA, OPT_SSL_CAPATH, - OPT_SSL_CIPHER,OPT_PS_PROTOCOL}; + OPT_SSL_CIPHER,OPT_PS_PROTOCOL,OPT_SP_PROTOCOL,OPT_CURSOR_PROTOCOL, + OPT_VIEW_PROTOCOL, OPT_SSL_VERIFY_SERVER_CERT, OPT_MAX_CONNECT_RETRIES}; /* ************************************************************************ */ /* @@ -118,7 +110,7 @@ enum {OPT_MANAGER_USER=256,OPT_MANAGER_HOST,OPT_MANAGER_PASSWD, set to type ERR_EMPTY. When an SQL statement returns an error, we use this list to check if this is an expected error. */ - + enum match_err_type { ERR_EMPTY= 0, @@ -136,18 +128,35 @@ typedef struct } code; } match_err; +typedef struct +{ + const char *name; + long code; +} st_error; + +static st_error global_error[] = +{ +#include <mysqld_ername.h> + { 0, 0 } +}; + static match_err global_expected_errno[MAX_EXPECTED_ERRORS]; static uint global_expected_errors; /* ************************************************************************ */ -static int record = 0, opt_sleep=0; +static int record= 0, opt_sleep= -1; static char *db = 0, *pass=0; const char *user = 0, *host = 0, *unix_sock = 0, *opt_basedir="./"; static int port = 0; +static int opt_max_connect_retries; static my_bool opt_big_test= 0, opt_compress= 0, silent= 0, verbose = 0; -static my_bool tty_password= 0, ps_protocol= 0, ps_protocol_enabled= 0; -static uint start_lineno, *lineno; +static my_bool tty_password= 0; +static my_bool ps_protocol= 0, ps_protocol_enabled= 0; +static my_bool sp_protocol= 0, sp_protocol_enabled= 0; +static my_bool view_protocol= 0, view_protocol_enabled= 0; +static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; +static int parsing_disabled= 0; const char *manager_user="root",*manager_host=0; char *manager_pass=0; int manager_port=MYSQL_MANAGER_PORT; @@ -162,17 +171,29 @@ typedef struct { FILE* file; const char *file_name; + uint lineno; /* Current line in file */ } test_file; static test_file file_stack[MAX_INCLUDE_DEPTH]; static test_file* cur_file; static test_file* file_stack_end; +uint start_lineno; /* Start line of query */ -static uint lineno_stack[MAX_INCLUDE_DEPTH]; static char TMPDIR[FN_REFLEN]; static char delimiter[MAX_DELIMITER]= DEFAULT_DELIMITER; static uint delimiter_length= 1; +/* Block stack */ +enum block_cmd { cmd_none, cmd_if, cmd_while }; +typedef struct +{ + int line; /* Start line of block */ + my_bool ok; /* Should block be executed */ + enum block_cmd cmd; /* Command owning the block */ +} BLOCK; +static BLOCK block_stack[BLOCK_STACK_DEPTH]; +static BLOCK *cur_block, *block_stack_end; + static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ static const char *charset_name= "latin1"; /* Default character set name */ @@ -188,13 +209,17 @@ static int got_end_timer= FALSE; static void timer_output(void); static ulonglong timer_now(void); -static my_regex_t ps_re; /* Holds precompiled re for valid PS statements */ -static void ps_init_re(void); -static int ps_match_re(char *); -static char *ps_eprint(int); -static void ps_free_reg(void); +/* Precompiled re's */ +static my_regex_t ps_re; /* the query can be run using PS protocol */ +static my_regex_t sp_re; /* the query can be run as a SP */ +static my_regex_t view_re; /* the query can be run as a view*/ + +static void init_re(void); +static int match_re(my_regex_t *, char *); +static void free_re(void); -static const char *embedded_server_groups[] = { +static const char *embedded_server_groups[]= +{ "server", "embedded", "mysqltest_SERVER", @@ -214,7 +239,10 @@ typedef struct struct connection { MYSQL mysql; + /* Used when creating views and sp, to avoid implicit commit */ + MYSQL* util_mysql; char *name; + MYSQL_STMT* stmt; }; typedef struct @@ -222,8 +250,6 @@ typedef struct int read_lines,current_line; } PARSER; -MYSQL_RES *last_result=0; - PARSER parser; MASTER_POS master_pos; /* if set, all results are concated and compared against this file */ @@ -241,21 +267,11 @@ typedef struct int alloced; } VAR; -#if defined(__NETWARE__) || defined(__WIN__) -/* - Netware doesn't proved environment variable substitution that is done - by the shell in unix environments. We do this in the following function: -*/ - -static char *subst_env_var(const char *cmd); -static FILE *my_popen(const char *cmd, const char *mode); -#define popen(A,B) my_popen((A),(B)) -#endif /* __NETWARE__ */ - VAR var_reg[10]; /*Perl/shell-like variable registers */ HASH var_hash; my_bool disable_query_log=0, disable_result_log=0, disable_warnings=0; +my_bool disable_ps_warnings= 0; my_bool disable_info= 1; /* By default off */ my_bool abort_on_error= 1; @@ -266,7 +282,7 @@ struct connection* cur_con, *next_con, *cons_end; enum enum_commands { Q_CONNECTION=1, Q_QUERY, -Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, +Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, Q_INC, Q_DEC, Q_SOURCE, Q_DISCONNECT, Q_LET, Q_ECHO, @@ -286,6 +302,7 @@ Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG, Q_SERVER_START, Q_SERVER_STOP,Q_REQUIRE_MANAGER, Q_WAIT_FOR_SLAVE_TO_STOP, Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS, +Q_ENABLE_PS_WARNINGS, Q_DISABLE_PS_WARNINGS, Q_ENABLE_INFO, Q_DISABLE_INFO, Q_ENABLE_METADATA, Q_DISABLE_METADATA, Q_EXEC, Q_DELIMITER, @@ -297,6 +314,7 @@ Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, Q_EXIT, Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, Q_IF, +Q_DISABLE_PARSING, Q_ENABLE_PARSING, Q_UNKNOWN, /* Unknown command. */ Q_COMMENT, /* Comments, ignored. */ @@ -348,8 +366,10 @@ const char *command_names[]= "enable_rpl_parse", "disable_rpl_parse", "eval_result", + /* Enable/disable that the _query_ is logged to result file */ "enable_query_log", "disable_query_log", + /* Enable/disable that the _result_ from a query is logged to result file */ "enable_result_log", "disable_result_log", "server_start", @@ -358,6 +378,8 @@ const char *command_names[]= "wait_for_slave_to_stop", "enable_warnings", "disable_warnings", + "enable_ps_warnings", + "disable_ps_warnings", "enable_info", "disable_info", "enable_metadata", @@ -379,19 +401,11 @@ const char *command_names[]= "disable_reconnect", "enable_reconnect", "if", + "disable_parsing", + "enable_parsing", 0 }; -/* Block stack */ -typedef struct -{ - int line; /* Start line of block */ - my_bool ok; /* Should block be executed */ - enum enum_commands cmd; /* Command owning the block */ -} BLOCK; -static BLOCK block_stack[BLOCK_STACK_DEPTH]; -static BLOCK *cur_block, *block_stack_end; - TYPELIB command_typelib= {array_elements(command_names),"", command_names, 0}; @@ -405,8 +419,8 @@ static VAR* var_init(VAR* v, const char *name, int name_len, const char *val, static void var_free(void* v); -int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname); -void reject_dump(const char *record_file, char *buf, int size); +void dump_result_to_reject_file(const char *record_file, char *buf, int size); +void dump_result_to_log_file(const char *record_file, char *buf, int size); int close_connection(struct st_query*); static void set_charset(struct st_query*); @@ -427,20 +441,21 @@ typedef struct st_pointer_array { /* when using array-strings */ struct st_replace; struct st_replace *init_replace(my_string *from, my_string *to, uint count, my_string word_end_chars); -uint replace_strings(struct st_replace *rep, my_string *start, - uint *max_length, const char *from); void free_replace(); static int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string name); +static void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, + const char *from, int len); void free_pointer_array(POINTER_ARRAY *pa); -static int initialize_replace_buffer(void); -static void free_replace_buffer(void); -static void do_eval(DYNAMIC_STRING *query_eval, const char *query); -void str_to_file(const char *fname, char *str, int size); -int do_server_op(struct st_query *q,const char *op); +static void do_eval(DYNAMIC_STRING *query_eval, const char *query, + my_bool pass_through_escape_chars); +static void str_to_file(const char *fname, char *str, int size); + +#ifdef __WIN__ +static void free_tmp_sh_file(); +static void free_win_path_patterns(); +#endif struct st_replace *glob_replace; -static char *out_buff; -static uint out_length; static int eval_result = 0; /* For column replace */ @@ -459,12 +474,17 @@ my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; } #endif static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, int len); -static int handle_no_error(struct st_query *q); - -static void do_eval(DYNAMIC_STRING* query_eval, const char *query) +static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); +static void handle_error(const char *query, struct st_query *q, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds); +static void handle_no_error(struct st_query *q); + +static void do_eval(DYNAMIC_STRING* query_eval, const char *query, + my_bool pass_through_escape_chars) { const char *p; - register char c; + register char c, next_c; register int escaped = 0; VAR* v; DBUG_ENTER("do_eval"); @@ -486,13 +506,25 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query) } break; case '\\': + next_c= *(p+1); if (escaped) { escaped = 0; dynstr_append_mem(query_eval, p, 1); } - else + else if (next_c == '\\' || next_c == '$') + { + /* Set escaped only if next char is \ or $ */ escaped = 1; + + if (pass_through_escape_chars) + { + /* The escape char should be added to the output string. */ + dynstr_append_mem(query_eval, p, 1); + } + } + else + dynstr_append_mem(query_eval, p, 1); break; default: dynstr_append_mem(query_eval, p, 1); @@ -506,11 +538,14 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query) static void close_cons() { DBUG_ENTER("close_cons"); - if (last_result) - mysql_free_result(last_result); for (--next_con; next_con >= cons; --next_con) { + if (next_con->stmt) + mysql_stmt_close(next_con->stmt); + next_con->stmt= 0; mysql_close(&next_con->mysql); + if (next_con->util_mysql) + mysql_close(next_con->util_mysql); my_free(next_con->name, MYF(MY_ALLOW_ZERO_PTR)); } DBUG_VOID_RETURN; @@ -520,7 +555,7 @@ static void close_cons() static void close_files() { DBUG_ENTER("close_files"); - for (; cur_file != (file_stack-1) ; cur_file--) + for (; cur_file >= file_stack; cur_file--) { DBUG_PRINT("info", ("file_name: %s", cur_file->file_name)); if (cur_file->file && cur_file->file != stdin) @@ -564,8 +599,11 @@ static void free_used_memory() my_free(pass,MYF(MY_ALLOW_ZERO_PTR)); free_defaults(default_argv); mysql_server_end(); - if (ps_protocol) - ps_free_reg(); + free_re(); +#ifdef __WIN__ + free_tmp_sh_file(); + free_win_path_patterns(); +#endif DBUG_VOID_RETURN; } @@ -573,6 +611,8 @@ static void die(const char *fmt, ...) { va_list args; DBUG_ENTER("die"); + + /* Print the error message */ va_start(args, fmt); if (fmt) { @@ -587,8 +627,18 @@ static void die(const char *fmt, ...) fflush(stderr); } va_end(args); + + /* Dump the result that has been accumulated so far to .log file */ + if (result_file && ds_res.length) + dump_result_to_log_file(result_file, ds_res.str, ds_res.length); + + /* Clean up and exit */ free_used_memory(); my_end(MY_CHECK_ERROR); + + if (!silent) + printf("not ok\n"); + exit(1); } @@ -615,7 +665,7 @@ static void verbose_msg(const char *fmt, ...) va_start(args, fmt); fprintf(stderr, "mysqltest: "); - if (start_lineno > 0) + if (start_lineno != 0) fprintf(stderr, "At line %u: ", start_lineno); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); @@ -631,7 +681,7 @@ void init_parser() } -int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) +static int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) { MY_STAT stat_info; char *tmp, *res_ptr; @@ -656,6 +706,7 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) { DBUG_PRINT("info",("Size differs: result size: %u file size: %u", ds->length, stat_info.st_size)); + DBUG_PRINT("info",("result: '%s'", ds->str)); DBUG_RETURN(RESULT_LENGTH_MISMATCH); } if (!(tmp = (char*) my_malloc(stat_info.st_size + 1, MYF(MY_WME)))) @@ -669,7 +720,7 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) init_dynamic_string(&res_ds, "", 0, 65536); if (eval_result) { - do_eval(&res_ds, tmp); + do_eval(&res_ds, tmp, FALSE); res_ptr = res_ds.str; if ((res_len = res_ds.length) != ds->length) { @@ -698,11 +749,25 @@ err: DBUG_RETURN(res); } -static int check_result(DYNAMIC_STRING* ds, const char *fname, +/* + Check the content of ds against content of file fname + + SYNOPSIS + check_result + ds - content to be checked + fname - name of file to check against + require_option - if set and check fails, the test will be aborted + with the special exit code "not supported test" + + RETURN VALUES + error - the function will not return + +*/ +static void check_result(DYNAMIC_STRING* ds, const char *fname, my_bool require_option) { - int error= RESULT_OK; int res= dyn_string_cmp(ds, fname); + DBUG_ENTER("check_result"); if (res && require_option) abort_not_supported_test(); @@ -710,19 +775,18 @@ static int check_result(DYNAMIC_STRING* ds, const char *fname, case RESULT_OK: break; /* ok */ case RESULT_LENGTH_MISMATCH: - verbose_msg("Result length mismatch"); - error= RESULT_LENGTH_MISMATCH; + dump_result_to_reject_file(fname, ds->str, ds->length); + die("Result length mismatch"); break; case RESULT_CONTENT_MISMATCH: - verbose_msg("Result content mismatch"); - error= RESULT_CONTENT_MISMATCH; + dump_result_to_reject_file(fname, ds->str, ds->length); + die("Result content mismatch"); break; default: /* impossible */ die("Unknown error code from dyn_string_cmp()"); } - if (error) - reject_dump(fname, ds->str, ds->length); - return error; + + DBUG_VOID_RETURN; } @@ -839,7 +903,7 @@ int open_file(const char *name) die("Could not open file %s", buff); } cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); - *++lineno=1; + cur_file->lineno=1; DBUG_RETURN(0); } @@ -910,17 +974,7 @@ int do_require_manager(struct st_query *query __attribute__((unused)) ) } #ifndef EMBEDDED_LIBRARY -int do_server_start(struct st_query *q) -{ - return do_server_op(q, "start"); -} - -int do_server_stop(struct st_query *q) -{ - return do_server_op(q, "stop"); -} - -int do_server_op(struct st_query *q, const char *op) +static int do_server_op(struct st_query *q, const char *op) { char *p= q->first_argument; char com_buf[256], *com_p; @@ -950,6 +1004,17 @@ int do_server_op(struct st_query *q, const char *op) q->last_argument= p; return 0; } + +int do_server_start(struct st_query *q) +{ + return do_server_op(q, "start"); +} + +int do_server_stop(struct st_query *q) +{ + return do_server_op(q, "stop"); +} + #endif @@ -987,6 +1052,35 @@ int do_source(struct st_query *query) return open_file(name); } +#ifdef __WIN__ +/* Variables used for temuprary sh files used for emulating Unix on Windows */ +char tmp_sh_name[64], tmp_sh_cmd[70]; + +static void init_tmp_sh_file() +{ + /* Format a name for the tmp sh file that is unique for this process */ + my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); + /* Format the command to execute in order to run the script */ + my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); +} + +static void free_tmp_sh_file() +{ + my_delete(tmp_sh_name, MYF(0)); +} +#endif + +FILE* my_popen(DYNAMIC_STRING* ds_cmd, const char* mode) +{ +#ifdef __WIN__ + /* Dump the command into a sh script file and execute with popen */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return popen(tmp_sh_cmd, mode); +#else + return popen(ds_cmd->str, mode); +#endif +} + /* Execute given command. @@ -1003,17 +1097,21 @@ int do_source(struct st_query *query) expected error array, previously set with the --error command. It can thus be used to execute a command that shall fail. + NOTE + Although mysqltest is executed from cygwin shell, the command will be + executed in "cmd.exe". Thus commands like "rm" etc can NOT be used, use + system for those commands. */ static void do_exec(struct st_query *query) { int error; - DYNAMIC_STRING *ds= NULL; - DYNAMIC_STRING ds_tmp; char buf[1024]; FILE *res_file; char *cmd= query->first_argument; + DYNAMIC_STRING ds_cmd; DBUG_ENTER("do_exec"); + DBUG_PRINT("enter", ("cmd: '%s'", cmd)); while (*cmd && my_isspace(charset_info, *cmd)) cmd++; @@ -1021,31 +1119,28 @@ static void do_exec(struct st_query *query) die("Missing argument in exec"); query->last_argument= query->end; - DBUG_PRINT("info", ("Executing '%s'", cmd)); + init_dynamic_string(&ds_cmd, 0, strlen(cmd)+256, 256); + /* Eval the command, thus replacing all environment variables */ + do_eval(&ds_cmd, cmd, TRUE); + cmd= ds_cmd.str; + + DBUG_PRINT("info", ("Executing '%s' as '%s'", + query->first_argument, cmd)); - if (!(res_file= popen(cmd, "r")) && query->abort_on_error) - die("popen(\"%s\", \"r\") failed", cmd); + if (!(res_file= my_popen(&ds_cmd, "r")) && query->abort_on_error) + die("popen(\"%s\", \"r\") failed", query->first_argument); - if (disable_result_log) + while (fgets(buf, sizeof(buf), res_file)) { - while (fgets(buf, sizeof(buf), res_file)) + if (disable_result_log) { buf[strlen(buf)-1]=0; DBUG_PRINT("exec_result",("%s", buf)); } - } - else - { - if (query->record_file[0]) + else { - init_dynamic_string(&ds_tmp, "", 16384, 65536); - ds= &ds_tmp; + replace_dynstr_append(&ds_res, buf); } - else - ds= &ds_res; - - while (fgets(buf, sizeof(buf), res_file)) - replace_dynstr_append_mem(ds, buf, strlen(buf)); } error= pclose(res_file); if (error != 0) @@ -1054,7 +1149,7 @@ static void do_exec(struct st_query *query) my_bool ok= 0; if (query->abort_on_error) - die("command \"%s\" failed", cmd); + die("command \"%s\" failed", query->first_argument); DBUG_PRINT("info", ("error: %d, status: %d", error, status)); @@ -1067,62 +1162,69 @@ static void do_exec(struct st_query *query) { ok= 1; DBUG_PRINT("info", ("command \"%s\" failed with expected error: %d", - cmd, status)); + query->first_argument, status)); } } if (!ok) die("command \"%s\" failed with wrong error: %d", - cmd, status); + query->first_argument, status); } else if (query->expected_errno[0].type == ERR_ERRNO && query->expected_errno[0].code.errnum != 0) { /* Error code we wanted was != 0, i.e. not an expected success */ die("command \"%s\" succeeded - should have failed with errno %d...", - cmd, query->expected_errno[0].code.errnum); + query->first_argument, query->expected_errno[0].code.errnum); } - if (!disable_result_log) - { - if (glob_replace) - free_replace(); - - if (record) - { - if (!query->record_file[0] && !result_file) - die("Missing result file"); - if (!result_file) - str_to_file(query->record_file, ds->str, ds->length); - } - else if (query->record_file[0]) - { - error= check_result(ds, query->record_file, query->require_file); - } - if (ds == &ds_tmp) - dynstr_free(&ds_tmp); - } + free_replace(); + DBUG_VOID_RETURN; } +/* + Set variable from the result of a query + + SYNOPSIS + var_query_set() + var variable to set from query + query start of query string to execute + query_end end of the query string to execute + + + DESCRIPTION + let @<var_name> = `<query>` + + Execute the query and assign the first row of result to var as + a tab separated strings -int var_query_set(VAR* v, const char *p, const char** p_end) + Also assign each column of the result set to + variable "$<var_name>_<column_name>" + Thus the tab separated output can be read from $<var_name> and + and each individual column can be read as $<var_name>_<col_name> + +*/ + +int var_query_set(VAR* var, const char *query, const char** query_end) { - char* end = (char*)((p_end && *p_end) ? *p_end : p + strlen(p)); + char* end = (char*)((query_end && *query_end) ? + *query_end : query + strlen(query)); MYSQL_RES *res; MYSQL_ROW row; MYSQL* mysql = &cur_con->mysql; LINT_INIT(res); - while (end > p && *end != '`') + while (end > query && *end != '`') --end; - if (p == end) + if (query == end) die("Syntax error in query, missing '`'"); - ++p; + ++query; - if (mysql_real_query(mysql, p, (int)(end - p)) || + if (mysql_real_query(mysql, query, (int)(end - query)) || !(res = mysql_store_result(mysql))) { *end = 0; - die("Error running query '%s': %s", p, mysql_error(mysql)); + die("Error running query '%s': %d: %s", query, + mysql_errno(mysql) ,mysql_error(mysql)); } if ((row = mysql_fetch_row(res)) && row[0]) @@ -1135,21 +1237,40 @@ int var_query_set(VAR* v, const char *p, const char** p_end) uint i; ulong *lengths; char *end; + MYSQL_FIELD *fields= mysql_fetch_fields(res); init_dynamic_string(&result, "", 16384, 65536); lengths= mysql_fetch_lengths(res); for (i=0; i < mysql_num_fields(res); i++) { if (row[0]) + { +#ifdef NOT_YET + /* Add to <var_name>_<col_name> */ + uint j; + char var_col_name[MAX_VAR_NAME]; + uint length= snprintf(var_col_name, MAX_VAR_NAME, + "$%s_%s", var->name, fields[i].name); + /* Convert characters not allowed in variable names to '_' */ + for (j= 1; j < length; j++) + { + if (!my_isvar(charset_info,var_col_name[j])) + var_col_name[j]= '_'; + } + var_set(var_col_name, var_col_name + length, + row[i], row[i] + lengths[i]); +#endif + /* Add column to tab separated string */ dynstr_append_mem(&result, row[i], lengths[i]); + } dynstr_append_mem(&result, "\t", 1); } end= result.str + result.length-1; - eval_expr(v, result.str, (const char**) &end); + eval_expr(var, result.str, (const char**) &end); dynstr_free(&result); } else - eval_expr(v, "", 0); + eval_expr(var, "", 0); mysql_free_result(res); return 0; @@ -1229,7 +1350,6 @@ enum enum_operator SYNOPSIS do_modify_var() query called command - name human readable name of operator operator operation to perform on the var DESCRIPTION @@ -1238,17 +1358,18 @@ enum enum_operator */ -int do_modify_var(struct st_query *query, const char *name, +int do_modify_var(struct st_query *query, enum enum_operator operator) { const char *p= query->first_argument; VAR* v; if (!*p) - die("Missing arguments to %s", name); + die("Missing argument to %.*s", query->first_word_len, query->query); if (*p != '$') - die("First argument to %s must be a variable (start with $)", name); + die("The argument to %.*s must be a variable (start with $)", + query->first_word_len, query->query); v= var_get(p, &p, 1, 0); - switch (operator){ + switch (operator) { case DO_DEC: v->int_val--; break; @@ -1256,7 +1377,7 @@ int do_modify_var(struct st_query *query, const char *name, v->int_val++; break; default: - die("Invalid operator to do_operator"); + die("Invalid operator to do_modify_var"); break; } v->int_dirty= 1; @@ -1265,79 +1386,110 @@ int do_modify_var(struct st_query *query, const char *name, } -int do_system(struct st_query *q) +/* + Wrapper for 'system' function + + NOTE + If mysqltest is executed from cygwin shell, the command will be + executed in the "windows command interpreter" cmd.exe and we prepend "sh" + to make it be executed by cygwins "bash". Thus commands like "rm", + "mkdir" as well as shellscripts can executed by "system" in Windows. + +*/ + +int my_system(DYNAMIC_STRING* ds_cmd) { - char *p=q->first_argument; - VAR v; - var_init(&v, 0, 0, 0, 0); - eval_expr(&v, p, 0); /* NULL terminated */ - if (v.str_val_len) - { - char expr_buf[1024]; - if ((uint)v.str_val_len > sizeof(expr_buf) - 1) - v.str_val_len = sizeof(expr_buf) - 1; - memcpy(expr_buf, v.str_val, v.str_val_len); - expr_buf[v.str_val_len] = 0; - DBUG_PRINT("info", ("running system command '%s'", expr_buf)); - if (system(expr_buf)) - { - if (q->abort_on_error) - die("system command '%s' failed", expr_buf); - /* If ! abort_on_error, display message and continue */ - verbose_msg("system command '%s' failed", expr_buf); - } - } - else +#ifdef __WIN__ + /* Dump the command into a sh script file and execute with system */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return system(tmp_sh_cmd); +#else + return system(ds_cmd->str); +#endif +} + + +/* + + SYNOPSIS + do_system + command called command + + DESCRIPTION + system <command> + + Eval the query to expand any $variables in the command. + Execute the command with the "system" command. + +*/ + +void do_system(struct st_query *command) +{ + DYNAMIC_STRING ds_cmd; + DBUG_ENTER("do_system"); + + if (strlen(command->first_argument) == 0) die("Missing arguments to system, nothing to do!"); - var_free(&v); - q->last_argument= q->end; - return 0; + + init_dynamic_string(&ds_cmd, 0, strlen(command->first_argument) + 64, 256); + + /* Eval the system command, thus replacing all environment variables */ + do_eval(&ds_cmd, command->first_argument, TRUE); + + DBUG_PRINT("info", ("running system command '%s' as '%s'", + command->first_argument, ds_cmd.str)); + if (my_system(&ds_cmd)) + { + if (command->abort_on_error) + die("system command '%s' failed", command->first_argument); + + /* If ! abort_on_error, log message and continue */ + dynstr_append(&ds_res, "system command '"); + replace_dynstr_append(&ds_res, command->first_argument); + dynstr_append(&ds_res, "' failed\n"); + } + + command->last_argument= command->end; + DBUG_VOID_RETURN; } /* Print the content between echo and <delimiter> to result file. - If content is a variable, the variable value will be retrieved + Evaluate all variables in the string before printing, allow + for variable names to be escaped using \ SYNOPSIS do_echo() q called command DESCRIPTION - Usage 1: echo text Print the text after echo until end of command to result file - Usage 2: echo $<var_name> Print the content of the variable <var_name> to result file + echo Some text $<var_name> + Print "Some text" plus the content of the variable <var_name> to + result file + + echo Some text \$<var_name> + Print "Some text" plus $<var_name> to result file */ -int do_echo(struct st_query *q) +int do_echo(struct st_query *command) { - char *p= q->first_argument; - DYNAMIC_STRING *ds; - DYNAMIC_STRING ds_tmp; - VAR v; - var_init(&v,0,0,0,0); + DYNAMIC_STRING *ds, ds_echo; - if (q->record_file[0]) - { - init_dynamic_string(&ds_tmp, "", 256, 512); - ds= &ds_tmp; - } - else - ds= &ds_res; + ds= &ds_res; - eval_expr(&v, p, 0); /* NULL terminated */ - if (v.str_val_len) - dynstr_append_mem(ds, v.str_val, v.str_val_len); + init_dynamic_string(&ds_echo, "", 256, 256); + do_eval(&ds_echo, command->first_argument, FALSE); + dynstr_append_mem(ds, ds_echo.str, ds_echo.length); dynstr_append_mem(ds, "\n", 1); - var_free(&v); - if (ds == &ds_tmp) - dynstr_free(&ds_tmp); - q->last_argument= q->end; + dynstr_free(&ds_echo); + command->last_argument= command->end; return 0; } @@ -1365,7 +1517,7 @@ wait_for_position: die("failed in %s: %d: %s", query_buf, mysql_errno(mysql), mysql_error(mysql)); - if (!(last_result= res= mysql_store_result(mysql))) + if (!(res= mysql_store_result(mysql))) die("mysql_store_result() returned NULL for '%s'", query_buf); if (!(row= mysql_fetch_row(res))) die("empty result in %s", query_buf); @@ -1382,7 +1534,6 @@ wait_for_position: goto wait_for_position; } mysql_free_result(res); - last_result=0; if (rpl_parse) mysql_enable_rpl_parse(mysql); @@ -1421,13 +1572,13 @@ int do_save_master_pos() die("failed in show master status: %d: %s", mysql_errno(mysql), mysql_error(mysql)); - if (!(last_result =res = mysql_store_result(mysql))) + if (!(res = mysql_store_result(mysql))) die("mysql_store_result() retuned NULL for '%s'", query); if (!(row = mysql_fetch_row(res))) die("empty result in show master status"); strnmov(master_pos.file, row[0], sizeof(master_pos.file)-1); master_pos.pos = strtoul(row[1], (char**) 0, 10); - mysql_free_result(res); last_result=0; + mysql_free_result(res); if (rpl_parse) mysql_enable_rpl_parse(mysql); @@ -1529,7 +1680,7 @@ int do_disable_rpl_parse(struct st_query *query __attribute__((unused))) do_sleep() q called command real_sleep use the value from opt_sleep as number of seconds to sleep - if real_sleep is false + if real_sleep is false DESCRIPTION sleep <seconds> @@ -1566,10 +1717,12 @@ int do_sleep(struct st_query *query, my_bool real_sleep) query->first_argument); /* Fixed sleep time selected by --sleep option */ - if (opt_sleep && !real_sleep) + if (opt_sleep >= 0 && !real_sleep) sleep_val= opt_sleep; - my_sleep((ulong) (sleep_val * 1000000L)); + DBUG_PRINT("info", ("sleep_val: %f", sleep_val)); + if (sleep_val) + my_sleep((ulong) (sleep_val * 1000000L)); query->last_argument= sleep_end; return 0; } @@ -1611,6 +1764,7 @@ static uint get_errcodes(match_err *to,struct st_query *q) { char *p= q->first_argument; uint count= 0; + DBUG_ENTER("get_errcodes"); if (!*p) @@ -1621,19 +1775,41 @@ static uint get_errcodes(match_err *to,struct st_query *q) if (*p == 'S') { /* SQLSTATE string */ - int i; - p++; - for (i = 0; my_isalnum(charset_info, *p) && i < SQLSTATE_LENGTH; p++, i++) - to[count].code.sqlstate[i]= *p; - to[count].code.sqlstate[i]= '\0'; + char *end= ++p + SQLSTATE_LENGTH; + char *to_ptr= to[count].code.sqlstate; + + for (; my_isalnum(charset_info, *p) && p != end; p++) + *to_ptr++= *p; + *to_ptr= 0; + to[count].type= ERR_SQLSTATE; } + else if (*p == 'E') + { + /* SQL error as string */ + st_error *e= global_error; + char *start= p++; + + for (; *p == '_' || my_isalnum(charset_info, *p); p++) + ; + for (; e->name; e++) + { + if (!strncmp(start, e->name, (int) (p - start))) + { + to[count].code.errnum= (uint) e->code; + to[count].type= ERR_ERRNO; + break; + } + } + if (!e->name) + die("Unknown SQL error '%s'", start); + } else { long val; - p=str2int(p,10,(long) INT_MIN, (long) INT_MAX, &val); - if (p == NULL) - die("Invalid argument in %s", q->query); + + if (!(p= str2int(p,10,(long) INT_MIN, (long) INT_MAX, &val))) + die("Invalid argument in %s", q->query); to[count].code.errnum= (uint) val; to[count].type= ERR_ERRNO; } @@ -1766,8 +1942,7 @@ static void get_replace(struct st_query *q) if (!(glob_replace=init_replace((char**) from_array.typelib.type_names, (char**) to_array.typelib.type_names, (uint) from_array.typelib.count, - word_end_chars)) || - initialize_replace_buffer()) + word_end_chars))) die("Can't initialize replace from '%s'", q->query); free_pointer_array(&from_array); free_pointer_array(&to_array); @@ -1783,28 +1958,32 @@ void free_replace() { my_free((char*) glob_replace,MYF(0)); glob_replace=0; - free_replace_buffer(); } DBUG_VOID_RETURN; } - -int select_connection_name(const char *name) +struct connection * find_connection_by_name(const char *name) { struct connection *con; - DBUG_ENTER("select_connection2"); - DBUG_PRINT("enter",("name: '%s'", name)); - for (con= cons; con < next_con; con++) { if (!strcmp(con->name, name)) { - cur_con= con; - DBUG_RETURN(0); + return con; } } - die("connection '%s' not found in connection pool", name); - DBUG_RETURN(1); /* Never reached */ + return 0; /* Connection not found */ +} + + +int select_connection_name(const char *name) +{ + DBUG_ENTER("select_connection2"); + DBUG_PRINT("enter",("name: '%s'", name)); + + if (!(cur_con= find_connection_by_name(name))) + die("connection '%s' not found in connection pool", name); + DBUG_RETURN(0); } @@ -1834,7 +2013,7 @@ int close_connection(struct st_query *q) DBUG_PRINT("enter",("name: '%s'",p)); if (!*p) - die("Missing connection name in connect"); + die("Missing connection name in disconnect"); name= p; while (*p && !my_isspace(charset_info,*p)) p++; @@ -1857,6 +2036,17 @@ int close_connection(struct st_query *q) } #endif mysql_close(&con->mysql); + if (con->util_mysql) + mysql_close(con->util_mysql); + con->util_mysql= 0; + my_free(con->name, MYF(0)); + /* + When the connection is closed set name to "closed_connection" + to make it possible to reuse the connection name. + The connection slot will not be reused + */ + if (!(con->name = my_strdup("closed_connection", MYF(MY_WME)))) + die("Out of memory"); DBUG_RETURN(0); } } @@ -1870,20 +2060,35 @@ int close_connection(struct st_query *q) future to handle quotes. For now we assume that anything that is not a comma, a space or ) belongs to the argument. space is a chopper, comma or ) are delimiters/terminators + + SYNOPSIS + safe_get_param + str - string to get param from + arg - pointer to string where result will be stored + msg - Message to display if param is not found + if msg is 0 this param is not required and param may be empty + + RETURNS + pointer to str after param + */ char* safe_get_param(char *str, char** arg, const char *msg) { DBUG_ENTER("safe_get_param"); + if(!*str) + { + if (msg) + die(msg); + *arg= str; + DBUG_RETURN(str); + } while (*str && my_isspace(charset_info,*str)) str++; *arg= str; - for (; *str && *str != ',' && *str != ')' ; str++) - { - if (my_isspace(charset_info,*str)) - *str= 0; - } - if (!*str) + while (*str && *str != ',' && *str != ')') + str++; + if (msg && !*arg) die(msg); *str++= 0; @@ -1903,91 +2108,287 @@ void init_manager() } #endif -int safe_connect(MYSQL* con, const char *host, const char *user, - const char *pass, - const char *db, int port, const char *sock) + +/* + Connect to a server doing several retries if needed. + + SYNOPSIS + safe_connect() + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + + NOTE + + Sometimes in a test the client starts before + the server - to solve the problem, we try again + after some sleep if connection fails the first + time + + This function will try to connect to the given server + "opt_max_connect_retries" times and sleep "connection_retry_sleep" + seconds between attempts before finally giving up. + This helps in situation when the client starts + before the server (which happens sometimes). + It will ignore any errors during these retries. One should use + connect_n_handle_errors() if he expects a connection error and wants + handle as if it was an error from a usual statement. + + RETURN VALUE + 0 - success, non-0 - failure +*/ + +int safe_connect(MYSQL* mysql, const char *host, const char *user, + const char *pass, const char *db, int port, const char *sock) { - int con_error = 1; + int con_error= 1; + my_bool reconnect= 1; + static int connection_retry_sleep= 2; /* Seconds */ int i; - for (i = 0; i < MAX_CON_TRIES; ++i) + for (i= 0; i < opt_max_connect_retries; i++) { - if (mysql_real_connect(con, host,user, pass, db, port, sock, - CLIENT_MULTI_STATEMENTS)) + if (mysql_real_connect(mysql, host,user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { - con_error = 0; + con_error= 0; break; } - sleep(CON_RETRY_SLEEP); + sleep(connection_retry_sleep); } + /* + TODO: change this to 0 in future versions, but the 'kill' test relies on + existing behavior + */ + mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); return con_error; } +/* + Connect to a server and handle connection errors in case they occur. + + SYNOPSIS + connect_n_handle_errors() + q - context of connect "query" (command) + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + create_conn - out parameter, set to zero if connection was + not established and is not touched otherwise + + DESCRIPTION + This function will try to establish a connection to server and handle + possible errors in the same manner as if "connect" was usual SQL-statement + (If error is expected it will ignore it once it occurs and log the + "statement" to the query log). + Unlike safe_connect() it won't do several attempts. + + RETURN VALUE + 0 - success, non-0 - failure +*/ + +int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host, + const char* user, const char* pass, + const char* db, int port, const char* sock, + int* create_conn) +{ + DYNAMIC_STRING *ds; + my_bool reconnect= 1; + int error= 0; + + ds= &ds_res; + + if (!disable_query_log) + { + /* + It is nice to have connect() statement logged in result file + in this case. + QQ: Should we do this only if we are expecting an error ? + */ + char port_buff[22]; /* This should be enough for any int */ + char *port_end; + dynstr_append_mem(ds, "connect(", 8); + replace_dynstr_append(ds, host); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, user); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, pass); + dynstr_append_mem(ds, ",", 1); + if (db) + replace_dynstr_append(ds, db); + dynstr_append_mem(ds, ",", 1); + port_end= int10_to_str(port, port_buff, 10); + replace_dynstr_append_mem(ds, port_buff, port_end - port_buff); + dynstr_append_mem(ds, ",", 1); + if (sock) + replace_dynstr_append(ds, sock); + dynstr_append_mem(ds, ")", 1); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + if (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock: 0, + CLIENT_MULTI_STATEMENTS)) + { + handle_error("connect", q, mysql_errno(con), mysql_error(con), + mysql_sqlstate(con), ds); + *create_conn= 0; + goto err; + } + + handle_no_error(q); + + /* + TODO: change this to 0 in future versions, but the 'kill' test relies on + existing behavior + */ + mysql_options(con, MYSQL_OPT_RECONNECT, (char *)&reconnect); + +err: + free_replace(); + return error; +} + + +/* + Open a new connection to MySQL Server with the parameters + specified + + SYNOPSIS + do_connect() + q called command + + DESCRIPTION + connect(<name>,<host>,<user>,<pass>,<db>,[<port>,<sock>[<opts>]]); + + <name> - name of the new connection + <host> - hostname of server + <user> - user to connect as + <pass> - password used when connecting + <db> - initial db when connected + <port> - server port + <sock> - server socket + <opts> - options to use for the connection + SSL - use SSL if available + COMPRESS - use compression if available + + */ + int do_connect(struct st_query *q) { char *con_name, *con_user,*con_pass, *con_host, *con_port_str, - *con_db, *con_sock; - char *p= q->first_argument; + *con_db, *con_sock, *con_options; + char *con_buf, *p; char buff[FN_REFLEN]; int con_port; + bool con_ssl= 0; + bool con_compress= 0; int free_con_sock= 0; + int error= 0; + int create_conn= 1; + VAR *var_port, *var_sock; DBUG_ENTER("do_connect"); - DBUG_PRINT("enter",("connect: %s",p)); + DBUG_PRINT("enter",("connect: %s", q->first_argument)); + + /* Make a copy of query before parsing, safe_get_param will modify */ + if (!(con_buf= my_strdup(q->first_argument, MYF(MY_WME)))) + die("Could not allocate con_buf"); + p= con_buf; if (*p != '(') die("Syntax error in connect - expected '(' found '%c'", *p); p++; - p= safe_get_param(p, &con_name, "missing connection name"); - p= safe_get_param(p, &con_host, "missing connection host"); - p= safe_get_param(p, &con_user, "missing connection user"); - p= safe_get_param(p, &con_pass, "missing connection password"); - p= safe_get_param(p, &con_db, "missing connection db"); - if (!*p || *p == ';') /* Default port and sock */ + p= safe_get_param(p, &con_name, "Missing connection name"); + p= safe_get_param(p, &con_host, "Missing connection host"); + p= safe_get_param(p, &con_user, "Missing connection user"); + p= safe_get_param(p, &con_pass, "Missing connection password"); + p= safe_get_param(p, &con_db, "Missing connection db"); + + /* Port */ + p= safe_get_param(p, &con_port_str, 0); + if (*con_port_str) { - con_port= port; - con_sock= (char*) unix_sock; - } - else - { - VAR* var_port, *var_sock; - p= safe_get_param(p, &con_port_str, "missing connection port"); if (*con_port_str == '$') { if (!(var_port= var_get(con_port_str, 0, 0, 0))) - die("Unknown variable '%s'", con_port_str+1); + die("Unknown variable '%s'", con_port_str+1); con_port= var_port->int_val; } else + { con_port= atoi(con_port_str); - p= safe_get_param(p, &con_sock, "missing connection socket"); + if (con_port == 0) + die("Illegal argument for port: '%s'", con_port_str); + } + } + else + { + con_port= port; + } + + /* Sock */ + p= safe_get_param(p, &con_sock, 0); + if (*con_sock) + { if (*con_sock == '$') { if (!(var_sock= var_get(con_sock, 0, 0, 0))) - die("Unknown variable '%s'", con_sock+1); + die("Unknown variable '%s'", con_sock+1); if (!(con_sock= (char*)my_malloc(var_sock->str_val_len+1, MYF(0)))) - die("Out of memory"); + die("Out of memory"); free_con_sock= 1; memcpy(con_sock, var_sock->str_val, var_sock->str_val_len); con_sock[var_sock->str_val_len]= 0; } } - q->last_argument= p; + else + { + con_sock= (char*) unix_sock; + } + + /* Options */ + p= safe_get_param(p, &con_options, 0); + while (*con_options) + { + char* str= con_options; + while (*str && !my_isspace(charset_info, *str)) + str++; + *str++= 0; + if (!strcmp(con_options, "SSL")) + con_ssl= 1; + else if (!strcmp(con_options, "COMPRESS")) + con_compress= 1; + else + die("Illegal option to connect: %s", con_options); + con_options= str; + } + /* Note: 'p' is pointing into the copy 'con_buf' */ + q->last_argument= q->first_argument + (p - con_buf); if (next_con == cons_end) die("Connection limit exhausted - increase MAX_CONS in mysqltest.c"); + if (find_connection_by_name(con_name)) + die("Connection %s already exists", con_name); + if (!mysql_init(&next_con->mysql)) die("Failed on mysql_init()"); - if (opt_compress) - mysql_options(&next_con->mysql,MYSQL_OPT_COMPRESS,NullS); + if (opt_compress || con_compress) + mysql_options(&next_con->mysql, MYSQL_OPT_COMPRESS, NullS); mysql_options(&next_con->mysql, MYSQL_OPT_LOCAL_INFILE, 0); mysql_options(&next_con->mysql, MYSQL_SET_CHARSET_NAME, charset_name); #ifdef HAVE_OPENSSL - if (opt_use_ssl) + if (opt_use_ssl || con_ssl) + { + /* Turn on ssl_verify_server_cert only if host is "localhost" */ + opt_ssl_verify_server_cert= !strcmp(con_host, "localhost"); + mysql_ssl_set(&next_con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, opt_ssl_cipher); + mysql_options(&next_con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); + } #endif if (con_sock && !free_con_sock && *con_sock && *con_sock != FN_LIBCHAR) con_sock=fn_format(buff, con_sock, TMPDIR, "",0); @@ -1996,18 +2397,28 @@ int do_connect(struct st_query *q) /* Special database to allow one to connect without a database name */ if (con_db && !strcmp(con_db,"*NO-ONE*")) con_db= 0; - if ((safe_connect(&next_con->mysql, con_host, - con_user, con_pass, - con_db, con_port, con_sock ? con_sock: 0))) - die("Could not open connection '%s': %s", con_name, - mysql_error(&next_con->mysql)); + if (q->abort_on_error) + { + if (safe_connect(&next_con->mysql, con_host, con_user, con_pass, + con_db, con_port, con_sock ? con_sock: 0)) + die("Could not open connection '%s': %d %s", con_name, + mysql_errno(&next_con->mysql), mysql_error(&next_con->mysql)); + } + else + error= connect_n_handle_errors(q, &next_con->mysql, con_host, con_user, + con_pass, con_db, con_port, con_sock, + &create_conn); - if (!(next_con->name= my_strdup(con_name, MYF(MY_WME)))) - die(NullS); - cur_con= next_con++; + if (create_conn) + { + if (!(next_con->name= my_strdup(con_name, MYF(MY_WME)))) + die(NullS); + cur_con= next_con++; + } if (free_con_sock) my_free(con_sock, MYF(MY_WME)); - DBUG_RETURN(0); + my_free(con_buf, MYF(MY_WME)); + DBUG_RETURN(error); } @@ -2022,7 +2433,7 @@ int do_done(struct st_query *q) } /* Test if inner block has been executed */ - if (cur_block->ok && cur_block->cmd == Q_WHILE) + if (cur_block->ok && cur_block->cmd == cmd_while) { /* Pop block from stack, re-execute outer block */ cur_block--; @@ -2038,12 +2449,41 @@ int do_done(struct st_query *q) } -int do_block(enum enum_commands cmd, struct st_query *q) +/* + Process start of a "if" or "while" statement + + SYNOPSIS + do_block() + cmd Type of block + q called command + + DESCRIPTION + if ([!]<expr>) + { + <block statements> + } + + while ([!]<expr>) + { + <block statements> + } + + Evaluates the <expr> and if it evaluates to + greater than zero executes the following code block. + A '!' can be used before the <expr> to indicate it should + be executed if it evaluates to zero. + + */ + +void do_block(enum block_cmd cmd, struct st_query* q) { char *p= q->first_argument; const char *expr_start, *expr_end; VAR v; - const char *cmd_name= (cmd == Q_WHILE ? "while" : "if"); + const char *cmd_name= (cmd == cmd_while ? "while" : "if"); + my_bool not_expr= FALSE; + DBUG_ENTER("do_block"); + DBUG_PRINT("enter", ("%s", cmd_name)); /* Check stack overflow */ if (cur_block == block_stack_end) @@ -2059,13 +2499,21 @@ int do_block(enum enum_commands cmd, struct st_query *q) cur_block++; cur_block->cmd= cmd; cur_block->ok= FALSE; - return 0; + DBUG_VOID_RETURN; } /* Parse and evaluate test expression */ expr_start= strchr(p, '('); - if (!expr_start) + if (!expr_start++) die("missing '(' in %s", cmd_name); + + /* Check for !<expr> */ + if (*expr_start == '!') + { + not_expr= TRUE; + expr_start++; /* Step past the '!' */ + } + /* Find ending ')' */ expr_end= strrchr(expr_start, ')'); if (!expr_end) die("missing ')' in %s", cmd_name); @@ -2079,15 +2527,20 @@ int do_block(enum enum_commands cmd, struct st_query *q) die("Missing '{' after %s. Found \"%s\"", cmd_name, p); var_init(&v,0,0,0,0); - eval_expr(&v, ++expr_start, &expr_end); + eval_expr(&v, expr_start, &expr_end); /* Define inner block */ cur_block++; cur_block->cmd= cmd; cur_block->ok= (v.int_val ? TRUE : FALSE); + if (not_expr) + cur_block->ok = !cur_block->ok; + + DBUG_PRINT("info", ("OK: %d", cur_block->ok)); + var_free(&v); - return 0; + DBUG_VOID_RETURN; } @@ -2147,8 +2600,8 @@ my_bool end_of_query(int c) size size of the buffer i.e max size to read DESCRIPTION - This function actually reads several lines an adds them to the - buffer buf. It will continue to read until it finds what it believes + This function actually reads several lines and adds them to the + buffer buf. It continues to read until it finds what it believes is a complete query. Normally that means it will read lines until it reaches the @@ -2165,14 +2618,15 @@ my_bool end_of_query(int c) int read_line(char *buf, int size) { int c; + char quote; char *p= buf, *buf_end= buf + size - 1; int no_save= 0; - enum {R_NORMAL, R_Q1, R_ESC_Q_Q1, R_ESC_Q_Q2, - R_ESC_SLASH_Q1, R_ESC_SLASH_Q2, - R_Q2, R_COMMENT, R_LINE_START} state= R_LINE_START; + enum {R_NORMAL, R_Q, R_Q_IN_Q, R_SLASH_IN_Q, + R_COMMENT, R_LINE_START} state= R_LINE_START; DBUG_ENTER("read_line"); + LINT_INIT(quote); - start_lineno= *lineno; + start_lineno= cur_file->lineno; for (; p < buf_end ;) { no_save= 0; @@ -2187,27 +2641,31 @@ int read_line(char *buf, int size) } my_free((gptr)cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); cur_file->file_name= 0; - lineno--; - start_lineno= *lineno; if (cur_file == file_stack) { /* We're back at the first file, check if all { have matching } */ if (cur_block != block_stack) - { - start_lineno= *(lineno+1); die("Missing end of block"); - } + + DBUG_PRINT("info", ("end of file")); DBUG_RETURN(1); } cur_file--; + start_lineno= cur_file->lineno; continue; } - /* Line counting is independent of state */ if (c == '\n') - (*lineno)++; + { + /* Line counting is independent of state */ + cur_file->lineno++; + + /* Convert cr/lf to lf */ + if (p != buf && *(p-1) == '\r') + *(p-1)= 0; + } switch(state) { case R_NORMAL: @@ -2217,10 +2675,11 @@ int read_line(char *buf, int size) *p= 0; DBUG_RETURN(0); } - else if (c == '\'') - state = R_Q1; - else if (c == '"') - state = R_Q2; + else if (c == '\'' || c == '"' || c == '`') + { + quote= c; + state= R_Q; + } else if (c == '\n') { state = R_LINE_START; @@ -2235,14 +2694,15 @@ int read_line(char *buf, int size) break; case R_LINE_START: /* Only accept start of comment if this is the first line in query */ - if ((*lineno == start_lineno) && (c == '#' || c == '-')) + if ((cur_file->lineno == start_lineno) && + (c == '#' || c == '-' || parsing_disabled)) { state = R_COMMENT; } else if (my_isspace(charset_info, c)) { if (c == '\n') - start_lineno= *lineno; /* Query hasn't started yet */ + start_lineno= cur_file->lineno; /* Query hasn't started yet */ no_save= 1; } else if (c == '}') @@ -2256,55 +2716,36 @@ int read_line(char *buf, int size) *p= 0; DBUG_RETURN(0); } - else if (c == '\'') - state= R_Q1; - else if (c == '"') - state= R_Q2; - else - state= R_NORMAL; - break; - - case R_Q1: - if (c == '\'') - state= R_ESC_Q_Q1; - else if (c == '\\') - state= R_ESC_SLASH_Q1; - break; - case R_ESC_Q_Q1: - if (end_of_query(c)) + else if (c == '\'' || c == '"' || c == '`') { - *p= 0; - DBUG_RETURN(0); + quote= c; + state= R_Q; } - if (c != '\'') - state= R_NORMAL; else - state= R_Q1; - break; - case R_ESC_SLASH_Q1: - state= R_Q1; + state= R_NORMAL; break; - case R_Q2: - if (c == '"') - state= R_ESC_Q_Q2; + case R_Q: + if (c == quote) + state= R_Q_IN_Q; else if (c == '\\') - state= R_ESC_SLASH_Q2; + state= R_SLASH_IN_Q; break; - case R_ESC_Q_Q2: + case R_Q_IN_Q: if (end_of_query(c)) { *p= 0; DBUG_RETURN(0); } - if (c != '"') + if (c != quote) state= R_NORMAL; else - state= R_Q2; + state= R_Q; break; - case R_ESC_SLASH_Q2: - state= R_Q2; + case R_SLASH_IN_Q: + state= R_Q; break; + } if (!no_save) @@ -2363,14 +2804,13 @@ int read_line(char *buf, int size) The advantage with this approach is to be able to execute commands terminated by new line '\n' regardless how many "delimiter" it contain. - If query starts with @<file_name> this will specify a file to .... */ static char read_query_buf[MAX_QUERY]; int read_query(struct st_query** q_ptr) { - char *p= read_query_buf, *p1; + char *p= read_query_buf; struct st_query* q; DBUG_ENTER("read_query"); @@ -2389,11 +2829,13 @@ int read_query(struct st_query** q_ptr) q->type= Q_UNKNOWN; q->query_buf= q->query= 0; + read_query_buf[0]= 0; if (read_line(read_query_buf, sizeof(read_query_buf))) { - DBUG_PRINT("warning",("too long query")); + check_eol_junk(read_query_buf); DBUG_RETURN(1); } + DBUG_PRINT("info", ("query: %s", read_query_buf)); if (*p == '#') { @@ -2401,33 +2843,29 @@ int read_query(struct st_query** q_ptr) /* This goto is to avoid losing the "expected error" info. */ goto end; } - memcpy((gptr) q->expected_errno, (gptr) global_expected_errno, - sizeof(global_expected_errno)); - q->expected_errors= global_expected_errors; - q->abort_on_error= (global_expected_errors == 0 && abort_on_error); + if (!parsing_disabled) + { + memcpy((gptr) q->expected_errno, (gptr) global_expected_errno, + sizeof(global_expected_errno)); + q->expected_errors= global_expected_errors; + q->abort_on_error= (global_expected_errors == 0 && abort_on_error); + } + if (p[0] == '-' && p[1] == '-') { q->type= Q_COMMENT_WITH_COMMAND; p+= 2; /* To calculate first word */ } - else + else if (!parsing_disabled) { while (*p && my_isspace(charset_info, *p)) p++ ; - if (*p == '@') - { - p++; - p1 = q->record_file; - while (!my_isspace(charset_info, *p) && - p1 < q->record_file + sizeof(q->record_file) - 1) - *p1++ = *p++; - *p1 = 0; - } } end: while (*p && my_isspace(charset_info, *p)) p++; + if (!(q->query_buf= q->query= my_strdup(p, MYF(MY_WME)))) die(NullS); @@ -2445,10 +2883,8 @@ end: static struct my_option my_long_options[] = { - {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", - 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, - {"database", 'D', "Database to use.", (gptr*) &db, (gptr*) &db, 0, - GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, {"basedir", 'b', "Basedir for tests.", (gptr*) &opt_basedir, (gptr*) &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"big-test", 'B', "Define BIG_TEST to 1.", (gptr*) &opt_big_test, @@ -2456,13 +2892,20 @@ static struct my_option my_long_options[] = {"compress", 'C', "Use the compressed server/client protocol.", (gptr*) &opt_compress, (gptr*) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, - 0, 0, 0, 0, 0, 0}, + {"cursor-protocol", OPT_CURSOR_PROTOCOL, "Use cursors for prepared statements.", + (gptr*) &cursor_protocol, (gptr*) &cursor_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'D', "Database to use.", (gptr*) &db, (gptr*) &db, 0, + GET_STR, REQUIRED_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. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif {"host", 'h', "Connect to host.", (gptr*) &host, (gptr*) &host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"manager-user", OPT_MANAGER_USER, "Undocumented: Used for debugging.", - (gptr*) &manager_user, (gptr*) &manager_user, 0, GET_STR, REQUIRED_ARG, 0, - 0, 0, 0, 0, 0}, {"manager-host", OPT_MANAGER_HOST, "Undocumented: Used for debugging.", (gptr*) &manager_host, (gptr*) &manager_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -2471,13 +2914,20 @@ static struct my_option my_long_options[] = {"manager-port", OPT_MANAGER_PORT, "Undocumented: Used for debugging.", (gptr*) &manager_port, (gptr*) &manager_port, 0, GET_INT, REQUIRED_ARG, MYSQL_MANAGER_PORT, 0, 0, 0, 0, 0}, + {"manager-user", OPT_MANAGER_USER, "Undocumented: Used for debugging.", + (gptr*) &manager_user, (gptr*) &manager_user, 0, GET_STR, REQUIRED_ARG, 0, + 0, 0, 0, 0, 0}, {"manager-wait-timeout", OPT_MANAGER_WAIT_TIMEOUT, "Undocumented: Used for debugging.", (gptr*) &manager_wait_timeout, (gptr*) &manager_wait_timeout, 0, GET_INT, REQUIRED_ARG, 3, 0, 0, 0, 0, 0}, + {"max-connect-retries", OPT_MAX_CONNECT_RETRIES, + "Max number of connection attempts when connecting to server", + (gptr*) &opt_max_connect_retries, (gptr*) &opt_max_connect_retries, 0, + GET_INT, REQUIRED_ARG, 5, 1, 10, 0, 0, 0}, {"password", 'p', "Password to use when connecting to server.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Port number to use for connection.", (gptr*) &port, - (gptr*) &port, 0, GET_INT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, 0}, + (gptr*) &port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"ps-protocol", OPT_PS_PROTOCOL, "Use prepared statements protocol for communication", (gptr*) &ps_protocol, (gptr*) &ps_protocol, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -2498,11 +2948,14 @@ static struct my_option my_long_options[] = "Don't use the memory allocation checking.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"sleep", 'T', "Sleep always this many seconds on sleep commands.", - (gptr*) &opt_sleep, (gptr*) &opt_sleep, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, + (gptr*) &opt_sleep, (gptr*) &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, 0, 0, 0, 0, 0}, {"socket", 'S', "Socket file to use for connection.", (gptr*) &unix_sock, (gptr*) &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"sp-protocol", OPT_SP_PROTOCOL, "Use stored procedures for select", + (gptr*) &sp_protocol, (gptr*) &sp_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, #include "sslopt-longopts.h" {"test-file", 'x', "Read test from/in this file (default stdin).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -2516,6 +2969,9 @@ static struct my_option my_long_options[] = GET_BOOL, 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}, + {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select", + (gptr*) &view_protocol, (gptr*) &view_protocol, 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} }; @@ -2575,6 +3031,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) die("Could not open %s: errno = %d", buff, errno); cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); + cur_file->lineno= 1; break; } case 'm': @@ -2659,27 +3116,29 @@ int parse_args(int argc, char **argv) return 0; } -char* safe_str_append(char *buf, const char *str, int size) -{ - int i,c ; - for (i = 0; (c = *str++) && i < size - 1; i++) - *buf++ = c; - *buf = 0; - return buf; -} -void str_to_file(const char *fname, char *str, int size) +/* + Write the content of str into file + + SYNOPSIS + str_to_file + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file +*/ + +static void str_to_file(const char *fname, char *str, int size) { int fd; char buff[FN_REFLEN]; if (!test_if_hard_path(fname)) { strxmov(buff, opt_basedir, fname, NullS); - fname=buff; + fname= buff; } fn_format(buff,fname,"","",4); - - if ((fd = my_open(buff, O_WRONLY | O_CREAT | O_TRUNC, + + if ((fd= my_open(buff, O_WRONLY | O_CREAT | O_TRUNC, MYF(MY_WME | MY_FFNF))) < 0) die("Could not open %s: errno = %d", buff, errno); if (my_write(fd, (byte*)str, size, MYF(MY_WME|MY_FNABP))) @@ -2687,26 +3146,197 @@ void str_to_file(const char *fname, char *str, int size) my_close(fd, MYF(0)); } -void reject_dump(const char *record_file, char *buf, int size) + +void dump_result_to_reject_file(const char *record_file, char *buf, int size) { char reject_file[FN_REFLEN]; str_to_file(fn_format(reject_file, record_file,"",".reject",2), buf, size); } +void dump_result_to_log_file(const char *record_file, char *buf, int size) +{ + char log_file[FN_REFLEN]; + str_to_file(fn_format(log_file, record_file,"",".log",2), buf, size); +} + -/* Append the string to ds, with optional replace */ +#ifdef __WIN__ -static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, - int len) +DYNAMIC_ARRAY patterns; + +/* + init_win_path_patterns + + DESCRIPTION + Setup string patterns that will be used to detect filenames that + needs to be converted from Win to Unix format + +*/ + +static void init_win_path_patterns() { + /* List of string patterns to match in order to find paths */ + const char* paths[] = { "$MYSQL_TEST_DIR", + "$MYSQL_TMP_DIR", + "./test/", 0 }; + int num_paths= 3; + int i; + char* p; + + DBUG_ENTER("init_win_path_patterns"); + + my_init_dynamic_array(&patterns, sizeof(const char*), 16, 16); + + /* Loop through all paths in the array */ + for (i= 0; i < num_paths; i++) + { + VAR* v; + if (*(paths[i]) == '$') + { + v= var_get(paths[i], 0, 0, 0); + p= my_strdup(v->str_val, MYF(MY_FAE)); + } + else + p= my_strdup(paths[i], MYF(MY_FAE)); + + if (insert_dynamic(&patterns, (gptr) &p)) + die(NullS); + + DBUG_PRINT("info", ("p: %s", p)); + while (*p) + { + if (*p == '/') + *p='\\'; + p++; + } + } + DBUG_VOID_RETURN; +} + +static void free_win_path_patterns() +{ + uint i= 0; + for (i=0 ; i < patterns.elements ; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + my_free((gptr) *pattern, MYF(0)); + } + delete_dynamic(&patterns); +} + +/* + fix_win_paths + + DESCRIPTION + Search the string 'val' for the patterns that are known to be + strings that contain filenames. Convert all \ to / in the + filenames that are found. + + Ex: + val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' + => $MYSQL_TEST_DIR is found by strstr + => all \ from c:\mysql\m... until next space is converted into / +*/ + +static void fix_win_paths(const char* val, int len) +{ + uint i; + char *p; + + DBUG_ENTER("fix_win_paths"); + for (i= 0; i < patterns.elements; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + DBUG_PRINT("info", ("pattern: %s", *pattern)); + /* Search for the path in string */ + while ((p= strstr(val, *pattern))) + { + DBUG_PRINT("info", ("Found %s in val p: %s", *pattern, p)); + + while (*p && !my_isspace(charset_info, *p)) + { + if (*p == '\\') + *p= '/'; + p++; + } + DBUG_PRINT("info", ("Converted \\ to /, p: %s", p)); + } + } + DBUG_PRINT("exit", (" val: %s, len: %d", val, len)); + DBUG_VOID_RETURN; +} +#endif + +/* Append the string to ds, with optional replace */ +static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, + const char *val, int len) +{ +#ifdef __WIN__ + fix_win_paths(val, len); +#endif + if (glob_replace) + replace_strings_append(glob_replace, ds, val, len); + else + dynstr_append_mem(ds, val, len); +} + + +/* Append zero-terminated string to ds, with optional replace */ +static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) +{ + replace_dynstr_append_mem(ds, val, strlen(val)); +} + + +/* + Append the result for one field to the dynamic string ds +*/ + +static void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, + const char* val, ulonglong len, bool is_null) +{ + if (col_idx < max_replace_column && replace_column[col_idx]) + { + val= replace_column[col_idx]; + len= strlen(val); + } + else if (is_null) + { + val= "NULL"; + len= 4; + } +#ifdef __WIN__ + else if ((field->type == MYSQL_TYPE_DOUBLE || + field->type == MYSQL_TYPE_FLOAT ) && + field->decimals >= 31) + { + /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */ + char *start= strchr(val, 'e'); + if (start && strlen(start) >= 5 && + (start[1] == '-' || start[1] == '+') && start[2] == '0') + { + start+=2; /* Now points at first '0' */ + /* Move all chars after the first '0' one step left */ + memmove(start, start + 1, strlen(start)); + len--; + } + } +#endif + + if (!display_result_vertically) { - len=(int) replace_strings(glob_replace, &out_buff, &out_length, val); - if (len == -1) - die("Out of memory in replace"); - val=out_buff; + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, (int)len); + } + else + { + dynstr_append(ds, field->name); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, (int)len); + dynstr_append_mem(ds, "\n", 1); } - dynstr_append_mem(ds, val, len); } @@ -2719,41 +3349,16 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) { MYSQL_ROW row; uint num_fields= mysql_num_fields(res); - MYSQL_FIELD *fields= !display_result_vertically ? 0 : mysql_fetch_fields(res); - unsigned long *lengths; + MYSQL_FIELD *fields= mysql_fetch_fields(res); + ulong *lengths; + while ((row = mysql_fetch_row(res))) { uint i; lengths = mysql_fetch_lengths(res); for (i = 0; i < num_fields; i++) - { - const char *val= row[i]; - ulonglong len= lengths[i]; - - if (i < max_replace_column && replace_column[i]) - { - val= replace_column[i]; - len= strlen(val); - } - if (!val) - { - val= "NULL"; - len= 4; - } - if (!display_result_vertically) - { - if (i) - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); - } - else - { - dynstr_append(ds, fields[i].name); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); - dynstr_append_mem(ds, "\n", 1); - } - } + append_field(ds, i, &fields[i], + (const char*)row[i], lengths[i], !row[i]); if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1); } @@ -2762,262 +3367,337 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) /* -* flags control the phased/stages of query execution to be performed -* if QUERY_SEND bit is on, the query will be sent. If QUERY_REAP is on -* the result will be read - for regular query, both bits must be on + Append all results from ps execution to the dynamic string separated + with '\t'. Values may be converted with 'replace_column' */ -static int run_query_normal(MYSQL *mysql, struct st_query *q, int flags); -static int run_query_stmt (MYSQL *mysql, struct st_query *q, int flags); -static void run_query_stmt_handle_warnings(MYSQL *mysql, DYNAMIC_STRING *ds); -static int run_query_stmt_handle_error(char *query, struct st_query *q, - MYSQL_STMT *stmt, DYNAMIC_STRING *ds); -static void run_query_display_metadata(MYSQL_FIELD *field, uint num_fields, - DYNAMIC_STRING *ds); - -static int run_query(MYSQL *mysql, struct st_query *q, int flags) +static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, + MYSQL_FIELD *fields, uint num_fields) { + MYSQL_BIND *bind; + my_bool *is_null; + ulong *length; + uint i; - /* - Try to find out if we can run this statement using the prepared - statement protocol. + /* Allocate array with bind structs, lengths and NULL flags */ + bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), + MYF(MY_WME | MY_FAE | MY_ZEROFILL)); + length= (ulong*) my_malloc(num_fields * sizeof(ulong), + MYF(MY_WME | MY_FAE)); + is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), + MYF(MY_WME | MY_FAE)); - We don't have a mysql_stmt_send_execute() so we only handle - complete SEND+REAP. + /* Allocate data for the result of each field */ + for (i= 0; i < num_fields; i++) + { + uint max_length= fields[i].max_length + 1; + bind[i].buffer_type= MYSQL_TYPE_STRING; + bind[i].buffer= (char *)my_malloc(max_length, MYF(MY_WME | MY_FAE)); + bind[i].buffer_length= max_length; + bind[i].is_null= &is_null[i]; + bind[i].length= &length[i]; + + DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %d", + i, bind[i].buffer_type, bind[i].buffer_length)); + } - If it is a '?' in the query it may be a SQL level prepared - statement already and we can't do it twice - */ + if (mysql_stmt_bind_result(stmt, bind)) + die("mysql_stmt_bind_result failed: %d: %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - if (ps_protocol_enabled && disable_info && - (flags & QUERY_SEND) && (flags & QUERY_REAP) && ps_match_re(q->query)) - return run_query_stmt(mysql, q, flags); - return run_query_normal(mysql, q, flags); + while (mysql_stmt_fetch(stmt) == 0) + { + for (i= 0; i < num_fields; i++) + append_field(ds, i, &fields[i], (const char *) bind[i].buffer, + *bind[i].length, *bind[i].is_null); + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } + + if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + die("fetch didn't end with MYSQL_NO_DATA from statement: %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + + free_replace_column(); + + for (i= 0; i < num_fields; i++) + { + /* Free data for output */ + my_free((gptr)bind[i].buffer, MYF(MY_WME | MY_FAE)); + } + /* Free array with bind structs, lengths and NULL flags */ + my_free((gptr)bind , MYF(MY_WME | MY_FAE)); + my_free((gptr)length , MYF(MY_WME | MY_FAE)); + my_free((gptr)is_null , MYF(MY_WME | MY_FAE)); } -static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) +/* + Append metadata for fields to output +*/ + +static void append_metadata(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) { - MYSQL_RES* res= 0; - uint i; - int error= 0, err= 0, counter= 0; - DYNAMIC_STRING *ds; - DYNAMIC_STRING ds_tmp; - DYNAMIC_STRING eval_query; - char* query; - int query_len, got_error_on_send= 0; - DBUG_ENTER("run_query_normal"); - DBUG_PRINT("enter",("flags: %d", flags)); - - if (q->type != Q_EVAL) + MYSQL_FIELD *field_end; + dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" + "Column_alias\tType\tLength\tMax length\tIs_null\t" + "Flags\tDecimals\tCharsetnr\n"); + + for (field_end= field+num_fields ; + field < field_end ; + field++) { - query = q->query; - query_len = strlen(query); + char buff[22]; + dynstr_append_mem(ds, field->catalog, + field->catalog_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->db, field->db_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_table, + field->org_table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->table, + field->table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_name, + field->org_name_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->name, field->name_length); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->type, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + longlong10_to_str((unsigned int) field->length, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + longlong10_to_str((unsigned int) field->max_length, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, (char*) (IS_NOT_NULL(field->flags) ? + "N" : "Y"), 1); + dynstr_append_mem(ds, "\t", 1); + + int10_to_str((int) field->flags, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->decimals, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->charsetnr, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\n", 1); } - else +} + + +/* + Append affected row count and other info to output +*/ + +static void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, + const char *info) +{ + char buf[40]; + sprintf(buf,"affected rows: %llu\n", affected_rows); + dynstr_append(ds, buf); + if (info) { - init_dynamic_string(&eval_query, "", 16384, 65536); - do_eval(&eval_query, q->query); - query = eval_query.str; - query_len = eval_query.length; + dynstr_append(ds, "info: "); + dynstr_append(ds, info); + dynstr_append_mem(ds, "\n", 1); } - DBUG_PRINT("enter", ("query: '%-.60s'", query)); +} + + +/* + Display the table headings with the names tab separated +*/ - if (q->record_file[0]) +static void append_table_headings(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) +{ + uint col_idx; + for (col_idx= 0; col_idx < num_fields; col_idx++) { - init_dynamic_string(&ds_tmp, "", 16384, 65536); - ds = &ds_tmp; + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append(ds, field[col_idx].name); } - else - ds= &ds_res; + dynstr_append_mem(ds, "\n", 1); +} + +/* + Fetch warnings from server and append to ds + + RETURN VALUE + Number of warnings appended to ds +*/ + +static int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) +{ + uint count; + MYSQL_RES *warn_res; + DBUG_ENTER("append_warnings"); + + if (!(count= mysql_warning_count(mysql))) + DBUG_RETURN(0); + + /* + If one day we will support execution of multi-statements + through PS API we should not issue SHOW WARNINGS until + we have not read all results... + */ + DBUG_ASSERT(!mysql_more_results(mysql)); + + if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) + die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); + + if (!(warn_res= mysql_store_result(mysql))) + die("Warning count is %u but didn't get any warnings", + count); + + append_result(ds, warn_res); + mysql_free_result(warn_res); + + DBUG_PRINT("warnings", ("%s", ds->str)); + + DBUG_RETURN(count); +} + + + +/* + Run query using MySQL C API + + SYNPOSIS + run_query_normal + mysql - mysql handle + command - currrent command pointer + flags -flags indicating wheter to SEND and/or REAP + query - query string to execute + query_len - length query string to execute + ds - output buffer wherte to store result form query + + RETURN VALUE + error - function will not return +*/ + +static void run_query_normal(MYSQL *mysql, struct st_query *command, + int flags, char *query, int query_len, + DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= 0; + int err= 0, counter= 0; + DBUG_ENTER("run_query_normal"); + DBUG_PRINT("enter",("flags: %d", flags)); + DBUG_PRINT("enter", ("query: '%-.60s'", query)); if (flags & QUERY_SEND) { - got_error_on_send= mysql_send_query(mysql, query, query_len); - if (got_error_on_send && q->expected_errno[0].type == ERR_EMPTY) - die("unable to send query '%s' (mysql_errno=%d , errno=%d)", - query, mysql_errno(mysql), errno); + /* + Send the query + */ + if (mysql_send_query(mysql, query, query_len)) + { + handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } } + if (!(flags & QUERY_REAP)) + DBUG_VOID_RETURN; + do { - if ((flags & QUERY_SEND) && !disable_query_log && !counter) - { - replace_dynstr_append_mem(ds,query, query_len); - dynstr_append_mem(ds, delimiter, delimiter_length); - dynstr_append_mem(ds, "\n", 1); - } - if (!(flags & QUERY_REAP)) - DBUG_RETURN(0); - - if (got_error_on_send || - (!counter && (*mysql->methods->read_query_result)(mysql)) || - (!(last_result= res= mysql_store_result(mysql)) && - mysql_field_count(mysql))) + /* + When on first result set, call mysql_read_query_result to retrieve + answer to the query sent earlier + */ + if ((counter==0) && mysql_read_query_result(mysql)) { - if (q->require_file) - { - abort_not_supported_test(); - } - if (q->abort_on_error) - die("query '%s' failed: %d: %s", query, - mysql_errno(mysql), mysql_error(mysql)); - - for (i=0 ; (uint) i < q->expected_errors ; i++) - { - if (((q->expected_errno[i].type == ERR_ERRNO) && - (q->expected_errno[i].code.errnum == mysql_errno(mysql))) || - ((q->expected_errno[i].type == ERR_SQLSTATE) && - (strcmp(q->expected_errno[i].code.sqlstate,mysql_sqlstate(mysql)) == 0))) - { - if (i == 0 && q->expected_errors == 1) - { - /* Only log error if there is one possible error */ - dynstr_append_mem(ds,"ERROR ",6); - replace_dynstr_append_mem(ds, mysql_sqlstate(mysql), - strlen(mysql_sqlstate(mysql))); - dynstr_append_mem(ds, ": ", 2); - replace_dynstr_append_mem(ds,mysql_error(mysql), - strlen(mysql_error(mysql))); - dynstr_append_mem(ds,"\n",1); - } - /* Don't log error if we may not get an error */ - else if (q->expected_errno[0].type == ERR_SQLSTATE || - (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0)) - dynstr_append(ds,"Got one of the listed errors\n"); - goto end; /* Ok */ - } - } - DBUG_PRINT("info",("i: %d expected_errors: %d", i, - q->expected_errors)); - dynstr_append_mem(ds, "ERROR ",6); - replace_dynstr_append_mem(ds, mysql_sqlstate(mysql), - strlen(mysql_sqlstate(mysql))); - dynstr_append_mem(ds,": ",2); - replace_dynstr_append_mem(ds, mysql_error(mysql), - strlen(mysql_error(mysql))); - dynstr_append_mem(ds,"\n",1); - if (i) - { - if (q->expected_errno[0].type == ERR_ERRNO) - verbose_msg("query '%s' failed with wrong errno %d instead of %d...", - q->query, mysql_errno(mysql), q->expected_errno[0].code.errnum); - else - verbose_msg("query '%s' failed with wrong sqlstate %s instead of %s...", - q->query, mysql_sqlstate(mysql), q->expected_errno[0].code.sqlstate); - error= 1; - goto end; - } - verbose_msg("query '%s' failed: %d: %s", q->query, mysql_errno(mysql), - mysql_error(mysql)); - /* - if we do not abort on error, failure to run the query does - not fail the whole test case - */ + handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); goto end; } - if (handle_no_error(q)) + /* + Store the result. If res is NULL, use mysql_field_count to + determine if that was expected + */ + if (!(res= mysql_store_result(mysql)) && mysql_field_count(mysql)) { - error= 1; + handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); goto end; } if (!disable_result_log) { - ulong affected_rows; /* Ok to be undef if 'disable_info' is set */ + ulonglong affected_rows; /* Ok to be undef if 'disable_info' is set */ LINT_INIT(affected_rows); if (res) { - MYSQL_FIELD *field= mysql_fetch_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); uint num_fields= mysql_num_fields(res); if (display_metadata) - run_query_display_metadata(field, num_fields, ds); + append_metadata(ds, fields, num_fields); if (!display_result_vertically) - { - for (i = 0; i < num_fields; i++) - { - if (i) - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[i].name, - strlen(field[i].name)); - } - dynstr_append_mem(ds, "\n", 1); - } + append_table_headings(ds, fields, num_fields); + append_result(ds, res); } /* - Need to call mysql_affected_rows() before the new + Need to call mysql_affected_rows() before the "new" query to find the warnings */ if (!disable_info) - affected_rows= (ulong)mysql_affected_rows(mysql); + affected_rows= mysql_affected_rows(mysql); - /* Add all warnings to the result */ - if (!disable_warnings && mysql_warning_count(mysql)) + /* + Add all warnings to the result. We can't do this if we are in + the middle of processing results from multi-statement, because + this will break protocol. + */ + if (!disable_warnings && !mysql_more_results(mysql)) { - MYSQL_RES *warn_res=0; - uint count= mysql_warning_count(mysql); - if (!mysql_real_query(mysql, "SHOW WARNINGS", 13)) - { - warn_res= mysql_store_result(mysql); - } - if (!warn_res) - die("Warning count is %u but didn't get any warnings\n", - count); - else + if (append_warnings(ds_warnings, mysql) || ds_warnings->length) { dynstr_append_mem(ds, "Warnings:\n", 10); - append_result(ds, warn_res); - mysql_free_result(warn_res); + dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length); } } + if (!disable_info) - { - char buf[40]; - sprintf(buf,"affected rows: %lu\n", affected_rows); - dynstr_append(ds, buf); - if (mysql_info(mysql)) - { - dynstr_append(ds, "info: "); - dynstr_append(ds, mysql_info(mysql)); - dynstr_append_mem(ds, "\n", 1); - } - } + append_info(ds, affected_rows, mysql_info(mysql)); } - if (record) - { - if (!q->record_file[0] && !result_file) - die("Missing result file"); - if (!result_file) - str_to_file(q->record_file, ds->str, ds->length); - } - else if (q->record_file[0]) - { - error = check_result(ds, q->record_file, q->require_file); - } if (res) mysql_free_result(res); - last_result= 0; counter++; } while (!(err= mysql_next_result(mysql))); - if (err >= 1) - mysql_error(mysql); + if (err > 0) + { + /* We got an error from mysql_next_result, maybe expected */ + handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + DBUG_ASSERT(err == -1); /* Successful and there are no more results */ + + /* If we come here the query is both executed and read successfully */ + handle_no_error(command); end: free_replace(); - last_result=0; - if (ds == &ds_tmp) - dynstr_free(&ds_tmp); - if (q->type == Q_EVAL) - dynstr_free(&eval_query); /* We save the return code (mysql_errno(mysql)) from the last call sent @@ -3025,562 +3705,625 @@ end: variable then can be used from the test case itself. */ var_set_errno(mysql_errno(mysql)); - DBUG_RETURN(error); + DBUG_VOID_RETURN; } -/****************************************************************************\ - * If --ps-protocol run ordinary statements using prepared statemnt C API -\****************************************************************************/ +/* + Handle errors which occurred during execution + + SYNOPSIS + handle_error() + query - query string + q - query context + err_errno - error number + err_error - error message + err_sqlstate - sql state + ds - dynamic string which is used for output buffer + + NOTE + If there is an unexpected error this function will abort mysqltest + immediately. + + RETURN VALUE + error - function will not return +*/ + +static void handle_error(const char *query, struct st_query *q, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds) +{ + uint i; + + DBUG_ENTER("handle_error"); + + if (q->require_file) + { + /* + The query after a "--require" failed. This is fine as long the server + returned a valid reponse. Don't allow 2013 or 2006 to trigger an + abort_not_supported_test + */ + if (err_errno == CR_SERVER_LOST || + err_errno == CR_SERVER_GONE_ERROR) + die("require query '%s' failed: %d: %s", query, err_errno, err_error); + abort_not_supported_test(); + } + + if (q->abort_on_error) + die("query '%s' failed: %d: %s", query, err_errno, err_error); + + for (i= 0 ; (uint) i < q->expected_errors ; i++) + { + if (((q->expected_errno[i].type == ERR_ERRNO) && + (q->expected_errno[i].code.errnum == err_errno)) || + ((q->expected_errno[i].type == ERR_SQLSTATE) && + (strcmp(q->expected_errno[i].code.sqlstate, err_sqlstate) == 0))) + { + if (!disable_result_log) + { + if (q->expected_errors == 1) + { + /* Only log error if there is one possible error */ + dynstr_append_mem(ds, "ERROR ", 6); + replace_dynstr_append(ds, err_sqlstate); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, err_error); + dynstr_append_mem(ds,"\n",1); + } + /* Don't log error if we may not get an error */ + else if (q->expected_errno[0].type == ERR_SQLSTATE || + (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0)) + dynstr_append(ds,"Got one of the listed errors\n"); + } + /* OK */ + DBUG_VOID_RETURN; + } + } + + DBUG_PRINT("info",("i: %d expected_errors: %d", i, q->expected_errors)); + + if (!disable_result_log) + { + dynstr_append_mem(ds, "ERROR ",6); + replace_dynstr_append(ds, err_sqlstate); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, err_error); + dynstr_append_mem(ds, "\n", 1); + } + + if (i) + { + if (q->expected_errno[0].type == ERR_ERRNO) + die("query '%s' failed with wrong errno %d: '%s', instead of %d...", + q->query, err_errno, err_error, q->expected_errno[0].code.errnum); + else + die("query '%s' failed with wrong sqlstate %s: '%s', instead of %s...", + q->query, err_sqlstate, err_error, + q->expected_errno[0].code.sqlstate); + } + + DBUG_VOID_RETURN; +} + /* - We don't have a mysql_stmt_send_execute() so we only handle - complete SEND+REAP + Handle absence of errors after execution + + SYNOPSIS + handle_no_error() + q - context of query + + RETURN VALUE + error - function will not return */ -static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) +static void handle_no_error(struct st_query *q) { - int error= 0; /* Function return code if "goto end;" */ - int err; /* Temporary storage of return code from calls */ - int query_len, got_error_on_execute; - uint num_rows; - char *query; - MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ - DYNAMIC_STRING *ds; - DYNAMIC_STRING ds_tmp; - DYNAMIC_STRING eval_query; - MYSQL_STMT *stmt; - DBUG_ENTER("run_query_stmt"); + DBUG_ENTER("handle_no_error"); - /* - We must allocate a new stmt for each query in this program becasue this - may be a new connection. - */ - if (!(stmt= mysql_stmt_init(mysql))) - die("unable init stmt structure"); - - if (q->type != Q_EVAL) + if (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0) { - query= q->query; - query_len= strlen(query); + /* Error code we wanted was != 0, i.e. not an expected success */ + die("query '%s' succeeded - should have failed with errno %d...", + q->query, q->expected_errno[0].code.errnum); } - else + else if (q->expected_errno[0].type == ERR_SQLSTATE && + strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) { - init_dynamic_string(&eval_query, "", 16384, 65536); - do_eval(&eval_query, q->query); - query= eval_query.str; - query_len= eval_query.length; + /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ + die("query '%s' succeeded - should have failed with sqlstate %s...", + q->query, q->expected_errno[0].code.sqlstate); } + + DBUG_VOID_RETURN; +} + + +/* + Run query using prepared statement C API + + SYNPOSIS + run_query_stmt + mysql - mysql handle + command - currrent command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +static void run_query_stmt(MYSQL *mysql, struct st_query *command, + char *query, int query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ + MYSQL_STMT *stmt; + DYNAMIC_STRING ds_prepare_warnings; + DYNAMIC_STRING ds_execute_warnings; + DBUG_ENTER("run_query_stmt"); DBUG_PRINT("query", ("'%-.60s'", query)); - if (q->record_file[0]) + /* + Init a new stmt if it's not already one created for this connection + */ + if(!(stmt= cur_con->stmt)) { - init_dynamic_string(&ds_tmp, "", 16384, 65536); - ds= &ds_tmp; + if (!(stmt= mysql_stmt_init(mysql))) + die("unable to init stmt structure"); + cur_con->stmt= stmt; } - else - ds= &ds_res; - /* Store the query into the output buffer if not disabled */ - if (!disable_query_log) + /* Init dynamic strings for warnings */ + if (!disable_warnings) { - replace_dynstr_append_mem(ds,query, query_len); - dynstr_append_mem(ds, delimiter, delimiter_length); - dynstr_append_mem(ds, "\n", 1); + init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); + init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); } /* - We use the prepared statement interface but there is actually no - '?' in the query. If unpreparable we fall back to use normal - C API. + Prepare the query */ - if ((err= mysql_stmt_prepare(stmt, query, query_len)) == CR_NO_PREPARE_STMT) - return run_query_normal(mysql, q, flags); - - if (err != 0) + if (mysql_stmt_prepare(stmt, query, query_len)) { - if (q->abort_on_error) - { - die("query '%s' failed: %d: %s", query, - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - } - else - { - /* - Preparing is part of normal execution and some errors may be expected - */ - error= run_query_stmt_handle_error(query, q, stmt, ds); - goto end; - } + handle_error(query, command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; } - /* We may have got warnings already, collect them if any */ - /* FIXME we only want this if the statement succeeds I think */ - run_query_stmt_handle_warnings(mysql, ds); + /* + Get the warnings from mysql_stmt_prepare and keep them in a + separate string + */ + if (!disable_warnings) + append_warnings(&ds_prepare_warnings, mysql); /* - No need to call mysql_stmt_bind_param() because we have no + No need to call mysql_stmt_bind_param() because we have no parameter markers. - - To optimize performance we use a global 'stmt' that is initiated - once. A new prepare will implicitely close the old one. When we - terminate we will lose the connection, this also closes the last - prepared statement. */ - if ((got_error_on_execute= mysql_stmt_execute(stmt)) != 0) /* 0 == Success */ + if (cursor_protocol_enabled) { - if (q->abort_on_error) - { - /* We got an error, unexpected */ - die("unable to execute statement '%s': " - "%s (mysql_stmt_errno=%d returned=%d)", - query, mysql_stmt_error(stmt), - mysql_stmt_errno(stmt), got_error_on_execute); - } - else - { - /* We got an error, maybe expected */ - error= run_query_stmt_handle_error(query, q, stmt, ds); - goto end; - } + /* + Use cursor when retrieving result + */ + ulong type= CURSOR_TYPE_READ_ONLY; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) + die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } + + /* + Execute the query + */ + if (mysql_stmt_execute(stmt)) + { + handle_error(query, command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; } /* + When running in cursor_protocol get the warnings from execute here + and keep them in a separate string for later. + */ + if (cursor_protocol_enabled && !disable_warnings) + append_warnings(&ds_execute_warnings, mysql); + + /* We instruct that we want to update the "max_length" field in mysql_stmt_store_result(), this is our only way to know how much buffer to allocate for result data */ { my_bool one= 1; - if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, - (void*) &one) != 0) - die("unable to set stmt attribute " - "'STMT_ATTR_UPDATE_MAX_LENGTH': %s (returned=%d)", - query, err); + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) + die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); } /* If we got here the statement succeeded and was expected to do so, get data. Note that this can still give errors found during execution! */ - if ((err= mysql_stmt_store_result(stmt)) != 0) + if (mysql_stmt_store_result(stmt)) { - if (q->abort_on_error) - { - /* We got an error, unexpected */ - die("unable to execute statement '%s': " - "%s (mysql_stmt_errno=%d returned=%d)", - query, mysql_stmt_error(stmt), - mysql_stmt_errno(stmt), got_error_on_execute); - } - else - { - /* We got an error, maybe expected */ - error= run_query_stmt_handle_error(query, q, stmt, ds); - goto end; - } + handle_error(query, command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; } - /* If we got here the statement was both executed and read succeesfully */ - if (handle_no_error(q)) + /* If we got here the statement was both executed and read successfully */ + handle_no_error(command); + if (!disable_result_log) { - error= 1; - goto end; - } + /* + Not all statements creates a result set. If there is one we can + now create another normal result set that contains the meta + data. This set can be handled almost like any other non prepared + statement result set. + */ + if ((res= mysql_stmt_result_metadata(stmt)) != NULL) + { + /* Take the column count from meta info */ + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); - num_rows= mysql_stmt_num_rows(stmt); + if (display_metadata) + append_metadata(ds, fields, num_fields); - /* - Not all statements creates a result set. If there is one we can - now create another normal result set that contains the meta - data. This set can be handled almost like any other non prepared - statement result set. - */ - if (!disable_result_log && ((res= mysql_stmt_result_metadata(stmt)) != NULL)) - { - /* Take the column count from meta info */ - MYSQL_FIELD *field= mysql_fetch_fields(res); - uint num_fields= mysql_num_fields(res); + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); - /* FIXME check error from the above? */ + append_stmt_result(ds, stmt, fields, num_fields); - if (display_metadata) - run_query_display_metadata(field, num_fields, ds); + mysql_free_result(res); /* Free normal result set with meta data */ - if (!display_result_vertically) + /* Clear prepare warnings */ + dynstr_set(&ds_prepare_warnings, NULL); + } + else { - /* Display the table heading with the names tab separated */ - uint col_idx; - for (col_idx= 0; col_idx < num_fields; col_idx++) - { - if (col_idx) - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[col_idx].name, - strlen(field[col_idx].name)); - } - dynstr_append_mem(ds, "\n", 1); + /* + This is a query without resultset + */ } - /* Now we are to put the real result into the output buffer */ - /* FIXME when it works, create function append_stmt_result() */ + if (!disable_warnings) { - MYSQL_BIND *bind; - my_bool *is_null; - unsigned long *length; - /* FIXME we don't handle vertical display ..... */ - uint col_idx, row_idx; - - /* Allocate array with bind structs, lengths and NULL flags */ - bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), - MYF(MY_WME | MY_FAE)); - length= (unsigned long*) my_malloc(num_fields * sizeof(unsigned long), - MYF(MY_WME | MY_FAE)); - is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), - MYF(MY_WME | MY_FAE)); - - for (col_idx= 0; col_idx < num_fields; col_idx++) - { - /* Allocate data for output */ - /* - FIXME it may be a bug that for non string/blob types - 'max_length' is 0, should try out 'length' in that case - */ - uint max_length= max(field[col_idx].max_length + 1, 1024); - char *str_data= (char *) my_malloc(max_length, MYF(MY_WME | MY_FAE)); - - bind[col_idx].buffer_type= MYSQL_TYPE_STRING; - bind[col_idx].buffer= (char *)str_data; - bind[col_idx].buffer_length= max_length; - bind[col_idx].is_null= &is_null[col_idx]; - bind[col_idx].length= &length[col_idx]; - } - - /* Fill in the data into the structures created above */ - if ((err= mysql_stmt_bind_result(stmt, bind)) != 0) - die("unable to bind result to statement '%s': " - "%s (mysql_stmt_errno=%d returned=%d)", - query, - mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); - - /* Read result from each row */ - for (row_idx= 0; row_idx < num_rows; row_idx++) - { - if ((err= mysql_stmt_fetch(stmt)) != 0) - die("unable to fetch all rows from statement '%s': " - "%s (mysql_stmt_errno=%d returned=%d)", - query, - mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); - - /* Read result from each column */ - for (col_idx= 0; col_idx < num_fields; col_idx++) - { - const char *val; - ulonglong len; - if (col_idx < max_replace_column && replace_column[col_idx]) - { - val= replace_column[col_idx]; - len= strlen(val); - } - else if (*bind[col_idx].is_null) - { - val= "NULL"; - len= 4; - } - else - { - /* FIXME is string terminated? */ - val= (const char *) bind[col_idx].buffer; - len= *bind[col_idx].length; - } - if (!display_result_vertically) - { - if (col_idx) /* No tab before first col */ - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); - } - else - { - dynstr_append(ds, field[col_idx].name); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); - dynstr_append_mem(ds, "\n", 1); - } - } - if (!display_result_vertically) - dynstr_append_mem(ds, "\n", 1); - } - - if ((err= mysql_stmt_fetch(stmt)) != MYSQL_NO_DATA) - die("fetch didn't end with MYSQL_NO_DATA from statement " - "'%s': %s (mysql_stmt_errno=%d returned=%d)", - query, - mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); - - free_replace_column(); + /* Get the warnings from execute */ - for (col_idx= 0; col_idx < num_fields; col_idx++) + /* Append warnings to ds - if there are any */ + if (append_warnings(&ds_execute_warnings, mysql) || + ds_execute_warnings.length || + ds_prepare_warnings.length || + ds_warnings->length) { - /* Free data for output */ - my_free((gptr)bind[col_idx].buffer, MYF(MY_WME | MY_FAE)); + dynstr_append_mem(ds, "Warnings:\n", 10); + if (ds_warnings->length) + dynstr_append_mem(ds, ds_warnings->str, + ds_warnings->length); + if (ds_prepare_warnings.length) + dynstr_append_mem(ds, ds_prepare_warnings.str, + ds_prepare_warnings.length); + if (ds_execute_warnings.length) + dynstr_append_mem(ds, ds_execute_warnings.str, + ds_execute_warnings.length); } - /* Free array with bind structs, lengths and NULL flags */ - my_free((gptr)bind , MYF(MY_WME | MY_FAE)); - my_free((gptr)length , MYF(MY_WME | MY_FAE)); - my_free((gptr)is_null , MYF(MY_WME | MY_FAE)); } - /* Add all warnings to the result */ - run_query_stmt_handle_warnings(mysql, ds); - if (!disable_info) - { - char buf[40]; - sprintf(buf,"affected rows: %lu\n",(ulong) mysql_affected_rows(mysql)); - dynstr_append(ds, buf); - if (mysql_info(mysql)) - { - dynstr_append(ds, "info: "); - dynstr_append(ds, mysql_info(mysql)); - dynstr_append_mem(ds, "\n", 1); - } - } - } - run_query_stmt_handle_warnings(mysql, ds); + append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql)); - if (record) - { - if (!q->record_file[0] && !result_file) - die("Missing result file"); - if (!result_file) - str_to_file(q->record_file, ds->str, ds->length); } - else if (q->record_file[0]) - { - error= check_result(ds, q->record_file, q->require_file); - } - if (res) - mysql_free_result(res); /* Free normal result set with meta data */ - last_result= 0; /* FIXME have no idea what this is about... */ - - if (err >= 1) - mysql_error(mysql); /* FIXME strange, has no effect... */ end: free_replace(); - last_result=0; - if (ds == &ds_tmp) - dynstr_free(&ds_tmp); - if (q->type == Q_EVAL) - dynstr_free(&eval_query); + + if (!disable_warnings) + { + dynstr_free(&ds_prepare_warnings); + dynstr_free(&ds_execute_warnings); + } + + /* + We save the return code (mysql_stmt_errno(stmt)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ var_set_errno(mysql_stmt_errno(stmt)); +#ifndef BUG15518_FIXED mysql_stmt_close(stmt); - DBUG_RETURN(error); + cur_con->stmt= NULL; +#endif + DBUG_VOID_RETURN; } -/****************************************************************************\ - * Broken out sub functions to run_query_stmt() -\****************************************************************************/ -static void run_query_display_metadata(MYSQL_FIELD *field, uint num_fields, - DYNAMIC_STRING *ds) -{ - MYSQL_FIELD *field_end; - dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" - "Column_alias\tType\tLength\tMax length\tIs_null\t" - "Flags\tDecimals\tCharsetnr\n"); +/* + Create a util connection if one does not already exists + and use that to run the query + This is done to avoid implict commit when creating/dropping objects such + as view, sp etc. +*/ - for (field_end= field+num_fields ; - field < field_end ; - field++) +static int util_query(MYSQL* org_mysql, const char* query){ + + MYSQL* mysql; + DBUG_ENTER("util_query"); + + if(!(mysql= cur_con->util_mysql)) { - char buff[22]; - dynstr_append_mem(ds, field->catalog, - field->catalog_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->db, field->db_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->org_table, - field->org_table_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->table, - field->table_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->org_name, - field->org_name_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->name, field->name_length); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->type, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - longlong10_to_str((unsigned int) field->length, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - longlong10_to_str((unsigned int) field->max_length, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, (char*) (IS_NOT_NULL(field->flags) ? - "N" : "Y"), 1); - dynstr_append_mem(ds, "\t", 1); + DBUG_PRINT("info", ("Creating util_mysql")); + if (!(mysql= mysql_init(mysql))) + die("Failed in mysql_init()"); - int10_to_str((int) field->flags, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->decimals, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->charsetnr, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\n", 1); + if (safe_connect(mysql, org_mysql->host, org_mysql->user, + org_mysql->passwd, org_mysql->db, org_mysql->port, + org_mysql->unix_socket)) + die("Could not open util connection: %d %s", + mysql_errno(mysql), mysql_error(mysql)); + + cur_con->util_mysql= mysql; } + + return mysql_query(mysql, query); } -static void run_query_stmt_handle_warnings(MYSQL *mysql, DYNAMIC_STRING *ds) -{ - uint count; - DBUG_ENTER("run_query_stmt_handle_warnings"); - if (!disable_warnings && (count= mysql_warning_count(mysql))) - { - if (mysql_real_query(mysql, "SHOW WARNINGS", 13) == 0) - { - MYSQL_RES *warn_res= mysql_store_result(mysql); - if (!warn_res) - die("Warning count is %u but didn't get any warnings\n", - count); - else - { - dynstr_append_mem(ds, "Warnings:\n", 10); - append_result(ds, warn_res); - mysql_free_result(warn_res); - } - } - } - DBUG_VOID_RETURN; -} +/* + Run query + + flags control the phased/stages of query execution to be performed + if QUERY_SEND bit is on, the query will be sent. If QUERY_REAP is on + the result will be read - for regular query, both bits must be on + SYNPOSIS + run_query + mysql - mysql handle + command - currrent command pointer + +*/ -static int run_query_stmt_handle_error(char *query, struct st_query *q, - MYSQL_STMT *stmt, DYNAMIC_STRING *ds) +static void run_query(MYSQL *mysql, struct st_query *command, int flags) { - if (q->require_file) /* FIXME don't understand this one */ + DYNAMIC_STRING *ds; + DYNAMIC_STRING ds_result; + DYNAMIC_STRING ds_warnings; + DYNAMIC_STRING eval_query; + char *query; + int query_len; + my_bool view_created= 0, sp_created= 0; + my_bool complete_query= ((flags & QUERY_SEND) && (flags & QUERY_REAP)); + + init_dynamic_string(&ds_warnings, NULL, 0, 256); + + /* + Evaluate query if this is an eval command + */ + if (command->type == Q_EVAL) { - abort_not_supported_test(); + init_dynamic_string(&eval_query, "", 16384, 65536); + do_eval(&eval_query, command->query, FALSE); + query = eval_query.str; + query_len = eval_query.length; + } + else + { + query = command->query; + query_len = strlen(query); } - if (q->abort_on_error) - die("query '%s' failed: %d: %s", query, - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + /* + When command->record_file is set the output of _this_ query + should be compared with an already existing file + Create a temporary dynamic string to contain the output from + this query. + */ + if (command->record_file[0]) + { + init_dynamic_string(&ds_result, "", 16384, 65536); + ds= &ds_result; + } else + ds= &ds_res; + + /* + Log the query into the output buffer + */ + if (!disable_query_log && (flags & QUERY_SEND)) { - int i; + replace_dynstr_append_mem(ds, query, query_len); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } - for (i=0 ; (uint) i < q->expected_errors ; i++) + if (view_protocol_enabled && + complete_query && + match_re(&view_re, query)) + { + /* + Create the query as a view. + Use replace since view can exist from a failed mysqltest run + */ + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ", + query_len+64, 256); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) { - if (((q->expected_errno[i].type == ERR_ERRNO) && - (q->expected_errno[i].code.errnum == mysql_stmt_errno(stmt))) || - ((q->expected_errno[i].type == ERR_SQLSTATE) && - (strcmp(q->expected_errno[i].code.sqlstate, - mysql_stmt_sqlstate(stmt)) == 0))) - { - if (i == 0 && q->expected_errors == 1) - { - /* Only log error if there is one possible error */ - dynstr_append_mem(ds,"ERROR ",6); - replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), - strlen(mysql_stmt_sqlstate(stmt))); - dynstr_append_mem(ds, ": ", 2); - replace_dynstr_append_mem(ds,mysql_stmt_error(stmt), - strlen(mysql_stmt_error(stmt))); - dynstr_append_mem(ds,"\n",1); - } - /* Don't log error if we may not get an error */ - else if (q->expected_errno[0].type == ERR_SQLSTATE || - (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0)) - dynstr_append(ds,"Got one of the listed errors\n"); - return 0; /* Ok */ - } + /* + Failed to create the view, this is not fatal + just run the query the normal way + */ + DBUG_PRINT("view_create_error", + ("Failed to create view '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); + + /* Log error to create view */ + verbose_msg("Failed to create view '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); } - DBUG_PRINT("info",("i: %d expected_errors: %d", i, - q->expected_errors)); - dynstr_append_mem(ds, "ERROR ",6); - replace_dynstr_append_mem(ds, mysql_stmt_sqlstate(stmt), - strlen(mysql_stmt_sqlstate(stmt))); - dynstr_append_mem(ds,": ",2); - replace_dynstr_append_mem(ds, mysql_stmt_error(stmt), - strlen(mysql_stmt_error(stmt))); - dynstr_append_mem(ds,"\n",1); - if (i) + else { - if (q->expected_errno[0].type == ERR_ERRNO) - verbose_msg("query '%s' failed with wrong errno %d instead of %d...", - q->query, mysql_stmt_errno(stmt), - q->expected_errno[0].code.errnum); - else - verbose_msg("query '%s' failed with wrong sqlstate %s instead of %s...", - q->query, mysql_stmt_sqlstate(stmt), - q->expected_errno[0].code.sqlstate); - return 1; /* Error */ + /* + Yes, it was possible to create this query as a view + */ + view_created= 1; + query= (char*)"SELECT * FROM mysqltest_tmp_v"; + query_len = strlen(query); + + /* + Collect warnings from create of the view that should otherwise + have been produced when the SELECT was executed + */ + append_warnings(&ds_warnings, cur_con->util_mysql); } - verbose_msg("query '%s' failed: %d: %s", q->query, mysql_stmt_errno(stmt), - mysql_stmt_error(stmt)); + + dynstr_free(&query_str); + + } + + if (sp_protocol_enabled && + complete_query && + match_re(&sp_re, query)) + { /* - if we do not abort on error, failure to run the query does - not fail the whole test case + Create the query as a stored procedure + Drop first since sp can exist from a failed mysqltest run */ - return 0; - } + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;", + query_len+64, 256); + util_query(mysql, query_str.str); + dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n"); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) + { + /* + Failed to create the stored procedure for this query, + this is not fatal just run the query the normal way + */ + DBUG_PRINT("sp_create_error", + ("Failed to create sp '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); - return 0; -} + /* Log error to create sp */ + verbose_msg("Failed to create sp '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); + } + else + { + sp_created= 1; -/* - Handle absence of errors after execution + query= (char*)"CALL mysqltest_tmp_sp()"; + query_len = strlen(query); + } + dynstr_free(&query_str); + } - SYNOPSIS - handle_no_error() - q - context of query + /* + Find out how to run this query - RETURN VALUE - 0 - OK - 1 - Some error was expected from this query. -*/ + Always run with normal C API if it's not a complete + SEND + REAP -static int handle_no_error(struct st_query *q) -{ - DBUG_ENTER("handle_no_error"); + If it is a '?' in the query it may be a SQL level prepared + statement already and we can't do it twice + */ + if (ps_protocol_enabled && + complete_query && + match_re(&ps_re, query)) + run_query_stmt(mysql, command, query, query_len, ds, &ds_warnings); + else + run_query_normal(mysql, command, flags, query, query_len, + ds, &ds_warnings); - if (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0) + if (sp_created) { - /* Error code we wanted was != 0, i.e. not an expected success */ - verbose_msg("query '%s' succeeded - should have failed with errno %d...", - q->query, q->expected_errno[0].code.errnum); - DBUG_RETURN(1); + if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp ")) + die("Failed to drop sp: %d: %s", mysql_errno(mysql), mysql_error(mysql)); } - else if (q->expected_errno[0].type == ERR_SQLSTATE && - strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) + + if (view_created) { - /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ - verbose_msg("query '%s' succeeded - should have failed with sqlstate %s...", - q->query, q->expected_errno[0].code.sqlstate); - DBUG_RETURN(1); + if (util_query(mysql, "DROP VIEW mysqltest_tmp_v ")) + die("Failed to drop view: %d: %s", + mysql_errno(mysql), mysql_error(mysql)); } - DBUG_RETURN(0); + if (command->record_file[0]) + { + + /* A result file was specified for _this_ query */ + if (record) + { + /* + Recording in progress + Dump the output from _this_ query to the specified record_file + */ + str_to_file(command->record_file, ds->str, ds->length); + + } else { + + /* + The output from _this_ query should be checked against an already + existing file which has been specified using --require or --result + */ + check_result(ds, command->record_file, command->require_file); + } + } + + dynstr_free(&ds_warnings); + if (ds == &ds_result) + dynstr_free(&ds_result); + if (command->type == Q_EVAL) + dynstr_free(&eval_query); } + /****************************************************************************\ - * Functions to match SQL statements that can be prepared + * Functions to detect different SQL statements \****************************************************************************/ -static void ps_init_re(void) +static char *re_eprint(int err) +{ + static char epbuf[100]; + size_t len= my_regerror(REG_ITOA|err, (my_regex_t *)NULL, + epbuf, sizeof(epbuf)); + assert(len <= sizeof(epbuf)); + return(epbuf); +} + +static void init_re_comp(my_regex_t *re, const char* str) { + int err= my_regcomp(re, str, (REG_EXTENDED | REG_ICASE | REG_NOSUB), + &my_charset_latin1); + if (err) + { + char erbuf[100]; + int len= my_regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), len, (int)sizeof(erbuf), erbuf); + } +} + +static void init_re(void) +{ + /* + Filter for queries that can be run using the + MySQL Prepared Statements C API + */ const char *ps_re_str = "^(" "[[:space:]]*REPLACE[[:space:]]|" @@ -3595,50 +4338,49 @@ static void ps_init_re(void) "[[:space:]]*UPDATE[[:space:]]+MULTI[[:space:]]|" "[[:space:]]*INSERT[[:space:]]+SELECT[[:space:]])"; - int err= my_regcomp(&ps_re, ps_re_str, - (REG_EXTENDED | REG_ICASE | REG_NOSUB), - &my_charset_latin1); - if (err) - { - char erbuf[100]; - int len= my_regerror(err, &ps_re, erbuf, sizeof(erbuf)); - fprintf(stderr, "error %s, %d/%d `%s'\n", - ps_eprint(err), len, (int)sizeof(erbuf), erbuf); - exit(1); - } + /* + Filter for queries that can be run using the + Stored procedures + */ + const char *sp_re_str =ps_re_str; + + /* + Filter for queries that can be run as views + */ + const char *view_re_str = + "^(" + "[[:space:]]*SELECT[[:space:]])"; + + init_re_comp(&ps_re, ps_re_str); + init_re_comp(&sp_re, sp_re_str); + init_re_comp(&view_re, view_re_str); } -static int ps_match_re(char *stmt_str) +static int match_re(my_regex_t *re, char *str) { - int err= my_regexec(&ps_re, stmt_str, (size_t)0, NULL, 0); + int err= my_regexec(re, str, (size_t)0, NULL, 0); if (err == 0) return 1; else if (err == REG_NOMATCH) return 0; - else + { char erbuf[100]; - int len= my_regerror(err, &ps_re, erbuf, sizeof(erbuf)); - fprintf(stderr, "error %s, %d/%d `%s'\n", - ps_eprint(err), len, (int)sizeof(erbuf), erbuf); - exit(1); + int len= my_regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), len, (int)sizeof(erbuf), erbuf); } + return 0; } -static char *ps_eprint(int err) -{ - static char epbuf[100]; - size_t len= my_regerror(REG_ITOA|err, (my_regex_t *)NULL, epbuf, sizeof(epbuf)); - assert(len <= sizeof(epbuf)); - return(epbuf); -} - - -static void ps_free_reg(void) +static void free_re(void) { my_regfree(&ps_re); + my_regfree(&sp_re); + my_regfree(&view_re); + my_regex_end(); } /****************************************************************************/ @@ -3649,20 +4391,46 @@ void get_query_type(struct st_query* q) uint type; DBUG_ENTER("get_query_type"); - if (*q->query == '}') + if (!parsing_disabled && *q->query == '}') { q->type = Q_END_BLOCK; DBUG_VOID_RETURN; } if (q->type != Q_COMMENT_WITH_COMMAND) - q->type = Q_QUERY; + q->type= parsing_disabled ? Q_COMMENT : Q_QUERY; save=q->query[q->first_word_len]; q->query[q->first_word_len]=0; type=find_type(q->query, &command_typelib, 1+2); q->query[q->first_word_len]=save; if (type > 0) + { q->type=(enum enum_commands) type; /* Found command */ + /* + If queries are disabled, only recognize + --enable_parsing and --disable_parsing + */ + if (parsing_disabled && q->type != Q_ENABLE_PARSING && + q->type != Q_DISABLE_PARSING) + q->type= Q_COMMENT; + } + else if (q->type == Q_COMMENT_WITH_COMMAND && + q->first_word_len && + q->query[q->first_word_len-1] == ';') + { + /* + Detect comment with command using extra delimiter + Ex --disable_query_log; + ^ Extra delimiter causing the command + to be skipped + */ + save= q->query[q->first_word_len-1]; + q->query[q->first_word_len-1]= 0; + type= find_type(q->query, &command_typelib, 1+2); + q->query[q->first_word_len-1]= save; + if (type > 0) + die("Extra delimiter \";\" found"); + } DBUG_VOID_RETURN; } @@ -3687,7 +4455,7 @@ static VAR *var_init(VAR *v, const char *name, int name_len, const char *val, val_len = strlen(val) ; val_alloc_len = val_len + 16; /* room to grow */ if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(sizeof(*tmp_var) - + name_len, MYF(MY_WME)))) + + name_len+1, MYF(MY_WME)))) die("Out of memory"); tmp_var->name = (name) ? (char*) tmp_var + sizeof(*tmp_var) : 0; @@ -3696,12 +4464,10 @@ static VAR *var_init(VAR *v, const char *name, int name_len, const char *val, if (!(tmp_var->str_val = my_malloc(val_alloc_len+1, MYF(MY_WME)))) die("Out of memory"); - memcpy(tmp_var->name, name, name_len); + if (name) + strmake(tmp_var->name, name, name_len); if (val) - { - memcpy(tmp_var->str_val, val, val_len); - tmp_var->str_val[val_len]=0; - } + strmake(tmp_var->str_val, val, val_len); tmp_var->name_len = name_len; tmp_var->str_val_len = val_len; tmp_var->alloced_len = val_alloc_len; @@ -3735,59 +4501,91 @@ static void init_var_hash(MYSQL *mysql) { VAR *v; DBUG_ENTER("init_var_hash"); - if (hash_init(&var_hash, charset_info, + if (hash_init(&var_hash, charset_info, 1024, 0, 0, get_var_key, var_free, MYF(0))) die("Variable hash initialization failed"); - if (opt_big_test) - my_hash_insert(&var_hash, (byte*) var_init(0,"BIG_TEST", 0, "1",0)); + my_hash_insert(&var_hash, (byte*) var_init(0,"BIG_TEST", 0, + (opt_big_test) ? "1" : "0", 0)); v= var_init(0,"MAX_TABLES", 0, (sizeof(ulong) == 4) ? "31" : "62",0); my_hash_insert(&var_hash, (byte*) v); v= var_init(0,"SERVER_VERSION", 0, mysql_get_server_info(mysql), 0); - my_hash_insert(&var_hash, (byte*) v); - v= var_init(0,"DB", 2, db, 0); + my_hash_insert(&var_hash, (byte*) v); v= var_init(0,"DB", 2, db, 0); my_hash_insert(&var_hash, (byte*) v); DBUG_VOID_RETURN; } +static void mark_progress(int line) +{ +#ifdef NOT_YET + static FILE* fp = NULL; + static double first; + + struct timeval tv; + double now; + + if (!fp) + { + + fp = fopen("/tmp/mysqltest_progress.log", "wt"); + + if (!fp) + { + abort(); + } + + gettimeofday(&tv, NULL); + first = tv.tv_sec * 1e6 + tv.tv_usec; + } + + gettimeofday(&tv, NULL); + now = tv.tv_sec * 1e6 + tv.tv_usec; + + fprintf(fp, "%d %f\n", parser.current_line, (now - first) / 1e6); +#endif +} int main(int argc, char **argv) { - int error = 0; struct st_query *q; - my_bool require_file=0, q_send_flag=0, query_executed= 0, abort_flag= 0; + my_bool require_file=0, q_send_flag=0, abort_flag= 0, + query_executed= 0; char save_file[FN_REFLEN]; MY_STAT res_info; MY_INIT(argv[0]); - { - DBUG_ENTER("main"); - DBUG_PROCESS(argv[0]); /* Use all time until exit if no explicit 'start_timer' */ timer_start= timer_now(); save_file[0]=0; TMPDIR[0]=0; + + /* Init cons */ memset(cons, 0, sizeof(cons)); cons_end = cons + MAX_CONS; next_con = cons + 1; cur_con = cons; + /* Init file stack */ memset(file_stack, 0, sizeof(file_stack)); - memset(&master_pos, 0, sizeof(master_pos)); file_stack_end= file_stack + MAX_INCLUDE_DEPTH - 1; cur_file= file_stack; - lineno = lineno_stack; - my_init_dynamic_array(&q_lines, sizeof(struct st_query*), INIT_Q_LINES, - INIT_Q_LINES); + /* Init block stack */ memset(block_stack, 0, sizeof(block_stack)); block_stack_end= block_stack + BLOCK_STACK_DEPTH - 1; cur_block= block_stack; cur_block->ok= TRUE; /* Outer block should always be executed */ - cur_block->cmd= Q_UNKNOWN; + cur_block->cmd= cmd_none; + + my_init_dynamic_array(&q_lines, sizeof(struct st_query*), INIT_Q_LINES, + INIT_Q_LINES); + + memset(&master_pos, 0, sizeof(master_pos)); init_dynamic_string(&ds_res, "", 0, 65536); parse_args(argc, argv); + + DBUG_PRINT("info",("result_file: '%s'", result_file ? result_file : "")); if (mysql_server_init(embedded_server_arg_count, embedded_server_args, (char**) embedded_server_groups)) @@ -3796,17 +4594,21 @@ int main(int argc, char **argv) { cur_file->file= stdin; cur_file->file_name= my_strdup("<stdin>", MYF(MY_WME)); + cur_file->lineno= 1; } - *lineno=1; #ifndef EMBEDDED_LIBRARY if (manager_host) init_manager(); #endif - if (ps_protocol) - { + init_re(); + ps_protocol_enabled= ps_protocol; + sp_protocol_enabled= sp_protocol; + view_protocol_enabled= view_protocol; + cursor_protocol_enabled= cursor_protocol; + /* Cursor protcol implies ps protocol */ + if (cursor_protocol_enabled) ps_protocol_enabled= 1; - ps_init_re(); - } + if (!( mysql_init(&cur_con->mysql))) die("Failed in mysql_init()"); if (opt_compress) @@ -3815,19 +4617,30 @@ int main(int argc, char **argv) mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_NAME, charset_name); #ifdef HAVE_OPENSSL + opt_ssl_verify_server_cert= TRUE; /* Always on in mysqltest */ if (opt_use_ssl) + { mysql_ssl_set(&cur_con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, opt_ssl_cipher); + mysql_options(&cur_con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); + } #endif if (!(cur_con->name = my_strdup("default", MYF(MY_WME)))) die("Out of memory"); if (safe_connect(&cur_con->mysql, host, user, pass, db, port, unix_sock)) - die("Failed in mysql_real_connect(): %s", mysql_error(&cur_con->mysql)); + die("Could not open connection '%s': %d %s", cur_con->name, + mysql_errno(&cur_con->mysql), mysql_error(&cur_con->mysql)); init_var_hash(&cur_con->mysql); +#ifdef __WIN__ + init_tmp_sh_file(); + init_win_path_patterns(); +#endif + /* Initialize $mysql_errno with -1, so we can - distinguish it from valid values ( >= 0 ) and @@ -3845,7 +4658,9 @@ int main(int argc, char **argv) q->last_argument= q->first_argument; processed = 1; switch (q->type) { - case Q_CONNECT: do_connect(q); break; + case Q_CONNECT: + do_connect(q); + break; case Q_CONNECTION: select_connection(q); break; case Q_DISCONNECT: case Q_DIRTY_CLOSE: @@ -3861,6 +4676,8 @@ int main(int argc, char **argv) case Q_DISABLE_RESULT_LOG: disable_result_log=1; break; case Q_ENABLE_WARNINGS: disable_warnings=0; break; case Q_DISABLE_WARNINGS: disable_warnings=1; break; + case Q_ENABLE_PS_WARNINGS: disable_ps_warnings=0; break; + case Q_DISABLE_PS_WARNINGS: disable_ps_warnings=1; break; case Q_ENABLE_INFO: disable_info=0; break; case Q_DISABLE_INFO: disable_info=1; break; case Q_ENABLE_METADATA: display_metadata=1; break; @@ -3874,9 +4691,9 @@ int main(int argc, char **argv) case Q_SERVER_START: do_server_start(q); break; case Q_SERVER_STOP: do_server_stop(q); break; #endif - case Q_INC: do_modify_var(q, "inc", DO_INC); break; - case Q_DEC: do_modify_var(q, "dec", DO_DEC); break; - case Q_ECHO: do_echo(q); break; + case Q_INC: do_modify_var(q, DO_INC); break; + case Q_DEC: do_modify_var(q, DO_DEC); break; + case Q_ECHO: do_echo(q); query_executed= 1; break; case Q_SYSTEM: do_system(q); break; case Q_DELIMITER: strmake(delimiter, q->first_argument, sizeof(delimiter) - 1); @@ -3903,16 +4720,7 @@ int main(int argc, char **argv) case Q_QUERY_HORIZONTAL: { my_bool old_display_result_vertically= display_result_vertically; - if (!q->query[q->first_word_len]) - { - /* This happens when we use 'query_..' on it's own line */ - q_send_flag=1; - DBUG_PRINT("info", - ("query: '%s' first_word_len: %d send_flag=1", - q->query, q->first_word_len)); - break; - } - /* fix up query pointer if this is * first iteration for this line */ + /* fix up query pointer if this is first iteration for this line */ if (q->query == q->query_buf) q->query += q->first_word_len + 1; display_result_vertically= (q->type==Q_QUERY_VERTICAL); @@ -3922,7 +4730,7 @@ int main(int argc, char **argv) q->require_file=require_file; save_file[0]=0; } - error|= run_query(&cur_con->mysql, q, QUERY_REAP|QUERY_SEND); + run_query(&cur_con->mysql, q, QUERY_REAP|QUERY_SEND); display_result_vertically= old_display_result_vertically; q->last_argument= q->end; query_executed= 1; @@ -3949,7 +4757,7 @@ int main(int argc, char **argv) q->require_file=require_file; save_file[0]=0; } - error |= run_query(&cur_con->mysql, q, flags); + run_query(&cur_con->mysql, q, flags); query_executed= 1; q->last_argument= q->end; break; @@ -3957,20 +4765,20 @@ int main(int argc, char **argv) case Q_SEND: if (!q->query[q->first_word_len]) { - /* This happens when we use 'send' on it's own line */ + /* This happens when we use 'send' on its own line */ q_send_flag=1; break; } - /* fix up query pointer if this is * first iteration for this line */ + /* fix up query pointer if this is first iteration for this line */ if (q->query == q->query_buf) q->query += q->first_word_len; /* - run_query() can execute a query partially, depending on the flags + run_query() can execute a query partially, depending on the flags. QUERY_SEND flag without QUERY_REAP tells it to just send the query and read the result some time later when reap instruction is given on this connection. */ - error |= run_query(&cur_con->mysql, q, QUERY_SEND); + run_query(&cur_con->mysql, q, QUERY_SEND); query_executed= 1; q->last_argument= q->end; break; @@ -4033,25 +4841,45 @@ int main(int argc, char **argv) ps_protocol_enabled= ps_protocol; break; case Q_DISABLE_RECONNECT: - cur_con->mysql.reconnect= 0; + { + my_bool reconnect= 0; + mysql_options(&cur_con->mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); break; + } case Q_ENABLE_RECONNECT: - cur_con->mysql.reconnect= 1; + { + my_bool reconnect= 1; + mysql_options(&cur_con->mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); break; + } + case Q_DISABLE_PARSING: + parsing_disabled++; + break; + case Q_ENABLE_PARSING: + /* + Ensure we don't get parsing_disabled < 0 as this would accidentally + disable code we don't want to have disabled + */ + if (parsing_disabled > 0) + parsing_disabled--; + break; + case Q_EXIT: abort_flag= 1; break; - default: processed = 0; break; + default: + processed= 0; + break; } } if (!processed) { - current_line_inc = 0; + current_line_inc= 0; switch (q->type) { - case Q_WHILE: do_block(Q_WHILE, q); break; - case Q_IF: do_block(Q_IF, q); break; + case Q_WHILE: do_block(cmd_while, q); break; + case Q_IF: do_block(cmd_if, q); break; case Q_END_BLOCK: do_done(q); break; default: current_line_inc = 1; break; } @@ -4070,52 +4898,70 @@ int main(int argc, char **argv) } parser.current_line += current_line_inc; + mark_progress(parser.current_line); } - if (!query_executed && result_file && my_stat(result_file, &res_info, 0)) - { - /* - my_stat() successful on result file. Check if we have not run a - single query, but we do have a result file that contains data. - Note that we don't care, if my_stat() fails. For example, for a - non-existing or non-readable file, we assume it's fine to have - no query output from the test file, e.g. regarded as no error. - */ - if (res_info.st_size) - error|= (RESULT_CONTENT_MISMATCH | RESULT_LENGTH_MISMATCH); - } - if (ds_res.length && !error) + start_lineno= 0; + + /* + The whole test has been executed _sucessfully_. + Time to compare result or save it to record file. + The entire output from test is now kept in ds_res. + */ + if (ds_res.length) { if (result_file) { - if (!record) - error |= check_result(&ds_res, result_file, q->require_file); + if (record) + { + /* Dump the output from test to result file */ + str_to_file(result_file, ds_res.str, ds_res.length); + } else - str_to_file(result_file, ds_res.str, ds_res.length); + { + /* Check that the output from test is equal to result file + - detect missing result file + - detect zero size result file + */ + check_result(&ds_res, result_file, 0); + } } else { - /* Print the result to stdout */ + /* No result_file specified to compare with, print to stdout */ printf("%s", ds_res.str); } } - dynstr_free(&ds_res); + else + { + die("The test didn't produce any output"); + } - if (!silent) + if (!query_executed && result_file && my_stat(result_file, &res_info, 0)) { - if (error) - printf("not ok\n"); - else - printf("ok\n"); + /* + my_stat() successful on result file. Check if we have not run a + single query, but we do have a result file that contains data. + Note that we don't care, if my_stat() fails. For example, for a + non-existing or non-readable file, we assume it's fine to have + no query output from the test file, e.g. regarded as no error. + */ + die("No queries executed but result file found!"); } + + dynstr_free(&ds_res); + if (!got_end_timer) timer_output(); /* No end_timer cmd, end it */ free_used_memory(); my_end(MY_CHECK_ERROR); - exit(error ? 1 : 0); - return error ? 1 : 0; /* Keep compiler happy */ - } + + /* Yes, if we got this far the test has suceeded! Sakila smiles */ + if (!silent) + printf("ok\n"); + exit(0); + return 0; /* Keep compiler happy */ } @@ -4790,7 +5636,7 @@ static int get_next_bit(REP_SET *set,uint lastpos) } /* find if there is a same set in sets. If there is, use it and - free given set, else put in given set in sets and return it's + free given set, else put in given set in sets and return its position */ static int find_set(REP_SETS *sets,REP_SET *find) @@ -4809,7 +5655,7 @@ static int find_set(REP_SETS *sets,REP_SET *find) /* find if there is a found_set with same table_offset & found_offset If there is return offset to it, else add new offset and return pos. - Pos returned is -offset-2 in found_set_structure because it's is + Pos returned is -offset-2 in found_set_structure because it is saved in set->next and set->next[] >= 0 points to next set and set->next[] == -1 is reserved for end without replaces. */ @@ -4857,66 +5703,57 @@ static uint replace_len(my_string str) } - /* Replace strings; Return length of result string */ - -uint replace_strings(REPLACE *rep, my_string *start,uint *max_length, - const char *from) +/* Replace strings while appending to ds */ +void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, + const char *str, int len) { reg1 REPLACE *rep_pos; reg2 REPLACE_STRING *rep_str; - my_string to,end,pos,new_str; + const char *start, *from; + DBUG_ENTER("replace_strings_append"); - end=(to= *start) + *max_length-1; + start= from= str; rep_pos=rep+1; for (;;) { + /* Loop through states */ + DBUG_PRINT("info", ("Looping through states")); while (!rep_pos->found) - { - rep_pos= rep_pos->next[(uchar) *from]; - if (to == end) - { - (*max_length)+=8192; - if (!(new_str=my_realloc(*start,*max_length,MYF(MY_WME)))) - return (uint) -1; - to=new_str+(to - *start); - end=(*start=new_str)+ *max_length-1; - } - *to++= *from++; - } + rep_pos= rep_pos->next[(uchar) *from++]; + + /* Does this state contain a string to be replaced */ if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string) - return (uint) (to - *start)-1; - to-=rep_str->to_offset; - for (pos=rep_str->replace_string; *pos ; pos++) { - if (to == end) - { - (*max_length)*=2; - if (!(new_str=my_realloc(*start,*max_length,MYF(MY_WME)))) - return (uint) -1; - to=new_str+(to - *start); - end=(*start=new_str)+ *max_length-1; - } - *to++= *pos; + /* No match found */ + dynstr_append_mem(ds, start, from - start - 1); + DBUG_PRINT("exit", ("Found no more string to replace, appended: %s", start)); + DBUG_VOID_RETURN; } + + /* Found a string that needs to be replaced */ + DBUG_PRINT("info", ("found: %d, to_offset: %d, from_offset: %d, string: %s", + rep_str->found, rep_str->to_offset, + rep_str->from_offset, rep_str->replace_string)); + + /* Append part of original string before replace string */ + dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start); + + /* Append replace string */ + dynstr_append_mem(ds, rep_str->replace_string, + strlen(rep_str->replace_string)); + if (!*(from-=rep_str->from_offset) && rep_pos->found != 2) - return (uint) (to - *start); + { + /* End of from string */ + DBUG_PRINT("exit", ("Found end of from string")); + DBUG_VOID_RETURN; + } + DBUG_ASSERT(from <= str+len); + start= from; rep_pos=rep; } } -static int initialize_replace_buffer(void) -{ - out_length=8192; - if (!(out_buff=my_malloc(out_length,MYF(MY_WME)))) - return(1); - return 0; -} - -static void free_replace_buffer(void) -{ - my_free(out_buff,MYF(MY_WME)); -} - /**************************************************************************** Replace results for a column @@ -4975,103 +5812,6 @@ static void get_replace_column(struct st_query *q) q->last_argument= q->end; } -#if defined(__NETWARE__) || defined(__WIN__) -/* - Substitute environment variables with text. - SYNOPSIS - subst_env_var() - arg String that should be substitute - - DESCRIPTION - This function takes a string as an input and replaces the - environment variables, that starts with '$' character, with it value. - - NOTES - Return string must be freed with my_free() - - RETURN - String with environment variables replaced. -*/ - -static char *subst_env_var(const char *str) -{ - char *result; - char *pos; - - result= pos= my_malloc(MAX_QUERY, MYF(MY_FAE)); - while (*str) - { - /* - need this only when we want to provide the functionality of - escaping through \ 'backslash' - if ((result == pos && *str=='$') || - (result != pos && *str=='$' && str[-1] !='\\')) - */ - if (*str == '$') - { - char env_var[256], *env_pos= env_var, *subst; - - /* Search for end of environment variable */ - for (str++; - *str && !isspace(*str) && *str != '\\' && *str != '/' && - *str != '$'; - str++) - *env_pos++= *str; - *env_pos= 0; - - if (!(subst= getenv(env_var))) - { - my_free(result, MYF(0)); - die("MYSQLTEST.NLM: Environment variable %s is not defined", - env_var); - } - - /* get the string to be substitued for env_var */ - pos= strmov(pos, subst); - /* Process delimiter in *str again */ - } - else - *pos++= *str++; - } - *pos= 0; - return result; -} - - -/* - popen replacement for Netware - - SYNPOSIS - my_popen() - name Command to execute (with possible env variables) - mode Mode for popen. - - NOTES - Environment variable expansion does not take place for popen function - on NetWare, so we use this function to wrap around popen to do this. - For the moment we ignore 'mode' and always use 'r0' - - RETURN - # >= 0 File handle - -1 Error -*/ - -#undef popen /* Remove wrapper */ -#ifdef __WIN__ -#define popen _popen /* redefine for windows */ -#endif - -FILE *my_popen(const char *cmd, const char *mode __attribute__((unused))) -{ - char *subst_cmd; - FILE *res_file; - - subst_cmd= subst_env_var(cmd); - res_file= popen(subst_cmd, "r0"); - my_free(subst_cmd, MYF(0)); - return res_file; -} -#endif /* __NETWARE__ or __WIN__*/ diff --git a/client/sql_string.cc b/client/sql_string.cc index 690997152f1..51f802e7465 100644 --- a/client/sql_string.cc +++ b/client/sql_string.cc @@ -28,6 +28,11 @@ #include <floatingpoint.h> #endif +/* + The following extern declarations are ok as these are interface functions + required by the string function +*/ + extern gptr sql_alloc(unsigned size); extern void sql_element_free(void *ptr); @@ -97,14 +102,7 @@ bool String::set(longlong num, CHARSET_INFO *cs) if (alloc(l)) return TRUE; - if (cs->cset->snprintf == my_snprintf_8bit) - { - str_length=(uint32) (longlong10_to_str(num,Ptr,-10)-Ptr); - } - else - { - str_length=cs->cset->snprintf(cs,Ptr,l,"%d",num); - } + str_length=(uint32) (cs->cset->longlong10_to_str)(cs,Ptr,l,-10,num); str_charset=cs; return FALSE; } @@ -115,14 +113,7 @@ bool String::set(ulonglong num, CHARSET_INFO *cs) if (alloc(l)) return TRUE; - if (cs->cset->snprintf == my_snprintf_8bit) - { - str_length=(uint32) (longlong10_to_str(num,Ptr,10)-Ptr); - } - else - { - str_length=cs->cset->snprintf(cs,Ptr,l,"%d",num); - } + str_length=(uint32) (cs->cset->longlong10_to_str)(cs,Ptr,l,10,num); str_charset=cs; return FALSE; } @@ -130,12 +121,13 @@ bool String::set(ulonglong num, CHARSET_INFO *cs) bool String::set(double num,uint decimals, CHARSET_INFO *cs) { char buff[331]; + uint dummy_errors; str_charset=cs; if (decimals >= NOT_FIXED_DEC) { - sprintf(buff,"%.14g",num); // Enough for a DATETIME - return copy(buff, (uint32) strlen(buff), &my_charset_latin1, cs); + uint32 len= my_sprintf(buff,(buff, "%.14g",num));// Enough for a DATETIME + return copy(buff, len, &my_charset_latin1, cs, &dummy_errors); } #ifdef HAVE_FCONVERT int decpt,sign; @@ -150,7 +142,8 @@ bool String::set(double num,uint decimals, CHARSET_INFO *cs) buff[0]='-'; pos=buff; } - return copy(pos,(uint32) strlen(pos), &my_charset_latin1, cs); + uint dummy_errors; + return copy(pos,(uint32) strlen(pos), &my_charset_latin1, cs, &dummy_errors); } if (alloc((uint32) ((uint32) decpt+3+decimals))) return TRUE; @@ -200,7 +193,8 @@ end: #else sprintf(buff,"%.*f",(int) decimals,num); #endif - return copy(buff,(uint32) strlen(buff), &my_charset_latin1, cs); + return copy(buff,(uint32) strlen(buff), &my_charset_latin1, cs, + &dummy_errors); #endif } @@ -237,55 +231,163 @@ bool String::copy(const char *str,uint32 arg_length, CHARSET_INFO *cs) return FALSE; } -/* Copy with charset convertion */ -bool String::copy(const char *str,uint32 arg_length, CHARSET_INFO *from, CHARSET_INFO *to) + +/* + Checks that the source string can be just copied to the destination string + without conversion. + + SYNPOSIS + + needs_conversion() + arg_length Length of string to copy. + from_cs Character set to copy from + to_cs Character set to copy to + uint32 *offset Returns number of unaligned characters. + + RETURN + 0 No conversion needed + 1 Either character set conversion or adding leading zeros + (e.g. for UCS-2) must be done +*/ + +bool String::needs_conversion(uint32 arg_length, + CHARSET_INFO *from_cs, + CHARSET_INFO *to_cs, + uint32 *offset) +{ + *offset= 0; + if ((to_cs == &my_charset_bin) || + (to_cs == from_cs) || + my_charset_same(from_cs, to_cs) || + ((from_cs == &my_charset_bin) && + (!(*offset=(arg_length % to_cs->mbminlen))))) + return FALSE; + return TRUE; +} + + +/* + Copy a multi-byte character sets with adding leading zeros. + + SYNOPSIS + + copy_aligned() + str String to copy + arg_length Length of string. This should NOT be dividable with + cs->mbminlen. + offset arg_length % cs->mb_minlength + cs Character set for 'str' + + NOTES + For real multi-byte, ascii incompatible charactser sets, + like UCS-2, add leading zeros if we have an incomplete character. + Thus, + SELECT _ucs2 0xAA + will automatically be converted into + SELECT _ucs2 0x00AA + + RETURN + 0 ok + 1 error +*/ + +bool String::copy_aligned(const char *str,uint32 arg_length, uint32 offset, + CHARSET_INFO *cs) { - uint32 new_length=to->mbmaxlen*arg_length; - int cnvres; - my_wc_t wc; - const uchar *s=(const uchar *)str; - const uchar *se=s+arg_length; - uchar *d, *de; + /* How many bytes are in incomplete character */ + offset= cs->mbmaxlen - offset; /* How many zeros we should prepend */ + DBUG_ASSERT(offset && offset != cs->mbmaxlen); - if (alloc(new_length)) + uint32 aligned_length= arg_length + offset; + if (alloc(aligned_length)) return TRUE; + + /* + Note, this is only safe for little-endian UCS-2. + If we add big-endian UCS-2 sometimes, this code + will be more complicated. But it's OK for now. + */ + bzero((char*) Ptr, offset); + memcpy(Ptr + offset, str, arg_length); + Ptr[aligned_length]=0; + /* str_length is always >= 0 as arg_length is != 0 */ + str_length= aligned_length; + str_charset= cs; + return FALSE; +} + - d=(uchar *)Ptr; - de=d+new_length; +bool String::set_or_copy_aligned(const char *str,uint32 arg_length, + CHARSET_INFO *cs) +{ + /* How many bytes are in incomplete character */ + uint32 offset= (arg_length % cs->mbminlen); - for (str_length=new_length ; s < se && d < de ; ) + if (!offset) /* All characters are complete, just copy */ { - if ((cnvres=from->cset->mb_wc(from,&wc,s,se)) > 0 ) - { - s+=cnvres; - } - else if (cnvres==MY_CS_ILSEQ) - { - s++; - wc='?'; - } - else - break; + set(str, arg_length, cs); + return FALSE; + } + return copy_aligned(str, arg_length, offset, cs); +} -outp: - if((cnvres=to->cset->wc_mb(to,wc,d,de)) >0 ) - { - d+=cnvres; - } - else if (cnvres==MY_CS_ILUNI && wc!='?') - { - wc='?'; - goto outp; - } - else - break; + /* Copy with charset convertion */ + +bool String::copy(const char *str, uint32 arg_length, + CHARSET_INFO *from_cs, CHARSET_INFO *to_cs, uint *errors) +{ + uint32 offset; + if (!needs_conversion(arg_length, from_cs, to_cs, &offset)) + { + *errors= 0; + return copy(str, arg_length, to_cs); + } + if ((from_cs == &my_charset_bin) && offset) + { + *errors= 0; + return copy_aligned(str, arg_length, offset, to_cs); } - Ptr[new_length]=0; - length((uint32) (d-(uchar *)Ptr)); - str_charset=to; + uint32 new_length= to_cs->mbmaxlen*arg_length; + if (alloc(new_length)) + return TRUE; + str_length=copy_and_convert((char*) Ptr, new_length, to_cs, + str, arg_length, from_cs, errors); + str_charset=to_cs; return FALSE; } + +/* + Set a string to the value of a latin1-string, keeping the original charset + + SYNOPSIS + copy_or_set() + str String of a simple charset (latin1) + arg_length Length of string + + IMPLEMENTATION + If string object is of a simple character set, set it to point to the + given string. + If not, make a copy and convert it to the new character set. + + RETURN + 0 ok + 1 Could not allocate result buffer + +*/ + +bool String::set_ascii(const char *str, uint32 arg_length) +{ + if (str_charset->mbminlen == 1) + { + set(str, arg_length, str_charset); + return 0; + } + uint dummy_errors; + return copy(str, arg_length, &my_charset_latin1, str_charset, &dummy_errors); +} + + /* This is used by mysql.cc */ bool String::fill(uint32 max_length,char fill_char) @@ -320,11 +422,34 @@ bool String::append(const String &s) return FALSE; } + +/* + Append an ASCII string to the a string of the current character set +*/ + bool String::append(const char *s,uint32 arg_length) { - if (!arg_length) // Default argument - if (!(arg_length= (uint32) strlen(s))) - return FALSE; + if (!arg_length) + return FALSE; + + /* + For an ASCII incompatible string, e.g. UCS-2, we need to convert + */ + if (str_charset->mbminlen > 1) + { + uint32 add_length=arg_length * str_charset->mbmaxlen; + uint dummy_errors; + if (realloc(str_length+ add_length)) + return TRUE; + str_length+= copy_and_convert(Ptr+str_length, add_length, str_charset, + s, arg_length, &my_charset_latin1, + &dummy_errors); + return FALSE; + } + + /* + For an ASCII compatinble string we can just append. + */ if (realloc(str_length+arg_length)) return TRUE; memcpy(Ptr+str_length,s,arg_length); @@ -332,6 +457,46 @@ bool String::append(const char *s,uint32 arg_length) return FALSE; } + +/* + Append a 0-terminated ASCII string +*/ + +bool String::append(const char *s) +{ + return append(s, strlen(s)); +} + + +/* + Append a string in the given charset to the string + with character set recoding +*/ + +bool String::append(const char *s,uint32 arg_length, CHARSET_INFO *cs) +{ + uint32 dummy_offset; + + if (needs_conversion(arg_length, cs, str_charset, &dummy_offset)) + { + uint32 add_length= arg_length / cs->mbminlen * str_charset->mbmaxlen; + uint dummy_errors; + if (realloc(str_length + add_length)) + return TRUE; + str_length+= copy_and_convert(Ptr+str_length, add_length, str_charset, + s, arg_length, cs, &dummy_errors); + } + else + { + if (realloc(str_length + arg_length)) + return TRUE; + memcpy(Ptr + str_length, s, arg_length); + str_length+= arg_length; + } + return FALSE; +} + + #ifdef TO_BE_REMOVED bool String::append(FILE* file, uint32 arg_length, myf my_flags) { @@ -360,48 +525,33 @@ bool String::append(IO_CACHE* file, uint32 arg_length) return FALSE; } -uint32 String::numchars() +bool String::append_with_prefill(const char *s,uint32 arg_length, + uint32 full_length, char fill_char) { -#ifdef USE_MB - register uint32 n=0,mblen; - register const char *mbstr=Ptr; - register const char *end=mbstr+str_length; - if (use_mb(str_charset)) + int t_length= arg_length > full_length ? arg_length : full_length; + + if (realloc(str_length + t_length)) + return TRUE; + t_length= full_length - arg_length; + if (t_length > 0) { - while (mbstr < end) { - if ((mblen=my_ismbchar(str_charset, mbstr,end))) mbstr+=mblen; - else ++mbstr; - ++n; - } - return n; + bfill(Ptr+str_length, t_length, fill_char); + str_length=str_length + t_length; } - else -#endif - return str_length; + append(s, arg_length); + return FALSE; +} + +uint32 String::numchars() +{ + return str_charset->cset->numchars(str_charset, Ptr, Ptr+str_length); } int String::charpos(int i,uint32 offset) { -#ifdef USE_MB - register uint32 mblen; - register const char *mbstr=Ptr+offset; - register const char *end=Ptr+str_length; - if (use_mb(str_charset)) - { - if (i<=0) return i; - while (i && mbstr < end) { - if ((mblen=my_ismbchar(str_charset, mbstr,end))) mbstr+=mblen; - else ++mbstr; - --i; - } - if ( INT_MAX32-i <= (int) (mbstr-Ptr-offset)) - return INT_MAX32; - else - return (int) ((mbstr-Ptr-offset)+i); - } - else -#endif + if (i <= 0) return i; + return str_charset->cset->charpos(str_charset,Ptr+offset,Ptr+str_length,i); } int String::strstr(const String &s,uint32 offset) @@ -432,40 +582,6 @@ skip: } /* - Search after a string without regarding to case - This needs to be replaced when we have character sets per string -*/ - -int String::strstr_case(const String &s,uint32 offset) -{ - if (s.length()+offset <= str_length) - { - if (!s.length()) - return ((int) offset); // Empty string is always found - - register const char *str = Ptr+offset; - register const char *search=s.ptr(); - const char *end=Ptr+str_length-s.length()+1; - const char *search_end=s.ptr()+s.length(); -skip: - while (str != end) - { - if (str_charset->sort_order[*str++] == str_charset->sort_order[*search]) - { - register char *i,*j; - i=(char*) str; j=(char*) search+1; - while (j != search_end) - if (str_charset->sort_order[*i++] != - str_charset->sort_order[*j++]) - goto skip; - return (int) (str-Ptr) -1; - } - } - } - return -1; -} - -/* ** Search string from end. Offset is offset to the end of string */ @@ -504,14 +620,20 @@ skip: bool String::replace(uint32 offset,uint32 arg_length,const String &to) { - long diff = (long) to.length()-(long) arg_length; + return replace(offset,arg_length,to.ptr(),to.length()); +} + +bool String::replace(uint32 offset,uint32 arg_length, + const char *to,uint32 length) +{ + long diff = (long) length-(long) arg_length; if (offset+arg_length <= str_length) { if (diff < 0) { - if (to.length()) - memcpy(Ptr+offset,to.ptr(),to.length()); - bmove(Ptr+offset+to.length(),Ptr+offset+arg_length, + if (length) + memcpy(Ptr+offset,to,length); + bmove(Ptr+offset+length,Ptr+offset+arg_length, str_length-offset-arg_length); } else @@ -523,14 +645,15 @@ bool String::replace(uint32 offset,uint32 arg_length,const String &to) bmove_upp(Ptr+str_length+diff,Ptr+str_length, str_length-offset-arg_length); } - if (to.length()) - memcpy(Ptr+offset,to.ptr(),to.length()); + if (length) + memcpy(Ptr+offset,to,length); } str_length+=(uint32) diff; } return FALSE; } + // added by Holyfoot for "geometry" needs int String::reserve(uint32 space_needed, uint32 grow_by) { @@ -542,9 +665,8 @@ int String::reserve(uint32 space_needed, uint32 grow_by) return FALSE; } -void String::qs_append(const char *str) +void String::qs_append(const char *str, uint32 len) { - int len = (int)strlen(str); memcpy(Ptr + str_length, str, len + 1); str_length += len; } @@ -552,8 +674,7 @@ void String::qs_append(const char *str) void String::qs_append(double d) { char *buff = Ptr + str_length; - sprintf(buff,"%.14g", d); - str_length += (int)strlen(buff); + str_length+= my_sprintf(buff, (buff, "%.14g", d)); } void String::qs_append(double *d) @@ -563,90 +684,70 @@ void String::qs_append(double *d) qs_append(ld); } -void String::qs_append(const char &c) +void String::qs_append(int i) { - Ptr[str_length] = c; - str_length += sizeof(c); + char *buff= Ptr + str_length; + char *end= int10_to_str(i, buff, -10); + str_length+= (int) (end-buff); } - -int sortcmp(const String *x,const String *y) +void String::qs_append(uint i) { - const char *s= x->ptr(); - const char *t= y->ptr(); - uint32 x_len=x->length(),y_len=y->length(),len=min(x_len,y_len); - - if (use_strnxfrm(x->str_charset)) - { -#ifndef CMP_ENDSPACE - while (x_len && my_isspace(x->str_charset,s[x_len-1])) - x_len--; - while (y_len && my_isspace(x->str_charset,t[y_len-1])) - y_len--; -#endif - return my_strnncoll(x->str_charset, - (unsigned char *)s,x_len,(unsigned char *)t,y_len); - } - else - { - x_len-=len; // For easy end space test - y_len-=len; - if (x->str_charset->sort_order) - { - while (len--) - { - if (x->str_charset->sort_order[(uchar) *s++] != - x->str_charset->sort_order[(uchar) *t++]) - return ((int) x->str_charset->sort_order[(uchar) s[-1]] - - (int) x->str_charset->sort_order[(uchar) t[-1]]); - } - } - else - { - while (len--) - { - if (*s++ != *t++) - return ((int) s[-1] - (int) t[-1]); - } - } -#ifndef CMP_ENDSPACE - /* Don't compare end space in strings */ - { - if (y_len) - { - const char *end=t+y_len; - for (; t != end ; t++) - if (!my_isspace(x->str_charset,*t)) - return -1; - } - else - { - const char *end=s+x_len; - for (; s != end ; s++) - if (!my_isspace(x->str_charset,*s)) - return 1; - } - return 0; - } -#else - return (int) (x_len-y_len); -#endif /* CMP_ENDSPACE */ - } + char *buff= Ptr + str_length; + char *end= int10_to_str(i, buff, 10); + str_length+= (int) (end-buff); } +/* + Compare strings according to collation, without end space. + + SYNOPSIS + sortcmp() + s First string + t Second string + cs Collation + + NOTE: + Normally this is case sensitive comparison -int stringcmp(const String *x,const String *y) + RETURN + < 0 s < t + 0 s == t + > 0 s > t +*/ + + +int sortcmp(const String *s,const String *t, CHARSET_INFO *cs) { - const char *s= x->ptr(); - const char *t= y->ptr(); - uint32 x_len=x->length(),y_len=y->length(),len=min(x_len,y_len); + return cs->coll->strnncollsp(cs, + (unsigned char *) s->ptr(),s->length(), + (unsigned char *) t->ptr(),t->length(), 0); +} - while (len--) - { - if (*s++ != *t++) - return ((int) (uchar) s[-1] - (int) (uchar) t[-1]); - } - return (int) (x_len-y_len); + +/* + Compare strings byte by byte. End spaces are also compared. + + SYNOPSIS + stringcmp() + s First string + t Second string + + NOTE: + Strings are compared as a stream of unsigned chars + + RETURN + < 0 s < t + 0 s == t + > 0 s > t +*/ + + +int stringcmp(const String *s,const String *t) +{ + uint32 s_len=s->length(),t_len=t->length(),len=min(s_len,t_len); + int cmp= memcmp(s->ptr(), t->ptr(), len); + return (cmp) ? cmp : (int) (s_len - t_len); } @@ -668,4 +769,124 @@ String *copy_if_not_alloced(String *to,String *from,uint32 from_length) } +/**************************************************************************** + Help functions +****************************************************************************/ +/* + copy a string from one character set to another + + SYNOPSIS + copy_and_convert() + to Store result here + to_cs Character set of result string + from Copy from here + from_length Length of from string + from_cs From character set + + NOTES + 'to' must be big enough as form_length * to_cs->mbmaxlen + + RETURN + length of bytes copied to 'to' +*/ + + +uint32 +copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, + const char *from, uint32 from_length, CHARSET_INFO *from_cs, + uint *errors) +{ + int cnvres; + my_wc_t wc; + const uchar *from_end= (const uchar*) from+from_length; + char *to_start= to; + uchar *to_end= (uchar*) to+to_length; + int (*mb_wc)(struct charset_info_st *, my_wc_t *, const uchar *, + const uchar *) = from_cs->cset->mb_wc; + int (*wc_mb)(struct charset_info_st *, my_wc_t, uchar *s, uchar *e)= + to_cs->cset->wc_mb; + uint error_count= 0; + + while (1) + { + if ((cnvres= (*mb_wc)(from_cs, &wc, (uchar*) from, + from_end)) > 0) + from+= cnvres; + else if (cnvres == MY_CS_ILSEQ) + { + error_count++; + from++; + wc= '?'; + } + else + break; // Impossible char. + +outp: + if ((cnvres= (*wc_mb)(to_cs, wc, (uchar*) to, to_end)) > 0) + to+= cnvres; + else if (cnvres == MY_CS_ILUNI && wc != '?') + { + error_count++; + wc= '?'; + goto outp; + } + else + break; + } + *errors= error_count; + return (uint32) (to - to_start); +} + + +void String::print(String *str) +{ + char *st= (char*)Ptr, *end= st+str_length; + for (; st < end; st++) + { + uchar c= *st; + switch (c) + { + case '\\': + str->append("\\\\", 2); + break; + case '\0': + str->append("\\0", 2); + break; + case '\'': + str->append("\\'", 2); + break; + case '\n': + str->append("\\n", 2); + break; + case '\r': + str->append("\\r", 2); + break; + case 26: //Ctrl-Z + str->append("\\z", 2); + break; + default: + str->append(c); + } + } +} + + +/* + Exchange state of this object and argument. + + SYNOPSIS + String::swap() + + RETURN + Target string will contain state of this object and vice versa. +*/ + +void String::swap(String &s) +{ + swap_variables(char *, Ptr, s.Ptr); + swap_variables(uint32, str_length, s.str_length); + swap_variables(uint32, Alloced_length, s.Alloced_length); + swap_variables(bool, alloced, s.alloced); + swap_variables(CHARSET_INFO*, str_charset, s.str_charset); +} diff --git a/client/sql_string.h b/client/sql_string.h index fd6d3ef59d9..e284301b214 100644 --- a/client/sql_string.h +++ b/client/sql_string.h @@ -25,9 +25,11 @@ #endif class String; -int sortcmp(const String *a,const String *b); -int stringcmp(const String *a,const String *b); +int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); +uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, + const char *from, uint32 from_length, + CHARSET_INFO *from_cs, uint *errors); class String { @@ -39,12 +41,12 @@ public: String() { Ptr=0; str_length=Alloced_length=0; alloced=0; - str_charset= &my_charset_latin1; + str_charset= &my_charset_bin; } String(uint32 length_arg) { alloced=0; Alloced_length=0; (void) real_alloc(length_arg); - str_charset= &my_charset_latin1; + str_charset= &my_charset_bin; } String(const char *str, CHARSET_INFO *cs) { @@ -67,12 +69,15 @@ public: Alloced_length=str.Alloced_length; alloced=0; str_charset=str.str_charset; } - static void *operator new(size_t size) { return (void*) sql_alloc((uint) size); } - static void operator delete(void *ptr_arg,size_t size) /*lint -e715 */ - { sql_element_free(ptr_arg); } + static void *operator new(size_t size, MEM_ROOT *mem_root) + { return (void*) alloc_root(mem_root, (uint) size); } + static void operator delete(void *ptr_arg,size_t size) + { TRASH(ptr_arg, size); } + static void operator delete(void *ptr_arg, MEM_ROOT *mem_root) + { /* never called */ } ~String() { free(); } - inline void set_charset(CHARSET_INFO *charset) { str_charset=charset; } + inline void set_charset(CHARSET_INFO *charset) { str_charset= charset; } inline CHARSET_INFO *charset() const { return str_charset; } inline uint32 length() const { return str_length;} inline uint32 alloced_length() const { return Alloced_length;} @@ -103,6 +108,7 @@ public: void set(String &str,uint32 offset,uint32 arg_length) { + DBUG_ASSERT(&str != this); free(); Ptr=(char*) str.ptr()+offset; str_length=arg_length; alloced=0; if (str.Alloced_length) @@ -123,6 +129,7 @@ public: Ptr=(char*) str; str_length=arg_length; Alloced_length=0 ; alloced=0; str_charset=cs; } + bool set_ascii(const char *str, uint32 arg_length); inline void set_quick(char *str,uint32 arg_length, CHARSET_INFO *cs) { if (!alloced) @@ -134,6 +141,34 @@ public: bool set(longlong num, CHARSET_INFO *cs); bool set(ulonglong num, CHARSET_INFO *cs); bool set(double num,uint decimals, CHARSET_INFO *cs); + + /* + PMG 2004.11.12 + This is a method that works the same as perl's "chop". It simply + drops the last character of a string. This is useful in the case + of the federated storage handler where I'm building a unknown + number, list of values and fields to be used in a sql insert + statement to be run on the remote server, and have a comma after each. + When the list is complete, I "chop" off the trailing comma + + ex. + String stringobj; + stringobj.append("VALUES ('foo', 'fi', 'fo',"); + stringobj.chop(); + stringobj.append(")"); + + In this case, the value of string was: + + VALUES ('foo', 'fi', 'fo', + VALUES ('foo', 'fi', 'fo' + VALUES ('foo', 'fi', 'fo') + + */ + inline void chop() + { + Ptr[str_length--]= '\0'; + } + inline void free() { if (alloced) @@ -175,6 +210,11 @@ public: { if (&s != this) { + /* + It is forbidden to do assignments like + some_string = substring_of_that_string + */ + DBUG_ASSERT(!s.uses_buffer_owned_by(this)); free(); Ptr=s.Ptr ; str_length=s.str_length ; Alloced_length=s.Alloced_length; alloced=0; @@ -185,13 +225,24 @@ public: bool copy(); // Alloc string if not alloced bool copy(const String &s); // Allocate new string bool copy(const char *s,uint32 arg_length, CHARSET_INFO *cs); // Allocate new string - bool copy(const char*s,uint32 arg_length, CHARSET_INFO *csfrom, CHARSET_INFO *csto); + static bool needs_conversion(uint32 arg_length, + CHARSET_INFO *cs_from, CHARSET_INFO *cs_to, + uint32 *offset); + bool copy_aligned(const char *s, uint32 arg_length, uint32 offset, + CHARSET_INFO *cs); + bool set_or_copy_aligned(const char *s, uint32 arg_length, CHARSET_INFO *cs); + bool copy(const char*s,uint32 arg_length, CHARSET_INFO *csfrom, + CHARSET_INFO *csto, uint *errors); bool append(const String &s); - bool append(const char *s,uint32 arg_length=0); + bool append(const char *s); + bool append(const char *s,uint32 arg_length); + bool append(const char *s,uint32 arg_length, CHARSET_INFO *cs); bool append(IO_CACHE* file, uint32 arg_length); + bool append_with_prefill(const char *s, uint32 arg_length, + uint32 full_length, char fill_char); int strstr(const String &search,uint32 offset=0); // Returns offset to substring or -1 - int strstr_case(const String &s,uint32 offset=0); int strrstr(const String &search,uint32 offset=0); // Returns offset to substring or -1 + bool replace(uint32 offset,uint32 arg_length,const char *to,uint32 length); bool replace(uint32 offset,uint32 arg_length,const String &to); inline bool append(char chr) { @@ -209,9 +260,7 @@ public: } bool fill(uint32 max_length,char fill); void strip_sp(); - inline void caseup() { my_caseup(str_charset,Ptr,str_length); } - inline void casedn() { my_casedn(str_charset,Ptr,str_length); } - friend int sortcmp(const String *a,const String *b); + friend int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); friend int stringcmp(const String *a,const String *b); friend String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); uint32 numchars(); @@ -228,11 +277,11 @@ public: q_*** methods writes values of parameters itself qs_*** methods writes string representation of value */ - void q_append(const char &c) + void q_append(const char c) { Ptr[str_length++] = c; } - void q_append(const uint32 &n) + void q_append(const uint32 n) { int4store(Ptr + str_length, n); str_length += 4; @@ -253,13 +302,53 @@ public: str_length += data_len; } - void WriteAtPosition(int position, uint32 value) + void write_at_position(int position, uint32 value) { int4store(Ptr + position,value); } - void qs_append(const char *str); + void qs_append(const char *str, uint32 len); void qs_append(double d); void qs_append(double *d); - void qs_append(const char &c); + inline void qs_append(const char c) + { + Ptr[str_length]= c; + str_length++; + } + void qs_append(int i); + void qs_append(uint i); + + /* Inline (general) functions used by the protocol functions */ + + inline char *prep_append(uint32 arg_length, uint32 step_alloc) + { + uint32 new_length= arg_length + str_length; + if (new_length > Alloced_length) + { + if (realloc(new_length + step_alloc)) + return 0; + } + uint32 old_length= str_length; + str_length+= arg_length; + return Ptr+ old_length; /* Area to use */ + } + + inline bool append(const char *s, uint32 arg_length, uint32 step_alloc) + { + uint32 new_length= arg_length + str_length; + if (new_length > Alloced_length && realloc(new_length + step_alloc)) + return TRUE; + memcpy(Ptr+str_length, s, arg_length); + str_length+= arg_length; + return FALSE; + } + void print(String *print); + + /* Swap two string objects. Efficient way to exchange data without memcpy. */ + void swap(String &s); + + inline bool uses_buffer_owned_by(const String *s) const + { + return (s->alloced && Ptr >= s->Ptr && Ptr < s->Ptr + s->str_length); + } }; |