diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/Makefile.am | 31 | ||||
-rw-r--r-- | client/client_priv.h | 9 | ||||
-rw-r--r-- | client/mysql.cc | 330 | ||||
-rw-r--r-- | client/mysqladmin.cc | 6 | ||||
-rw-r--r-- | client/mysqlbinlog.cc | 635 | ||||
-rw-r--r-- | client/mysqlcheck.c | 4 | ||||
-rw-r--r-- | client/mysqldump.c | 980 | ||||
-rw-r--r-- | client/mysqlimport.c | 1 | ||||
-rw-r--r-- | client/mysqlshow.c | 138 | ||||
-rw-r--r-- | client/mysqltest.c | 920 | ||||
-rw-r--r-- | client/sql_string.cc | 649 | ||||
-rw-r--r-- | client/sql_string.h | 127 |
12 files changed, 2794 insertions, 1036 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index 1e8851fb3b9..d47e9f98f5e 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -16,6 +16,11 @@ # 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) @@ -23,18 +28,26 @@ 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 \ + 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 $(yassl_dummy_link_fix) +mysqlimport_SOURCES= mysqlimport.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 @@ -43,7 +56,11 @@ link_sources: for f in $(sql_src) ; do \ rm -f $(srcdir)/$$f; \ @LN_CP_F@ $(top_srcdir)/sql/$$f $(srcdir)/$$f; \ - done; + done; \ + for f in $(strings_src) ; do \ + rm -f $(srcdir)/$$f; \ + @LN_CP_F@ $(top_srcdir)/strings/$$f $(srcdir)/$$f; \ + done; # Don't update the files from bitkeeper %::SCCS/s.% diff --git a/client/client_priv.h b/client/client_priv.h index 37ed407de68..a9d5364df49 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 }; diff --git a/client/mysql.cc b/client/mysql.cc index d408e8a5423..bf417e73e22 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"}; @@ -191,14 +195,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_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 +237,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,6 +268,10 @@ 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." }, + { "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, ""}, @@ -311,7 +320,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); @@ -320,6 +329,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); @@ -330,16 +340,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"); @@ -423,7 +432,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*/ @@ -442,6 +451,13 @@ int main(int argc,char *argv[]) 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 @@ -452,7 +468,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); @@ -470,7 +486,8 @@ sig_handler mysql_end(int sig) /* 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); @@ -485,6 +502,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)); @@ -610,7 +628,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, @@ -685,6 +703,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} }; @@ -936,7 +957,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]; @@ -956,7 +977,7 @@ static int read_lines(bool execute_commands) for (;;) { - if (status.batch || !execute_commands) + if (!interactive) { line=batch_readline(status.line_buff); line_number++; @@ -989,11 +1010,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 @@ -1049,7 +1071,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()) && !in_string && (com=find_command(line,0))) { if ((*com->func)(&glob_buffer,line) > 0) @@ -1057,7 +1079,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; @@ -1067,7 +1089,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()) @@ -1089,10 +1111,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) { @@ -1103,8 +1127,17 @@ 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); @@ -1120,15 +1153,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); } @@ -1138,15 +1174,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++) @@ -1155,18 +1193,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 @@ -1187,7 +1225,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++ ; @@ -1205,29 +1243,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; @@ -1252,6 +1301,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 @@ -1261,7 +1311,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()) @@ -1271,9 +1328,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); } /***************************************************************** @@ -1679,11 +1736,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; @@ -1939,7 +1997,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"); } @@ -1994,6 +2052,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(); @@ -2053,6 +2118,7 @@ static void end_tee() return; } + static int com_ego(String *buffer,char *line) { @@ -2064,18 +2130,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); } @@ -2093,6 +2236,8 @@ print_table_data(MYSQL_RES *result) if (info_flag) { print_field_types(result); + if (!mysql_num_rows(result)) + return; mysql_field_seek(result,0); } separator.copy("+",1,charset_info); @@ -2210,13 +2355,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); } @@ -2255,13 +2398,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() { - int x; - for (x= 0; array[x]; x+= 2) - if (*array[x] == key) - return array[x + 1]; + 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) +{ + for (; *array; array+= 2) + if (**array == key) + return array[1]; return 0; } @@ -2665,7 +2836,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); @@ -2750,6 +2921,23 @@ com_use(String *buffer __attribute__((unused)), char *line) 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 @@ -2873,6 +3061,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); diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index da790bce375..ec136402bd8 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -431,6 +431,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); @@ -726,7 +727,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 +1346,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 802d5081ad6..6cba3c0cb44 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,6 +62,7 @@ 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; @@ -66,6 +84,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); @@ -78,6 +104,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; /* @@ -119,7 +161,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); } @@ -136,33 +178,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); }; @@ -240,22 +340,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); @@ -263,7 +383,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) { @@ -271,9 +391,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; @@ -281,19 +413,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))) @@ -305,8 +453,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); @@ -316,30 +464,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)) { @@ -348,32 +524,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 @@ -381,8 +554,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 @@ -391,23 +566,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; } @@ -416,8 +591,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); } } @@ -436,6 +650,15 @@ static struct my_option my_long_options[] = {"auto-close", 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}, @@ -455,6 +678,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, @@ -570,7 +796,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); } @@ -596,7 +822,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) @@ -711,6 +937,7 @@ static MYSQL* safe_connect() mysql_close(local_mysql); die("failed on connect: %s", errmsg); } + local_mysql->reconnect= 1; return local_mysql; } @@ -722,12 +949,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))) @@ -753,11 +985,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'", @@ -767,18 +1006,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]; @@ -791,7 +1030,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 @@ -820,6 +1070,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) { @@ -832,9 +1084,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; @@ -842,25 +1094,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) { @@ -883,7 +1137,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; @@ -895,64 +1149,135 @@ 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) + { + my_b_seek(file, tmp_pos); /* seek back to event's start */ + if (!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); + } + else + break; } } my_b_seek(file, pos); - DBUG_RETURN(old_format); } @@ -960,13 +1285,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) @@ -975,65 +1297,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; @@ -1045,6 +1369,7 @@ end: if (fd >= 0) my_close(fd, MYF(MY_WME)); end_io_cache(file); + delete description_event; return error; } @@ -1095,6 +1420,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"); + for (save_stop_position= stop_position, stop_position= ~(my_off_t)0 ; (--argc >= 0) && !stop_passed ; ) { @@ -1109,6 +1442,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"); @@ -1130,9 +1470,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 2a2ae1311c9..2eb3e55c2e9 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -497,6 +497,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"); @@ -643,6 +646,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 3458f74d8a2..b92b971ea4f 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -30,14 +30,14 @@ ** 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> @@ -70,6 +70,11 @@ /* 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); static ulong find_set(TYPELIB *lib, const char *x, uint length, @@ -86,7 +91,8 @@ static my_bool verbose=0,tFlag=0,dFlag=0,quick= 1, extended_insert= 1, 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_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 +127,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"; +/* do we met VIEWs during tables scaning */ +my_bool was_views= 0; const char *compatible_mode_names[]= { @@ -206,7 +214,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, @@ -334,6 +342,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 +382,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 +401,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 }; @@ -400,8 +417,10 @@ 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> @@ -418,6 +437,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); } } @@ -492,6 +512,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,"\ @@ -518,6 +545,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) { @@ -778,16 +808,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); safe_exit(EX_MYSQLERR); DBUG_VOID_RETURN; -} /* DBerror */ +} /* DB_error */ /* @@ -797,7 +827,8 @@ 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 @@ -863,7 +894,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; } /* @@ -884,6 +915,20 @@ static int dbConnect(char *host, char *user,char *passwd) 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 */ @@ -930,6 +975,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; @@ -1126,7 +1187,7 @@ static void print_xml_row(FILE *xml_file, const char *row_name, 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 +1207,165 @@ 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])) + { + if (opt_drop) + fprintf(sql_file, "/*!50003 DROP %s IF EXISTS %s */;;\n", + routine_type[i], routine_name); + /* + 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", row[2]); + fprintf(sql_file, + "/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE*/" + ";;\n"); + } + } /* 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) + 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 ie "InnoDB" + 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; + 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 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 +1374,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 +1390,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)) @@ -1237,15 +1424,82 @@ static uint getTableStructure(char *table, char* db) check_io(sql_file); } - tableRes=mysql_store_result(sock); - row=mysql_fetch_row(tableRes); + result= mysql_store_result(sock); + field= mysql_fetch_field_direct(result, 0); + if (strcmp(field->name, "View") == 0) + { + if (verbose) + fprintf(stderr, "-- It's a view, create dummy table for view\n"); + + 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)) + { + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); + } + + if ((result= mysql_store_result(sock))) + { + if (mysql_num_rows(result)) + { + if (opt_drop) + { + fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n", + opt_quoted_table); + fprintf(sql_file, "/*!50001 DROP TABLE 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); + + was_views= 1; + DBUG_RETURN(0); + } + + 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)) + if (mysql_query_with_error_report(sock, &result, query_buff)) { if (path) my_fclose(sql_file, MYF(MY_WME)); @@ -1253,35 +1507,45 @@ static uint getTableStructure(char *table, char* db) 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) - { - dynstr_append_mem(&insert_pat, " (", 2); - } - else + /* + 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, " 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 { @@ -1292,7 +1556,7 @@ static uint getTableStructure(char *table, char* db) my_snprintf(query_buff, sizeof(query_buff), "show fields from %s", result_table); - if (mysql_query_with_error_report(sock, &tableRes, query_buff)) + if (mysql_query_with_error_report(sock, &result, query_buff)) { safe_exit(EX_MYSQLERR); DBUG_RETURN(0); @@ -1318,7 +1582,7 @@ static uint getTableStructure(char *table, char* db) 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); else @@ -1326,24 +1590,25 @@ static uint getTableStructure(char *table, char* db) 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) @@ -1351,7 +1616,7 @@ static uint getTableStructure(char *table, char* db) fputs(",\n",sql_file); check_io(sql_file); } - if (opt_complete_insert) + if (complete_insert) dynstr_append_mem(&insert_pat, ", ", 2); } init=1; @@ -1362,7 +1627,7 @@ static uint getTableStructure(char *table, char* db) { if (opt_xml) { - print_xml_row(sql_file, "field", tableRes, &row); + print_xml_row(sql_file, "field", result, &row); continue; } @@ -1386,16 +1651,24 @@ static uint getTableStructure(char *table, char* db) 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)); safe_exit(EX_MYSQLERR); @@ -1405,7 +1678,7 @@ 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) { @@ -1421,16 +1694,16 @@ static uint getTableStructure(char *table, char* db) } } } - 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); + print_xml_row(sql_file, "key", result, &row); continue; } - + if (atoi(row[3]) == 1) { if (keynr++) @@ -1462,14 +1735,13 @@ static uint getTableStructure(char *table, char* db) /* 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)); my_snprintf(buff, sizeof(buff), "show table status like %s", 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 */ @@ -1479,7 +1751,7 @@ static uint getTableStructure(char *table, char* db) 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", @@ -1488,21 +1760,20 @@ static uint getTableStructure(char *table, char* db) else { if (opt_xml) - { - print_xml_row(sql_file, "options", tableRes, &row); - } + print_xml_row(sql_file, "options", result, &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); + 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); else @@ -1522,9 +1793,74 @@ 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 TRIGGER %s %s %s ON %s FOR EACH ROW%s */;;\n\n", + row[6], /* sql_mode */ + quote_name(row[0], name_buff, 0), /* Trigger */ + row[4], /* Timing */ + row[1], /* Event */ + result_table, + 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) @@ -1547,10 +1883,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) @@ -1589,34 +1925,81 @@ static char *alloc_query_str(ulong size) 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; + int error= 0; + ulong rownr, row_break, total_length, init_length; + uint num_fields; MYSQL_RES *res; MYSQL_FIELD *field; MYSQL_ROW row; - ulong rownr, row_break, total_length, init_length; - const char *table_type; - int error= 0; + 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); + + /* Check --no-data flag */ + if (dFlag) + { + if (verbose) + fprintf(stderr, + "-- Skipping dump data for table '%s', --no-data was used\n", + table); + DBUG_VOID_RETURN; + } - /* Check table type */ - if ((table_type= check_if_ignore_table(table))) + 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, - "-- Skipping data for table '%s' because it's of type %s\n", + "-- Warning: Skipping data for table '%s' because it's of type %s\n", table, table_type); - return; + 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"); if (path) @@ -1658,8 +2041,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 @@ -1705,16 +2088,16 @@ 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"); 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); @@ -1747,10 +2130,10 @@ 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) { @@ -1777,19 +2160,21 @@ static void dumpTable(uint numFields, char *table) 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; + (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) { ulong length = lengths[i]; @@ -2010,15 +2395,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) @@ -2058,6 +2443,21 @@ static int dump_all_databases() if (dump_all_tables_in_db(row[0])) result=1; } + if (was_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 */ @@ -2066,20 +2466,33 @@ 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 && was_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"); + DB_error(sock, "when selecting the database"); return 1; /* If --force */ } if (!path && !opt_xml) @@ -2175,14 +2588,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))) @@ -2190,13 +2603,20 @@ 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); @@ -2209,6 +2629,61 @@ static int dump_all_tables_in_db(char *database) /* + 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 (init_dumping(database)) + return 1; + 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(sock,"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 @@ -2223,12 +2698,11 @@ static int get_actual_table_name(const char *old_table_name, int buf_size) { int retval; - 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)); @@ -2240,32 +2714,35 @@ static int get_actual_table_name(const char *old_table_name, safe_exit(EX_MYSQLERR); } - tableRes= mysql_store_result( sock ); retval = 1; - 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 ); + /* + Return first row + TODO: Return all matching rows + */ + row= mysql_fetch_row(table_res); strmake(new_table_name, row[0], buf_size-1); retval= 0; - DBUG_PRINT("info", ("new_table_name: %s", new_table_name)); } - mysql_free_result(tableRes); + mysql_free_result(table_res); } - DBUG_PRINT("exit", ("retval: %d", retval)); - DBUG_RETURN(retval); + return retval; } static int dump_selected_tables(char *db, char **table_names, int tables) { - uint numrows, i; + uint i; char table_buff[NAME_LEN*+3]; char new_table_name[NAME_LEN]; DYNAMIC_STRING lock_tables_query; HASH dump_tables; + char *table_name; DBUG_ENTER("dump_selected_tables"); if (init_dumping(db)) @@ -2273,17 +2750,16 @@ static int dump_selected_tables(char *db, char **table_names, int tables) /* Init hash table for storing the actual name of tables to dump */ if (hash_init(&dump_tables, charset_info, 16, 0, 0, - (hash_get_key) get_table_key, (hash_free_key) free_table_ent, + (hash_get_key) get_table_key, (hash_free_key) free_table_ent, 0)) 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 (!get_actual_table_name(*table_names, - new_table_name, sizeof(new_table_name) )) + new_table_name, sizeof(new_table_name))) { /* Add found table name to lock_tables_query */ if (lock_tables) @@ -2312,27 +2788,45 @@ static int dump_selected_tables(char *db, char **table_names, int 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 (i= 0 ; i < dump_tables.records ; i++) + for (i= 0; i < dump_tables.records; i++) { - const char *table_name= hash_element(&dump_tables, i); + table_name= hash_element(&dump_tables, i); DBUG_PRINT("info",("Dumping table %s", table_name)); - numrows= getTableStructure((char*) table_name, db); - if (!dFlag && numrows > 0) - dumpTable(numrows, (char*) table_name); + dump_table(table_name,db); + if (opt_dump_triggers && + mysql_get_server_version(sock) >= 50009) + dump_triggers_for_table(table_name, db); + } + + /* Dump each selected view */ + if (was_views) + { + for(i=0; i < dump_tables.records; i++) + { + table_name= hash_element(&dump_tables, i); + get_view_structure(table_name, db); + } + } + /* obtain dump of routines (procs/functions) */ + if (opt_routines && !opt_xml && + mysql_get_server_version(sock) >= 50009) + { + DBUG_PRINT("info", ("Dumping routines for database %s", db)); + dump_routines_for_db(db); } hash_free(&dump_tables); my_free(order_by, MYF(MY_ALLOW_ZERO_PTR)); @@ -2511,28 +3005,37 @@ static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, /* - 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_type Type of table GLOBAL VARIABLES 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)); @@ -2546,7 +3049,7 @@ static const char *check_if_ignore_table(const char *table_name) fprintf(stderr, "-- Warning: Couldn't get status information for table %s (%s)\n", table_name,mysql_error(sock)); - return 0; /* assume table is ok */ + DBUG_RETURN(result); /* assume table is ok */ } } if (!(row= mysql_fetch_row(res))) @@ -2555,13 +3058,38 @@ static const char *check_if_ignore_table(const char *table_name) "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 */ + 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; } - if (strcmp(row[1], (result= "MRG_MyISAM")) && - strcmp(row[1], (result= "MRG_ISAM"))) - result= 0; mysql_free_result(res); - return result; + DBUG_RETURN(result); } /* @@ -2581,6 +3109,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; @@ -2617,11 +3146,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; } @@ -2640,13 +3171,114 @@ cleanup: } +/* + 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 buff[20+FN_REFLEN]; + FILE *sql_file = md_result_file; + DBUG_ENTER("get_view_structure"); + + if (tFlag) + 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); + + sprintf(buff,"show create table %s", result_table); + if (mysql_query(sock, buff)) + { + fprintf(stderr, "%s: Can't get CREATE TABLE for view %s (%s)\n", + my_progname, result_table, mysql_error(sock)); + safe_exit(EX_MYSQLERR); + DBUG_RETURN(0); + } + + 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 */ + { + safe_exit(EX_MYSQLERR); + DBUG_RETURN(1); + } + write_header(sql_file, db); + } + table_res= mysql_store_result(sock); + 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 (!opt_xml && opt_comments) + { + fprintf(sql_file, "\n--\n-- 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); + } + + row= mysql_fetch_row(table_res); + fprintf(sql_file, "/*!50001 %s*/;\n", row[1]); + check_io(sql_file); + mysql_free_result(table_res); + + 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 32e2a7ccffe..19d4ef16ef9 100644 --- a/client/mysqlimport.c +++ b/client/mysqlimport.c @@ -398,6 +398,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 44fc9448782..8f6be6cf70b 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; @@ -125,6 +125,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 +166,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}, @@ -196,6 +201,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 +320,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 +342,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 +372,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 +381,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 +439,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 +450,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 +480,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 +523,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 +546,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 +588,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 +614,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 +625,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 +634,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 +659,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 +695,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 35408368a73..6653d24e575 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -70,7 +70,8 @@ # 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 @@ -136,6 +137,18 @@ 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; @@ -147,6 +160,7 @@ const char *user = 0, *host = 0, *unix_sock = 0, *opt_basedir="./"; static int port = 0; 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 int parsing_disabled= 0; static uint start_lineno, *lineno; const char *manager_user="root",*manager_host=0; char *manager_pass=0; @@ -173,6 +187,17 @@ 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 */ @@ -194,7 +219,8 @@ static int ps_match_re(char *); static char *ps_eprint(int); static void ps_free_reg(void); -static const char *embedded_server_groups[] = { +static const char *embedded_server_groups[]= +{ "server", "embedded", "mysqltest_SERVER", @@ -249,6 +275,7 @@ typedef struct static char *subst_env_var(const char *cmd); static FILE *my_popen(const char *cmd, const char *mode); +#undef popen #define popen(A,B) my_popen((A),(B)) #endif /* __NETWARE__ */ @@ -256,6 +283,7 @@ 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; @@ -286,6 +314,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 +326,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. */ @@ -365,6 +395,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", @@ -386,19 +418,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}; @@ -466,6 +490,10 @@ 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 void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); +static int 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 int handle_no_error(struct st_query *q); static void do_eval(DYNAMIC_STRING* query_eval, const char *query) @@ -527,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) @@ -663,6 +691,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)))) @@ -708,8 +737,8 @@ err: static int 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(); @@ -718,18 +747,16 @@ static int check_result(DYNAMIC_STRING* ds, const char *fname, break; /* ok */ case RESULT_LENGTH_MISMATCH: verbose_msg("Result length mismatch"); - error= RESULT_LENGTH_MISMATCH; break; case RESULT_CONTENT_MISMATCH: verbose_msg("Result content mismatch"); - error= RESULT_CONTENT_MISMATCH; break; default: /* impossible */ die("Unknown error code from dyn_string_cmp()"); } - if (error) + if (res != RESULT_OK) reject_dump(fname, ds->str, ds->length); - return error; + DBUG_RETURN(res); } @@ -1052,7 +1079,7 @@ static void do_exec(struct st_query *query) ds= &ds_res; while (fgets(buf, sizeof(buf), res_file)) - replace_dynstr_append_mem(ds, buf, strlen(buf)); + replace_dynstr_append(ds, buf); } error= pclose(res_file); if (error != 0) @@ -1067,6 +1094,8 @@ static void do_exec(struct st_query *query) ("error: %d, status: %d", error, status)); for (i= 0; i < query->expected_errors; i++) { + DBUG_PRINT("info", + ("error: %d, status: %d", error, status)); DBUG_PRINT("info", ("expected error: %d", query->expected_errno[i].code.errnum)); if ((query->expected_errno[i].type == ERR_ERRNO) && @@ -1255,7 +1284,7 @@ int do_modify_var(struct st_query *query, const char *name, if (*p != '$') die("First argument to %s must be a variable (start with $)", name); v= var_get(p, &p, 1, 0); - switch (operator){ + switch (operator) { case DO_DEC: v->int_val--; break; @@ -1608,6 +1637,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) @@ -1618,19 +1648,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; } @@ -1900,26 +1952,171 @@ void init_manager() } #endif + +/* + 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 + This function will try to connect to the given server MAX_CON_TRIES + times and sleep CON_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* con, 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; int i; - for (i = 0; i < MAX_CON_TRIES; ++i) + for (i= 0; i < MAX_CON_TRIES; ++i) { if (mysql_real_connect(con, host,user, pass, db, port, sock, - CLIENT_MULTI_STATEMENTS)) + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { - con_error = 0; + con_error= 0; break; } sleep(CON_RETRY_SLEEP); } + /* + TODO: change this to 0 in future versions, but the 'kill' test relies on + existing behavior + */ + mysql_options(con, MYSQL_OPT_RECONNECT, (char *)&reconnect); return con_error; } +/* + Connect to a server and handle connection errors in case when 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_tmp, *ds; + my_bool reconnect= 1; + int error= 0; + + /* + Altough we ignore --require or --result before connect() command we still + need to handle record_file because of "@result_file sql-command" syntax. + */ + if (q->record_file[0]) + { + init_dynamic_string(&ds_tmp, "", 16384, 65536); + ds= &ds_tmp; + } + else + 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)) + { + error= handle_error("connect", q, mysql_errno(con), mysql_error(con), + mysql_sqlstate(con), ds); + *create_conn= 0; + goto err; + } + else if (handle_no_error(q)) + { + /* + Fail if there was no error but we expected it. + We also don't want to have connection in this case. + */ + mysql_close(con); + *create_conn= 0; + error= 1; + goto err; + } + + /* + TODO: change this to 0 in future versions, but the 'kill' test relies on + existing behavior + */ + mysql_options(con, MYSQL_OPT_RECONNECT, (char *)&reconnect); + + 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); + +err: + free_replace(); + if (ds == &ds_tmp) + dynstr_free(&ds_tmp); + return error; +} + + int do_connect(struct st_query *q) { char *con_name, *con_user,*con_pass, *con_host, *con_port_str, @@ -1928,6 +2125,8 @@ int do_connect(struct st_query *q) char buff[FN_REFLEN]; int con_port; int free_con_sock= 0; + int error= 0; + int create_conn= 1; DBUG_ENTER("do_connect"); DBUG_PRINT("enter",("connect: %s",p)); @@ -1993,18 +2192,27 @@ 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': %s", con_name, + 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); + DBUG_RETURN(error); } @@ -2019,7 +2227,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--; @@ -2035,12 +2243,12 @@ int do_done(struct st_query *q) } -int do_block(enum enum_commands cmd, struct st_query *q) +int 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"); /* Check stack overflow */ if (cur_block == block_stack_end) @@ -2162,12 +2370,13 @@ 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; for (; p < buf_end ;) @@ -2196,6 +2405,7 @@ int read_line(char *buf, int size) start_lineno= *(lineno+1); die("Missing end of block"); } + DBUG_PRINT("info", ("end of file")); DBUG_RETURN(1); } cur_file--; @@ -2214,10 +2424,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; @@ -2232,7 +2443,7 @@ 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 ((*lineno == start_lineno) && (c == '#' || c == '-' || parsing_disabled)) { state = R_COMMENT; } @@ -2253,55 +2464,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) @@ -2388,7 +2580,6 @@ int read_query(struct st_query** q_ptr) q->query_buf= q->query= 0; if (read_line(read_query_buf, sizeof(read_query_buf))) { - DBUG_PRINT("warning",("too long query")); DBUG_RETURN(1); } DBUG_PRINT("info", ("query: %s", read_query_buf)); @@ -2398,16 +2589,20 @@ 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++ ; @@ -2539,6 +2734,8 @@ void usage() #include <help_end.h> +#include <help_end.h> + static my_bool get_one_option(int optid, const struct my_option *opt __attribute__((unused)), @@ -2706,6 +2903,13 @@ static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, 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 all results to the dynamic string separated with '\t' @@ -2741,13 +2945,13 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) { if (i) dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); + replace_dynstr_append_mem(ds, val, (int)len); } else { dynstr_append(ds, fields[i].name); dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); + replace_dynstr_append_mem(ds, val, (int)len); dynstr_append_mem(ds, "\n", 1); } } @@ -2767,8 +2971,6 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) 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); @@ -2852,73 +3054,9 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) (!(last_result= res= mysql_store_result(mysql)) && mysql_field_count(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); + if (handle_error(query, q, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds)) 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 - */ - goto end; - - } - - if (handle_no_error(q)) - { - error= 1; goto end; } @@ -2941,8 +3079,7 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) { if (i) dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[i].name, - strlen(field[i].name)); + replace_dynstr_append(ds, field[i].name); } dynstr_append_mem(ds, "\n", 1); } @@ -2956,18 +3093,20 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) if (!disable_info) affected_rows= (ulong)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_warning_count(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) - verbose_msg("Warning count is %u but didn't get any warnings\n", - count); + die("Warning count is %u but didn't get any warnings", count); else { dynstr_append_mem(ds, "Warnings:\n", 10); @@ -2998,15 +3137,28 @@ static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) } else if (q->record_file[0]) { - error = check_result(ds, q->record_file, q->require_file); + 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 */ + if (handle_error(query, q, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds)) + error= 1; + goto end; + } + + /* If we come here the query is both executed and read successfully */ + if (handle_no_error(q)) + { + error= 1; + goto end; + } end: free_replace(); @@ -3026,6 +3178,132 @@ end: } +/* + Handle errors which occurred after 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 + 0 - OK + 1 - Some other error was expected. +*/ + +static int 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) + 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 (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_RETURN(0); + } + } + + DBUG_PRINT("info",("i: %d expected_errors: %d", i, q->expected_errors)); + + 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 instead of %d...", + q->query, err_errno, q->expected_errno[0].code.errnum); + else + die("query '%s' failed with wrong sqlstate %s instead of %s...", + q->query, err_sqlstate, q->expected_errno[0].code.sqlstate); + DBUG_RETURN(1); + } + + /* + If we do not abort on error, failure to run the query does not fail the + whole test case. + */ + verbose_msg("query '%s' failed: %d: %s", q->query, err_errno, + err_error); + DBUG_RETURN(0); +} + + +/* + Handle absence of errors after execution + + SYNOPSIS + handle_no_error() + q - context of query + + RETURN VALUE + 0 - OK + 1 - Some error was expected from this query. +*/ + +static int handle_no_error(struct st_query *q) +{ + DBUG_ENTER("handle_no_error"); + + if (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0) + { + /* 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); + DBUG_RETURN(1); + } + else if (q->expected_errno[0].type == ERR_SQLSTATE && + strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) + { + /* 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_RETURN(1); + } + + DBUG_RETURN(0); +} + /****************************************************************************\ * If --ps-protocol run ordinary statements using prepared statemnt C API \****************************************************************************/ @@ -3039,8 +3317,8 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) { 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; + int query_len; + ulonglong num_rows; char *query; MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ DYNAMIC_STRING *ds; @@ -3055,7 +3333,7 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) */ if (!(stmt= mysql_stmt_init(mysql))) die("unable init stmt structure"); - + if (q->type != Q_EVAL) { query= q->query; @@ -3096,27 +3374,21 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) if (err != 0) { - 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; - } + /* + Preparing is part of normal execution and some errors may be expected + */ + if (handle_error(query, q, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds)) + error= 1; + 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); + if (!disable_ps_warnings) + run_query_stmt_handle_warnings(mysql, ds); /* - 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 @@ -3125,22 +3397,13 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) prepared statement. */ - if ((got_error_on_execute= mysql_stmt_execute(stmt)) != 0) /* 0 == Success */ + if (mysql_stmt_execute(stmt) != 0) /* 0 == Success */ { - 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; - } + /* We got an error, maybe expected */ + if (handle_error(query, q, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds)) + error= 1; + goto end; } /* @@ -3150,11 +3413,10 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) */ { my_bool one= 1; - if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, - (void*) &one) != 0) + if ((err= 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); + "'STMT_ATTR_UPDATE_MAX_LENGTH' err: %d", err); } /* @@ -3163,20 +3425,11 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) */ if ((err= mysql_stmt_store_result(stmt)) != 0) { - 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; - } + /* We got an error, maybe expected */ + if(handle_error(query, q, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds)) + error = 1; + goto end; } /* If we got here the statement was both executed and read succeesfully */ @@ -3200,8 +3453,6 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) MYSQL_FIELD *field= mysql_fetch_fields(res); uint num_fields= mysql_num_fields(res); - /* FIXME check error from the above? */ - if (display_metadata) run_query_display_metadata(field, num_fields, ds); @@ -3213,8 +3464,7 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) { if (col_idx) dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, field[col_idx].name, - strlen(field[col_idx].name)); + replace_dynstr_append(ds, field[col_idx].name); } dynstr_append_mem(ds, "\n", 1); } @@ -3230,7 +3480,7 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) /* Allocate array with bind structs, lengths and NULL flags */ bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), - MYF(MY_WME | MY_FAE)); + MYF(MY_WME | MY_FAE | MY_ZEROFILL)); 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), @@ -3289,13 +3539,13 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) { if (col_idx) /* No tab before first col */ dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); + replace_dynstr_append_mem(ds, val, (int)len); } else { dynstr_append(ds, field[col_idx].name); dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, len); + replace_dynstr_append_mem(ds, val, (int)len); dynstr_append_mem(ds, "\n", 1); } } @@ -3355,9 +3605,6 @@ static int run_query_stmt(MYSQL *mysql, struct st_query *q, int flags) 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; @@ -3437,12 +3684,19 @@ static void run_query_stmt_handle_warnings(MYSQL *mysql, DYNAMIC_STRING *ds) if (!disable_warnings && (count= mysql_warning_count(mysql))) { + /* + 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) == 0) { MYSQL_RES *warn_res= mysql_store_result(mysql); if (!warn_res) - verbose_msg("Warning count is %u but didn't get any warnings\n", - count); + die("Warning count is %u but didn't get any warnings", + count); else { dynstr_append_mem(ds, "Warnings:\n", 10); @@ -3455,118 +3709,6 @@ 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) -{ - if (q->require_file) /* FIXME don't understand this one */ - { - abort_not_supported_test(); - } - - if (q->abort_on_error) - die("query '%s' failed: %d: %s", query, - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - else - { - int i; - - for (i=0 ; (uint) i < q->expected_errors ; i++) - { - 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 */ - } - } - 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) - { - 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 */ - } - verbose_msg("query '%s' failed: %d: %s", q->query, mysql_stmt_errno(stmt), - mysql_stmt_error(stmt)); - /* - if we do not abort on error, failure to run the query does - not fail the whole test case - */ - return 0; - } - - return 0; -} - - -/* - Handle absence of errors after execution - - SYNOPSIS - handle_no_error() - q - context of query - - RETURN VALUE - 0 - OK - 1 - Some error was expected from this query. -*/ - -static int handle_no_error(struct st_query *q) -{ - DBUG_ENTER("handle_no_error"); - - if (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0) - { - /* 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); - } - else if (q->expected_errno[0].type == ERR_SQLSTATE && - strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) - { - /* 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); - } - - DBUG_RETURN(0); -} - /****************************************************************************\ * Functions to match SQL statements that can be prepared \****************************************************************************/ @@ -3641,20 +3783,45 @@ 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-queries and --disable-queries + */ + 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->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; } @@ -3730,8 +3897,8 @@ static void init_var_hash(MYSQL *mysql) 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); @@ -3746,13 +3913,11 @@ 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(); @@ -3776,10 +3941,12 @@ int main(int argc, char **argv) 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; 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)) @@ -3837,7 +4004,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: + error|= do_connect(q); + break; case Q_CONNECTION: select_connection(q); break; case Q_DISCONNECT: case Q_DIRTY_CLOSE: @@ -3853,6 +4022,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; @@ -4025,25 +4196,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 accidently + 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; } @@ -4107,7 +4298,6 @@ int main(int argc, char **argv) my_end(MY_CHECK_ERROR); exit(error ? 1 : 0); return error ? 1 : 0; /* Keep compiler happy */ - } } 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); + } }; |