diff options
author | unknown <monty@mysql.com> | 2004-10-29 19:26:52 +0300 |
---|---|---|
committer | unknown <monty@mysql.com> | 2004-10-29 19:26:52 +0300 |
commit | f095274fe8c3d3394d6c0ce0a68f4bea04311999 (patch) | |
tree | 23bcc9a71fe7237887a111b158e30f5a6bb665d3 /client | |
parent | f41bba8c6156a7adf4c67dfa75e16112767a5d3c (diff) | |
parent | 5be6c328f5a9f78f37176bbbd88a538fa3b65fe9 (diff) | |
download | mariadb-git-f095274fe8c3d3394d6c0ce0a68f4bea04311999.tar.gz |
merge with 4.1
BitKeeper/etc/ignore:
auto-union
BitKeeper/etc/logging_ok:
auto-union
BitKeeper/triggers/post-commit:
Auto merged
Docs/Support/texi2html:
Auto merged
Makefile.am:
Auto merged
client/Makefile.am:
Auto merged
client/mysql.cc:
Auto merged
client/mysqldump.c:
Auto merged
include/my_base.h:
Auto merged
include/my_global.h:
Auto merged
include/my_pthread.h:
Auto merged
include/my_sys.h:
Auto merged
include/my_time.h:
Auto merged
include/mysql.h:
Auto merged
include/mysql_com.h:
Auto merged
innobase/buf/buf0buf.c:
Auto merged
innobase/include/row0mysql.h:
Auto merged
innobase/row/row0sel.c:
Auto merged
libmysql/libmysql.c:
Auto merged
libmysqld/examples/Makefile.am:
Auto merged
myisam/mi_check.c:
Auto merged
mysql-test/include/ps_modify.inc:
Auto merged
mysql-test/install_test_db.sh:
Auto merged
mysql-test/r/alter_table.result:
Auto merged
mysql-test/r/auto_increment.result:
Auto merged
mysql-test/r/bdb.result:
Auto merged
mysql-test/r/ctype_latin1_de.result:
Auto merged
mysql-test/r/ctype_recoding.result:
Auto merged
mysql-test/r/fulltext.result:
Auto merged
mysql-test/r/func_gconcat.result:
Auto merged
mysql-test/r/func_group.result:
Auto merged
mysql-test/r/func_if.result:
Auto merged
mysql-test/t/derived.test:
Auto merged
mysql-test/t/insert.test:
merge with 4.1
Fixed test case to not use 'if exists' when it shouldn't
mysql-test/t/range.test:
merge with 4.1
Added missing drop table
sql/ha_ndbcluster.cc:
merge with 4.1
Simple optimization: use max() instead of ? :
sql/item_func.cc:
merge with 4.1
(Added back old variable names for easier merges)
sql/opt_range.cc:
merge with 4.1
Removed argument 'parent_alloc' from QUICK_RANGE_SELECT as this was not used
Added assert if using QUICK_GROUP_MIN_MAX_SELECT with parent_alloc as the init() function can't handle this
Changed back get_quick_select_for_ref() to use it's own alloc root becasue this function may be called several times for one query
sql/sql_handler.cc:
merge with 4.1
change variable 'err' to 'error' as same function had a label named 'err'
sql/sql_update.cc:
Use multi-update code from 5.0 instead of 4.1
We will fix the locking code shortly in 5.0 to be faster than in 4.1
Diffstat (limited to 'client')
-rw-r--r-- | client/Makefile.am | 6 | ||||
-rw-r--r-- | client/client_priv.h | 3 | ||||
-rw-r--r-- | client/mysql.cc | 91 | ||||
-rw-r--r-- | client/mysqladmin.c | 24 | ||||
-rw-r--r-- | client/mysqlcheck.c | 11 | ||||
-rw-r--r-- | client/mysqldump.c | 66 | ||||
-rw-r--r-- | client/mysqlimport.c | 12 | ||||
-rw-r--r-- | client/mysqlshow.c | 3 | ||||
-rw-r--r-- | client/mysqltest.c | 1010 |
9 files changed, 1064 insertions, 162 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index 7400717731d..7fdba8f43d8 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -17,7 +17,8 @@ # This file is public domain and comes with NO WARRANTY of any kind #AUTOMAKE_OPTIONS = nostdinc -INCLUDES = -I$(top_srcdir)/include $(openssl_includes) +INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/regex \ + $(openssl_includes) LIBS = @CLIENT_LIBS@ DEPLIB= ../libmysql/libmysqlclient.la LDADD = @CLIENT_EXTRA_LDFLAGS@ $(DEPLIB) @@ -34,8 +35,9 @@ mysqlcheck_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) mysqlshow_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) mysqldump_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) mysqlimport_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) -mysqltest_SOURCES= mysqltest.c +mysqltest_SOURCES= mysqltest.c ../mysys/my_getsystime.c mysqltest_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) +mysqltest_LDADD = $(LDADD) $(top_builddir)/regex/libregex.a mysqlbinlog_SOURCES = mysqlbinlog.cc ../mysys/mf_tempdir.c mysqlbinlog_DEPENDENCIES= $(LIBRARIES) $(pkglib_LTLIBRARIES) $(DEPLIB) mysqlmanagerc_SOURCES = mysqlmanagerc.c diff --git a/client/client_priv.h b/client/client_priv.h index ad08484b706..f16ec0e802b 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -44,5 +44,6 @@ enum options_client OPT_SHARED_MEMORY_BASE_NAME, OPT_FRM, OPT_SKIP_OPTIMIZATION, 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_START_POSITION, OPT_STOP_POSITION, OPT_START_DATETIME, OPT_STOP_DATETIME, + OPT_SIGINT_IGNORE, OPT_HEXBLOB }; diff --git a/client/mysql.cc b/client/mysql.cc index 0b43f9b80ec..358b13e652b 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -44,7 +44,7 @@ #include <locale.h> #endif -const char *VER= "14.6"; +const char *VER= "14.7"; /* Don't try to make a nice table if the data is too big */ #define MAX_COLUMN_LENGTH 1024 @@ -135,7 +135,7 @@ 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; + default_pager_set= 0, opt_sigint_ignore= 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; @@ -394,7 +394,11 @@ int main(int argc,char *argv[]) } if (!status.batch) ignore_errors=1; // Don't abort monitor - signal(SIGINT, mysql_end); // Catch SIGINT to clean up + + if (opt_sigint_ignore) + signal(SIGINT, SIG_IGN); + else + signal(SIGINT, mysql_end); // Catch SIGINT to clean up signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up /* @@ -573,6 +577,9 @@ static struct my_option my_long_options[] = {"set-variable", 'O', "Change the value of a variable. Please note that this option is deprecated; you can set variables directly with --variable-name=value.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"sigint-ignore", OPT_SIGINT_IGNORE, "Ignore SIGINT (CTRL-C)", + (gptr*) &opt_sigint_ignore, (gptr*) &opt_sigint_ignore, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, {"one-database", 'o', "Only update the default database. This is useful for skipping updates to other database in the update log.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -1964,7 +1971,7 @@ print_field_types(MYSQL_RES *result) MYSQL_FIELD *field; while ((field = mysql_fetch_field(result))) { - tee_fprintf(PAGER,"Catalog: '%s'\nDatabase: '%s'\nTable: '%s'\nName: '%s'\nType: %d\nLength: %d\nMax length: %d\nIs_null: %d\nFlags: %d\nDecimals: %d\n\n", + 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, @@ -2010,7 +2017,8 @@ print_table_data(MYSQL_RES *result) (void) tee_fputs("|", PAGER); for (uint off=0; (field = mysql_fetch_field(result)) ; off++) { - tee_fprintf(PAGER, " %-*s|",min(field->max_length,MAX_COLUMN_LENGTH), + tee_fprintf(PAGER, " %-*s|",(int) min(field->max_length, + MAX_COLUMN_LENGTH), field->name); num_flag[off]= IS_NUM(field->type); } @@ -2602,32 +2610,31 @@ com_use(String *buffer __attribute__((unused)), char *line) put_info("USE must be followed by a database name", INFO_ERROR); return 0; } - - /* - We need to recheck the current database, because it may change - under our feet, for example if DROP DATABASE or RENAME DATABASE - (latter one not yet available by the time the comment was written) + /* + We need to recheck the current database, because it may change + under our feet, for example if DROP DATABASE or RENAME DATABASE + (latter one not yet available by the time the comment was written) */ - current_db= 0; // Let's reset current_db, assume it's gone - /* - We don't care about in case of an error below because current_db - was just set to 0. + /* Let's reset current_db, assume it's gone */ + my_free(current_db, MYF(MY_ALLOW_ZERO_PTR)); + current_db= 0; + /* + We don't care about in case of an error below because current_db + was just set to 0. */ if (!mysql_query(&mysql, "SELECT DATABASE()") && (res= mysql_use_result(&mysql))) { row= mysql_fetch_row(res); - if (row[0] && - (!current_db || cmp_database(charset_info, current_db, row[0]))) + if (row[0]) { - my_free(current_db, MYF(MY_ALLOW_ZERO_PTR)); current_db= my_strdup(row[0], MYF(MY_WME)); } - (void) mysql_fetch_row(res); // Read eof + (void) mysql_fetch_row(res); // Read eof mysql_free_result(res); } - - if (!current_db || cmp_database(charset_info, current_db, tmp)) + + if (!current_db || cmp_database(charset_info, current_db,tmp)) { if (one_database) skip_updates= 1; @@ -2835,14 +2842,18 @@ com_status(String *buffer __attribute__((unused)), const char *status; char buff[22]; ulonglong id; + MYSQL_RES *result; + LINT_INIT(result); tee_puts("--------------", stdout); usage(1); /* Print version */ if (connected) { - MYSQL_RES *result; - LINT_INIT(result); tee_fprintf(stdout, "\nConnection id:\t\t%lu\n",mysql_thread_id(&mysql)); + /* + Don't remove "limit 1", + it is protection againts SQL_SELECT_LIMIT=0 + */ if (!mysql_query(&mysql,"select DATABASE(), USER() limit 1") && (result=mysql_use_result(&mysql))) { @@ -2853,7 +2864,7 @@ com_status(String *buffer __attribute__((unused)), tee_fprintf(stdout, "Current user:\t\t%s\n", cur[1]); } mysql_free_result(result); - } + } #ifdef HAVE_OPENSSL if (mysql.net.vio && mysql.net.vio->ssl_arg && SSL_get_cipher((SSL*) mysql.net.vio->ssl_arg)) @@ -2887,9 +2898,30 @@ com_status(String *buffer __attribute__((unused)), if ((id= mysql_insert_id(&mysql))) tee_fprintf(stdout, "Insert id:\t\t%s\n", llstr(id, buff)); - tee_fprintf(stdout, "Client characterset:\t%s\n", - charset_info->name); - tee_fprintf(stdout, "Server characterset:\t%s\n", mysql.charset->name); + /* + Don't remove "limit 1", + it is protection againts SQL_SELECT_LIMIT=0 + */ + if (!mysql_query(&mysql,"select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1") && + (result=mysql_use_result(&mysql))) + { + MYSQL_ROW cur=mysql_fetch_row(result); + if (cur) + { + tee_fprintf(stdout, "Server characterset:\t%s\n", cur[0] ? cur[0] : ""); + tee_fprintf(stdout, "Db characterset:\t%s\n", cur[3] ? cur[3] : ""); + tee_fprintf(stdout, "Client characterset:\t%s\n", cur[2] ? cur[2] : ""); + tee_fprintf(stdout, "Conn. characterset:\t%s\n", cur[1] ? cur[1] : ""); + } + mysql_free_result(result); + } + else + { + /* Probably pre-4.1 server */ + tee_fprintf(stdout, "Client characterset:\t%s\n", charset_info->csname); + tee_fprintf(stdout, "Server characterset:\t%s\n", mysql.charset->csname); + } + #ifndef EMBEDDED_LIBRARY if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket) tee_fprintf(stdout, "TCP port:\t\t%d\n", mysql.port); @@ -2948,7 +2980,12 @@ put_info(const char *str,INFO_TYPE info_type, uint error, const char *sqlstate) (void) fflush(file); fprintf(file,"ERROR"); if (error) - (void) fprintf(file," %d",error); + { + if (sqlstate) + (void) fprintf(file," %d (%s)",error, sqlstate); + else + (void) fprintf(file," %d",error); + } if (status.query_start_line && line_numbers) { (void) fprintf(file," at line %lu",status.query_start_line); diff --git a/client/mysqladmin.c b/client/mysqladmin.c index df3e8dfed62..bccbf29ef83 100644 --- a/client/mysqladmin.c +++ b/client/mysqladmin.c @@ -25,7 +25,7 @@ #include <sys/stat.h> #include <mysql.h> -#define ADMIN_VERSION "8.40" +#define ADMIN_VERSION "8.41" #define MAX_MYSQL_VAR 256 #define SHUTDOWN_DEF_TIMEOUT 3600 /* Wait for shutdown */ #define MAX_TRUNC_LENGTH 3 @@ -209,6 +209,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1]=0; /* Cut length of argument */ + tty_password= 0; } else tty_password=1; @@ -268,7 +269,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), int main(int argc,char *argv[]) { - int error, ho_error; + int error= 0, ho_error; MYSQL mysql; char **commands, **save_argv; @@ -313,10 +314,25 @@ int main(int argc,char *argv[]) mysql_options(&mysql,MYSQL_SHARED_MEMORY_BASE_NAME,shared_memory_base_name); #endif if (sql_connect(&mysql, option_wait)) - error = 1; + { + unsigned int err= mysql_errno(&mysql); + if (err >= CR_MIN_ERROR && err <= CR_MAX_ERROR) + error= 1; + else + { + /* Return 0 if all commands are PING */ + for (; argc > 0; argv++, argc--) + { + if (find_type(argv[0], &command_typelib, 2) != ADMIN_PING) + { + error= 1; + break; + } + } + } + } else { - error = 0; while (!interrupted && (!opt_count_iterations || nr_iterations)) { new_line = 0; diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c index b072d1c86fe..8182b95fb83 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -16,7 +16,7 @@ /* By Jani Tolonen, 2001-04-20, MySQL Development Team */ -#define CHECK_VERSION "2.4.3" +#define CHECK_VERSION "2.4.4" #include "client_priv.h" #include <m_ctype.h> @@ -246,6 +246,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1] = 0; /* Cut length of argument */ + tty_password= 0; } else tty_password = 1; @@ -436,18 +437,18 @@ static int process_all_tables_in_db(char *database) LINT_INIT(res); if (use_db(database)) return 1; - if (!(mysql_query(sock, "SHOW TABLES") || - (res = mysql_store_result(sock)))) + if (mysql_query(sock, "SHOW TABLES") || + !((res= mysql_store_result(sock)))) return 1; if (opt_all_in_1) { - /* + /* We need table list in form `a`, `b`, `c` that's why we need 4 more chars added to to each table name space is for more readable output in logs and in case of error */ - + char *tables, *end; uint tot_length = 0; diff --git a/client/mysqldump.c b/client/mysqldump.c index 57367a7d8af..af271343ab9 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -37,7 +37,7 @@ ** 10 Jun 2003: SET NAMES and --no-set-names by Alexander Barkov */ -#define DUMP_VERSION "10.7" +#define DUMP_VERSION "10.8" #include <my_global.h> #include <my_sys.h> @@ -81,7 +81,8 @@ static my_bool verbose=0,tFlag=0,cFlag=0,dFlag=0,quick= 1, extended_insert= 1, opt_alldbs=0,opt_create_db=0,opt_first_slave=0,opt_set_charset, opt_autocommit=0,opt_master_data,opt_disable_keys=1,opt_xml=0, opt_delete_master_logs=0, tty_password=0, - opt_single_transaction=0, opt_comments= 0, opt_compact= 0; + opt_single_transaction=0, opt_comments= 0, opt_compact= 0, + opt_hex_blob=0; static ulong opt_max_allowed_packet, opt_net_buffer_length; static MYSQL mysql_connection,*sock=0; static char insert_pat[12 * 1024],*opt_password=0,*current_user=0, @@ -318,6 +319,8 @@ static struct my_option my_long_options[] = {"comments", 'i', "Write additional information.", (gptr*) &opt_comments, (gptr*) &opt_comments, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"hex-blob", OPT_HEXBLOB, "Dump BLOBs in HEX. this mode does not work with extended-insert", + (gptr*) &opt_hex_blob, (gptr*) &opt_hex_blob, 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} }; @@ -490,6 +493,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1]=0; /* Cut length of argument */ + tty_password= 0; } else tty_password=1; @@ -1526,6 +1530,7 @@ static void dumpTable(uint numFields, char *table) for (i = 0; i < mysql_num_fields(res); i++) { + int is_blob; if (!(field = mysql_fetch_field(res))) { sprintf(query,"%s: Not enough fields from table %s! Aborting.\n", @@ -1534,6 +1539,17 @@ 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 it in hex only BLOB columns. + */ + is_blob= (opt_hex_blob && field->charsetnr == 63 && + (field->type == FIELD_TYPE_BLOB || + field->type == FIELD_TYPE_LONG_BLOB || + field->type == FIELD_TYPE_MEDIUM_BLOB || + field->type == FIELD_TYPE_TINY_BLOB)) ? 1 : 0; if (extended_insert) { ulong length = lengths[i]; @@ -1554,12 +1570,28 @@ static void dumpTable(uint numFields, char *table) error= EX_EOM; goto err; } - dynstr_append(&extended_row,"'"); - extended_row.length += - mysql_real_escape_string(&mysql_connection, - &extended_row.str[extended_row.length],row[i],length); - extended_row.str[extended_row.length]='\0'; - dynstr_append(&extended_row,"'"); + if (opt_hex_blob && is_blob) + { + ulong counter; + unsigned char *ptr= row[i]; + dynstr_append(&extended_row, "0x"); + for (counter = 0; counter < lengths[i]; counter++) + { + char xx[3]; + sprintf(xx, "%02X", ptr[counter]); + dynstr_append(&extended_row, xx); + } + } + else + { + dynstr_append(&extended_row,"'"); + extended_row.length += + mysql_real_escape_string(&mysql_connection, + &extended_row.str[extended_row.length], + row[i],length); + extended_row.str[extended_row.length]='\0'; + dynstr_append(&extended_row,"'"); + } } else { @@ -1610,8 +1642,20 @@ static void dumpTable(uint numFields, char *table) print_quoted_xml(md_result_file, row[i], lengths[i]); fputs("</field>\n", md_result_file); } - else - unescape(md_result_file, row[i], lengths[i]); + else if (opt_hex_blob && is_blob) + { /* sakaik got this idea. */ + ulong counter; + char xx[4]; + unsigned char *ptr= row[i]; + fputs("0x", md_result_file); + for (counter = 0; counter < lengths[i]; counter++) + { + sprintf(xx, "%02X", ptr[counter]); + fputs(xx, md_result_file); + } + } + else + unescape(md_result_file, row[i], lengths[i]); } else { @@ -1825,7 +1869,7 @@ static int init_dumping(char *database) if (opt_databases || opt_alldbs) { /* - length of table name * 2 (if name contain quotas), 2 quotas and 0 + length of table name * 2 (if name contains quotes), 2 quotes and 0 */ char quoted_database_buf[64*2+3]; char *qdatabase= quote_name(database,quoted_database_buf,opt_quoted); diff --git a/client/mysqlimport.c b/client/mysqlimport.c index c68d2d9f724..fae84be610a 100644 --- a/client/mysqlimport.c +++ b/client/mysqlimport.c @@ -25,7 +25,7 @@ ** * * ** ************************* */ -#define IMPORT_VERSION "3.4" +#define IMPORT_VERSION "3.5" #include "client_priv.h" #include "mysql_version.h" @@ -191,6 +191,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1]=0; /* Cut length of argument */ + tty_password= 0; } else tty_password= 1; @@ -263,14 +264,11 @@ static int write_to_table(char *filename, MYSQL *sock) { char tablename[FN_REFLEN], hard_path[FN_REFLEN], sql_statement[FN_REFLEN*16+256], *end; - my_bool local_file; DBUG_ENTER("write_to_table"); DBUG_PRINT("enter",("filename: %s",filename)); - local_file= sock->unix_socket == 0 || opt_local_file; - fn_format(tablename, filename, "", "", 1 | 2); /* removes path & ext. */ - if (local_file) + if (! opt_local_file) strmov(hard_path,filename); else my_load_path(hard_path, filename, NULL); /* filename includes the path */ @@ -289,7 +287,7 @@ static int write_to_table(char *filename, MYSQL *sock) to_unix_path(hard_path); if (verbose) { - if (local_file) + if (opt_local_file) fprintf(stdout, "Loading data from LOCAL file: %s into %s\n", hard_path, tablename); else @@ -298,7 +296,7 @@ static int write_to_table(char *filename, MYSQL *sock) } sprintf(sql_statement, "LOAD DATA %s %s INFILE '%s'", opt_low_priority ? "LOW_PRIORITY" : "", - local_file ? "LOCAL" : "", hard_path); + opt_local_file ? "LOCAL" : "", hard_path); end= strend(sql_statement); if (replace) end= strmov(end, " REPLACE"); diff --git a/client/mysqlshow.c b/client/mysqlshow.c index 9c9fdf6e443..ee478058cdc 100644 --- a/client/mysqlshow.c +++ b/client/mysqlshow.c @@ -16,7 +16,7 @@ /* Show databases, tables or columns */ -#define SHOW_VERSION "9.4" +#define SHOW_VERSION "9.5" #include "client_priv.h" #include <my_sys.h> @@ -257,6 +257,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), while (*argument) *argument++= 'x'; /* Destroy argument */ if (*start) start[1]=0; /* Cut length of argument */ + tty_password= 0; } else tty_password=1; diff --git a/client/mysqltest.c b/client/mysqltest.c index 02982e8e37f..e5a7a6228e0 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -42,7 +42,7 @@ **********************************************************************/ -#define MTEST_VERSION "2.1" +#define MTEST_VERSION "2.3" #include <my_global.h> #include <mysql_embed.h> @@ -53,12 +53,13 @@ #include <mysqld_error.h> #include <m_ctype.h> #include <my_dir.h> +#include <errmsg.h> /* Error codes */ #include <hash.h> #include <my_getopt.h> #include <stdarg.h> #include <sys/stat.h> #include <violite.h> - +#include <regex.h> /* Our own version of lib */ #define MAX_QUERY 131072 #define MAX_VAR_NAME 256 #define MAX_COLUMNS 256 @@ -75,7 +76,7 @@ #ifndef MYSQL_MANAGER_PORT #define MYSQL_MANAGER_PORT 23546 #endif -#define MAX_SERVER_ARGS 20 +#define MAX_SERVER_ARGS 64 /* Sometimes in a test the client starts before @@ -93,14 +94,46 @@ enum {OPT_MANAGER_USER=256,OPT_MANAGER_HOST,OPT_MANAGER_PASSWD, OPT_MANAGER_PORT,OPT_MANAGER_WAIT_TIMEOUT, OPT_SKIP_SAFEMALLOC, OPT_SSL_SSL, OPT_SSL_KEY, OPT_SSL_CERT, OPT_SSL_CA, OPT_SSL_CAPATH, - OPT_SSL_CIPHER}; + OPT_SSL_CIPHER,OPT_PS_PROTOCOL}; + +/* ************************************************************************ */ +/* + A line that starts with !$ or $S, and the list of error codes to + --error are stored in an internal array of structs. This struct can + hold numeric SQL error codes or SQLSTATE codes as strings. The + element next to the last active element in the list is set to type + ERR_EMPTY. When an SQL statement return an error we use this list to + check if this is an expected error. +*/ + +enum match_err_type +{ + ERR_EMPTY= 0, + ERR_ERRNO, + ERR_SQLSTATE +}; + +typedef struct +{ + enum match_err_type type; + union + { + uint errnum; + char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ + } code; +} match_err; + +static match_err global_expected_errno[MAX_EXPECTED_ERRORS]; +static uint global_expected_errors; + +/* ************************************************************************ */ static int record = 0, opt_sleep=0; static char *db = 0, *pass=0; 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, - tty_password= 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 uint start_lineno, *lineno; const char* manager_user="root",*manager_host=0; char *manager_pass=0; @@ -124,14 +157,27 @@ static int *cur_block, *block_stack_end; static int block_stack[BLOCK_STACK_DEPTH]; static int block_ok_stack[BLOCK_STACK_DEPTH]; -static uint global_expected_errno[MAX_EXPECTED_ERRORS], global_expected_errors; -static CHARSET_INFO *charset_info= &my_charset_latin1; +static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ +static const char *charset_name= "latin1"; /* Default character set name */ static int embedded_server_arg_count=0; static char *embedded_server_args[MAX_SERVER_ARGS]; static my_bool display_result_vertically= FALSE, display_metadata= FALSE; +/* See the timer_output() definition for details */ +static char *timer_file = NULL; +static ulonglong timer_start; +static int got_end_timer= FALSE; +static void timer_output(void); +static ulonglong timer_now(void); + +static regex_t ps_re; /* Holds precompiled re for valid PS statements */ +static void ps_init_re(void); +static int ps_match_re(char *); +static char *ps_eprint(int); +static void ps_free_reg(void); + static const char *embedded_server_groups[] = { "server", "embedded", @@ -229,7 +275,9 @@ Q_ENABLE_INFO, Q_DISABLE_INFO, Q_ENABLE_METADATA, Q_DISABLE_METADATA, Q_EXEC, Q_DELIMITER, Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, -Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_EXIT, +Q_START_TIMER, Q_END_TIMER, +Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, +Q_EXIT, Q_UNKNOWN, /* Unknown command. */ Q_COMMENT, /* Comments, ignored. */ @@ -242,7 +290,7 @@ struct st_query char *query, *query_buf,*first_argument,*end; int first_word_len; my_bool abort_on_error, require_file; - uint expected_errno[MAX_EXPECTED_ERRORS]; + match_err expected_errno[MAX_EXPECTED_ERRORS]; uint expected_errors; char record_file[FN_REFLEN]; enum enum_commands type; @@ -308,6 +356,11 @@ const char *command_names[]= "horizontal_results", "query_vertical", "query_horizontal", + "start_timer", + "end_timer", + "character_set", + "disable_ps_protocol", + "enable_ps_protocol", "exit", 0 }; @@ -319,8 +372,7 @@ DYNAMIC_STRING ds_res; static void die(const char *fmt, ...); static void init_var_hash(); static VAR* var_from_env(const char *, const char *); -static byte* get_var_key(const byte* rec, uint* len, - my_bool __attribute__((unused)) t); +static byte* get_var_key(const byte* rec, uint* len, my_bool t); static VAR* var_init(VAR* v, const char *name, int name_len, const char *val, int val_len); @@ -330,6 +382,7 @@ int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname); void reject_dump(const char *record_file, char *buf, int size); int close_connection(struct st_query* q); +static void set_charset(struct st_query*); VAR* var_get(const char *var_name, const char** var_name_end, my_bool raw, my_bool ignore_not_existing); int eval_expr(VAR* v, const char *p, const char** p_end); @@ -480,6 +533,8 @@ static void free_used_memory() my_free(pass,MYF(MY_ALLOW_ZERO_PTR)); free_defaults(default_argv); mysql_server_end(); + if (ps_protocol) + ps_free_reg(); my_end(MY_CHECK_ERROR); DBUG_VOID_RETURN; } @@ -845,10 +900,10 @@ int do_source(struct st_query* q) 1 error */ -int do_exec(struct st_query* q) +static void do_exec(struct st_query* q) { - int error= 0; - DYNAMIC_STRING *ds; + int error; + DYNAMIC_STRING *ds= NULL; /* Assign just to avoid warning */ DYNAMIC_STRING ds_tmp; char buf[1024]; FILE *res_file; @@ -885,7 +940,15 @@ int do_exec(struct st_query* q) while (fgets(buf, sizeof(buf), res_file)) replace_dynstr_append_mem(ds, buf, strlen(buf)); + } + + error= pclose(res_file); + if (error != 0) + die("command \"%s\" failed", cmd); + + if (!disable_result_log) + { if (glob_replace) free_replace(); @@ -903,9 +966,6 @@ int do_exec(struct st_query* q) if (ds == &ds_tmp) dynstr_free(&ds_tmp); } - pclose(res_file); - - DBUG_RETURN(error); } @@ -1225,25 +1285,58 @@ static void get_file_name(char *filename, struct st_query* q) p[0]=0; } +static void set_charset(struct st_query* q) +{ + char* charset_name= q->first_argument; + char* tmp; + + if (!charset_name || !*charset_name) + die("Missing charset name in 'character_set'\n"); + /* Remove end space */ + tmp= charset_name; + while (*tmp && !my_isspace(charset_info,*tmp)) + tmp++; + *tmp= 0; + + charset_info= get_charset_by_csname(charset_name,MY_CS_PRIMARY,MYF(MY_WME)); + if (!charset_info) + abort_not_supported_test(); +} -static uint get_ints(uint *to,struct st_query* q) +static uint get_errcodes(match_err *to,struct st_query* q) { - char* p=q->first_argument; - long val; - uint count=0; - DBUG_ENTER("get_ints"); + char* p= q->first_argument; + uint count= 0; + DBUG_ENTER("get_errcodes"); if (!*p) die("Missing argument in %s\n", q->query); - for (; (p=str2int(p,10,(long) INT_MIN, (long) INT_MAX, &val)) ; p++) + do { + 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'; + to[count].type= ERR_SQLSTATE; + } + else + { + long val; + p=str2int(p,10,(long) INT_MIN, (long) INT_MAX, &val); + if (p == NULL) + die("Invalid argument in %s\n", q->query); + to[count].code.errnum= (uint) val; + to[count].type= ERR_ERRNO; + } count++; - *to++= (uint) val; - if (*p != ',') - break; - } - *to++=0; /* End of data */ + } while (*(p++) == ','); + + to[count].type= ERR_EMPTY; /* End of data */ DBUG_RETURN(count); } @@ -1569,7 +1662,7 @@ int do_connect(struct st_query* q) if (opt_compress) mysql_options(&next_con->mysql,MYSQL_OPT_COMPRESS,NullS); mysql_options(&next_con->mysql, MYSQL_OPT_LOCAL_INFILE, 0); - mysql_options(&next_con->mysql, MYSQL_SET_CHARSET_NAME, "latin1"); + mysql_options(&next_con->mysql, MYSQL_SET_CHARSET_NAME, charset_name); #ifdef HAVE_OPENSSL if (opt_use_ssl) @@ -1715,6 +1808,7 @@ int read_line(char* buf, int size) c= my_getc(*cur_file); if (feof(*cur_file)) { + found_eof: if ((*cur_file) != stdin) my_fclose(*cur_file, MYF(0)); cur_file--; @@ -1724,6 +1818,10 @@ int read_line(char* buf, int size) continue; } + /* Line counting is independent of state */ + if (c == '\n') + (*lineno)++; + switch(state) { case R_NORMAL: /* Only accept '{' in the beginning of a line */ @@ -1739,14 +1837,12 @@ int read_line(char* buf, int size) else if (c == '\n') { state = R_LINE_START; - (*lineno)++; } break; case R_COMMENT: if (c == '\n') { *p= 0; - (*lineno)++; DBUG_RETURN(0); } break; @@ -1758,7 +1854,7 @@ int read_line(char* buf, int size) else if (my_isspace(charset_info, c)) { if (c == '\n') - start_lineno= ++*lineno; /* Query hasn't started yet */ + start_lineno= *lineno; /* Query hasn't started yet */ no_save= 1; } else if (c == '}') @@ -1824,7 +1920,39 @@ int read_line(char* buf, int size) } if (!no_save) - *p++= c; + { + /* Could be a multibyte character */ + /* This code is based on the code in "sql_load.cc" */ +#ifdef USE_MB + int charlen = my_mbcharlen(charset_info, c); + /* We give up if multibyte character is started but not */ + /* completed before we pass buf_end */ + if ((charlen > 1) && (p + charlen) <= buf_end) + { + int i; + char* mb_start = p; + + *p++ = c; + + for (i= 1; i < charlen; i++) + { + if (feof(*cur_file)) + goto found_eof; /* FIXME: could we just break here?! */ + c= my_getc(*cur_file); + *p++ = c; + } + if (! my_ismbchar(charset_info, mb_start, p)) + { + /* It was not a multiline char, push back the characters */ + /* We leave first 'c', i.e. pretend it was a normal char */ + while (p > mb_start) + my_ungetc(*--p); + } + } + else +#endif + *p++= c; + } } *p= 0; /* Always end with \0 */ DBUG_RETURN(feof(*cur_file)); @@ -1836,7 +1964,6 @@ static char read_query_buf[MAX_QUERY]; int read_query(struct st_query** q_ptr) { char *p = read_query_buf, * p1 ; - int expected_errno; struct st_query* q; DBUG_ENTER("read_query"); @@ -1886,13 +2013,25 @@ int read_query(struct st_query** q_ptr) p++; if (*p == '$') { - expected_errno= 0; - p++; - for (; my_isdigit(charset_info, *p); p++) - expected_errno = expected_errno * 10 + *p - '0'; - q->expected_errno[0] = expected_errno; - q->expected_errno[1] = 0; - q->expected_errors=1; + int expected_errno= 0; + p++; + for (; my_isdigit(charset_info, *p); p++) + expected_errno = expected_errno * 10 + *p - '0'; + q->expected_errno[0].code.errnum = expected_errno; + q->expected_errno[0].type= ERR_ERRNO; + q->expected_errno[1].type= ERR_EMPTY; + q->expected_errors=1; + } + else if (*p == 'S') /* SQLSTATE */ + { + int i; + p++; + for (i = 0; my_isalnum(charset_info, *p) && i < SQLSTATE_LENGTH; p++, i++) + q->expected_errno[0].code.sqlstate[i]= *p; + q->expected_errno[0].code.sqlstate[i]= '\0'; + q->expected_errno[0].type= ERR_SQLSTATE; + q->expected_errno[1].type= ERR_EMPTY; + q->expected_errors=1; } } @@ -1962,6 +2101,9 @@ static struct my_option my_long_options[] = 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"port", 'P', "Port number to use for connection.", (gptr*) &port, (gptr*) &port, 0, GET_INT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, 0}, + {"ps-protocol", OPT_PS_PROTOCOL, "Use prepared statements protocol for communication", + (gptr*) &ps_protocol, (gptr*) &ps_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"quiet", 's', "Suppress all normal output.", (gptr*) &silent, (gptr*) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"record", 'r', "Record output of test_file into result file.", @@ -1987,6 +2129,8 @@ static struct my_option my_long_options[] = #include "sslopt-longopts.h" {"test-file", 'x', "Read test from/in this file (default stdin).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"timer-file", 'm', "File where the timing in micro seconds is stored.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"tmpdir", 't', "Temporary directory where sockets are put.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "User for login.", (gptr*) &user, (gptr*) &user, 0, GET_STR, @@ -2048,12 +2192,26 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), die("Could not open %s: errno = %d", argument, errno); break; } + case 'm': + { + static char buff[FN_REFLEN]; + if (!test_if_hard_path(argument)) + { + strxmov(buff, opt_basedir, argument, NullS); + argument= buff; + } + fn_format(buff, argument, "", "", 4); + timer_file= buff; + unlink(timer_file); /* Ignore error, may not exist */ + break; + } case 'p': if (argument) { my_free(pass, MYF(MY_ALLOW_ZERO_PTR)); pass= my_strdup(argument, MYF(MY_FAE)); while (*argument) *argument++= 'x'; /* Destroy argument */ + tty_password= 0; } else tty_password= 1; @@ -2068,10 +2226,9 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), embedded_server_arg_count=1; embedded_server_args[0]= (char*) ""; } - embedded_server_args[embedded_server_arg_count++]= - my_strdup(argument, MYF(MY_FAE)); - if (embedded_server_arg_count == MAX_SERVER_ARGS || - !embedded_server_args[embedded_server_arg_count-1]) + if (embedded_server_arg_count == MAX_SERVER_ARGS-1 || + !(embedded_server_args[embedded_server_arg_count++]= + my_strdup(argument, MYF(MY_FAE)))) { die("Can't use server argument"); } @@ -2225,7 +2382,36 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) * the result will be read - for regular query, both bits must be on */ -int run_query(MYSQL* mysql, struct st_query* q, int flags) +static int run_query_normal(MYSQL *mysql, struct st_query *q, int flags); +static int run_query_stmt (MYSQL *mysql, struct st_query *q, int flags); +static void run_query_stmt_handle_warnings(MYSQL *mysql, DYNAMIC_STRING *ds); +static int run_query_stmt_handle_error(char *query, struct st_query *q, + MYSQL_STMT *stmt, DYNAMIC_STRING *ds); +static void run_query_display_metadata(MYSQL_FIELD *field, uint num_fields, + DYNAMIC_STRING *ds); + +static int run_query(MYSQL *mysql, struct st_query *q, int flags) +{ + + /* + Try to find out if we can run this statement using the prepared + statement protocol. + + We don't have a mysql_stmt_send_execute() so we only handle + complete SEND+REAP. + + If it is a '?' in the query it may be a SQL level prepared + statement already and we can't do it twice + */ + + if (ps_protocol_enabled && disable_info && + (flags & QUERY_SEND) && (flags & QUERY_REAP) && ps_match_re(q->query)) + return run_query_stmt (mysql, q, flags); + return run_query_normal(mysql, q, flags); +} + + +static int run_query_normal(MYSQL* mysql, struct st_query* q, int flags) { MYSQL_RES* res= 0; uint i; @@ -2235,7 +2421,7 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) DYNAMIC_STRING eval_query; char* query; int query_len, got_error_on_send= 0; - DBUG_ENTER("run_query"); + DBUG_ENTER("run_query_normal"); DBUG_PRINT("enter",("flags: %d", flags)); if (q->type != Q_EVAL) @@ -2263,7 +2449,7 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) if (flags & QUERY_SEND) { got_error_on_send= mysql_send_query(mysql, query, query_len); - if (got_error_on_send && !q->expected_errno[0]) + if (got_error_on_send && q->expected_errno[0].type == ERR_EMPTY) die("At line %u: unable to send query '%s' (mysql_errno=%d , errno=%d)", start_lineno, query, mysql_errno(mysql), errno); } @@ -2295,7 +2481,10 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) { for (i=0 ; (uint) i < q->expected_errors ; i++) { - if ((q->expected_errno[i] == mysql_errno(mysql))) + 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) { @@ -2309,7 +2498,9 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) dynstr_append_mem(ds,"\n",1); } /* Don't log error if we may not get an error */ - else if (q->expected_errno[0] != 0) + 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 */ } @@ -2325,8 +2516,12 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) dynstr_append_mem(ds,"\n",1); if (i) { - verbose_msg("query '%s' failed with wrong errno %d instead of %d...", - q->query, mysql_errno(mysql), q->expected_errno[0]); + if (q->expected_errno[0].type == ERR_ERRNO) + verbose_msg("query '%s' failed with wrong errno %d instead of %d...", + q->query, mysql_errno(mysql), q->expected_errno[0].code.errnum); + else + verbose_msg("query '%s' failed with wrong sqlstate %s instead of %s...", + q->query, mysql_sqlstate(mysql), q->expected_errno[0].code.sqlstate); error= 1; goto end; } @@ -2346,11 +2541,22 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) }*/ } - if (q->expected_errno[0]) + if (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0) { - error = 1; + /* 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]); + q->query, q->expected_errno[0].code.errnum); + error = 1; + goto end; + } + 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); + error = 1; goto end; } @@ -2358,56 +2564,14 @@ int run_query(MYSQL* mysql, struct st_query* q, int flags) { if (res) { - MYSQL_FIELD *field, *field_end; + MYSQL_FIELD *field= mysql_fetch_fields(res); uint num_fields= mysql_num_fields(res); if (display_metadata) - { - dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\tColumn_alias\tName\tType\tLength\tMax length\tIs_null\tFlags\tDecimals\tCharsetnr\n"); - for (field= mysql_fetch_fields(res), field_end= field+num_fields ; - field < field_end ; - field++) - { - char buff[22]; - dynstr_append_mem(ds, field->catalog, field->catalog_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->db, field->db_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->org_table, field->org_table_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->table, field->table_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->org_name, field->org_name_length); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, field->name, field->name_length); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->type, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->length, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->max_length, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - dynstr_append_mem(ds, (char*) (IS_NOT_NULL(field->flags) ? - "N" : "Y"), 1); - dynstr_append_mem(ds, "\t", 1); - - int10_to_str((int) field->flags, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->decimals, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->charsetnr, buff, 10); - dynstr_append(ds, buff); - dynstr_append_mem(ds, "\n", 1); - } - } + run_query_display_metadata(field, num_fields, ds); + if (!display_result_vertically) { - field= mysql_fetch_fields(res); for (i = 0; i < num_fields; i++) { if (i) @@ -2483,6 +2647,576 @@ end: } +/****************************************************************************\ + * If --ps-protocol run ordinary statements using prepared statemnt C API +\****************************************************************************/ + +/* + We don't have a mysql_stmt_send_execute() so we only handle + complete SEND+REAP +*/ + +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; + char *query; + MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ + DYNAMIC_STRING *ds; + DYNAMIC_STRING ds_tmp; + DYNAMIC_STRING eval_query; + MYSQL_STMT *stmt; + DBUG_ENTER("run_query_stmt"); + + /* + We must allocate a new stmt for each query in this program becasue this + may be a new connection. + */ + if (!(stmt= mysql_stmt_init(mysql))) + die("At line %u: unable init stmt structure"); + + if (q->type != Q_EVAL) + { + query= q->query; + query_len= strlen(query); + } + else + { + init_dynamic_string(&eval_query, "", 16384, 65536); + do_eval(&eval_query, q->query); + query= eval_query.str; + query_len= eval_query.length; + } + DBUG_PRINT("query", ("'%-.60s'", query)); + + if (q->record_file[0]) + { + init_dynamic_string(&ds_tmp, "", 16384, 65536); + ds= &ds_tmp; + } + else + ds= &ds_res; + + /* Store the query into the output buffer if not disabled */ + if (!disable_query_log) + { + replace_dynstr_append_mem(ds,query, query_len); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + + /* + We use the prepared statement interface but there is actually no + '?' in the query. If unpreparable we fall back to use normal + C API. + */ + if ((err= mysql_stmt_prepare(stmt, query, query_len)) == CR_NO_PREPARE_STMT) + return run_query_normal(mysql, q, flags); + + if (err != 0) + { + if (q->abort_on_error) + { + die("At line %u: unable to prepare statement '%s': " + "%s (mysql_stmt_errno=%d returned=%d)", + start_lineno, query, + mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); + } + 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; + } + } + + /* 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); + + /* + No need to call mysql_stmt_bind_param() because we have no + parameter markers. + + To optimize performance we use a global 'stmt' that is initiated + once. A new prepare will implicitely close the old one. When we + terminate we will lose the connection, this also closes the last + prepared statement. + */ + + if ((got_error_on_execute= mysql_stmt_execute(stmt)) != 0) /* 0 == Success */ + { + if (q->abort_on_error) + { + /* We got an error, unexpected */ + die("At line %u: unable to execute statement '%s': " + "%s (mysql_stmt_errno=%d returned=%d)", + start_lineno, 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 instruct that we want to update the "max_length" field in + mysql_stmt_store_result(), this is our only way to know how much + buffer to allocate for result data + */ + { + my_bool one= 1; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, + (void*) &one) != 0) + die("At line %u: unable to set stmt attribute " + "'STMT_ATTR_UPDATE_MAX_LENGTH': %s (returned=%d)", + start_lineno, query, err); + } + + /* + If we got here the statement succeeded and was expected to do so, + get data. Note that this can still give errors found during execution! + */ + if ((err= mysql_stmt_store_result(stmt)) != 0) + { + if (q->abort_on_error) + { + /* We got an error, unexpected */ + die("At line %u: unable to execute statement '%s': " + "%s (mysql_stmt_errno=%d returned=%d)", + start_lineno, 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; + } + } + + /* If we got here the statement was both executed and read succeesfully */ + + if (q->expected_errno[0].type == ERR_ERRNO && + q->expected_errno[0].code.errnum != 0) + { + verbose_msg("query '%s' succeeded - should have failed with errno %d...", + q->query, q->expected_errno[0].code.errnum); + error= 1; + goto end; + } + + num_rows= mysql_stmt_num_rows(stmt); + + /* + Not all statements creates a result set. If there is one we can + now create another normal result set that contains the meta + data. This set can be handled almost like any other non prepared + statement result set. + */ + if (!disable_result_log && ((res= mysql_stmt_result_metadata(stmt)) != NULL)) + { + /* Take the column count from meta info */ + MYSQL_FIELD *field= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + /* FIXME check error from the above? */ + + if (display_metadata) + run_query_display_metadata(field, num_fields, ds); + + if (!display_result_vertically) + { + /* Display the table heading with the names tab separated */ + uint col_idx; + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, field[col_idx].name, + strlen(field[col_idx].name)); + } + dynstr_append_mem(ds, "\n", 1); + } + + /* Now we are to put the real result into the output buffer */ + /* FIXME when it works, create function append_stmt_result() */ + { + MYSQL_BIND *bind; + my_bool *is_null; + unsigned long *length; + /* FIXME we don't handle vertical display ..... */ + uint col_idx, row_idx; + + /* Allocate array with bind structs, lengths and NULL flags */ + bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), + MYF(MY_WME | MY_FAE)); + length= (unsigned long*) my_malloc(num_fields * sizeof(unsigned long), + MYF(MY_WME | MY_FAE)); + is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), + MYF(MY_WME | MY_FAE)); + + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + /* Allocate data for output */ + /* + FIXME it may be a bug that for non string/blob types + 'max_length' is 0, should try out 'length' in that case + */ + uint max_length= max(field[col_idx].max_length + 1, 1024); + char *str_data= (char *) my_malloc(max_length, MYF(MY_WME | MY_FAE)); + + bind[col_idx].buffer_type= MYSQL_TYPE_STRING; + bind[col_idx].buffer= (char *)str_data; + bind[col_idx].buffer_length= max_length; + bind[col_idx].is_null= &is_null[col_idx]; + bind[col_idx].length= &length[col_idx]; + } + + /* Fill in the data into the structures created above */ + if ((err= mysql_stmt_bind_result(stmt, bind)) != 0) + die("At line %u: unable to bind result to statement '%s': " + "%s (mysql_stmt_errno=%d returned=%d)", + start_lineno, query, + mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); + + /* Read result from each row */ + for (row_idx= 0; row_idx < num_rows; row_idx++) + { + if ((err= mysql_stmt_fetch(stmt)) != 0) + die("At line %u: unable to fetch all rows from statement '%s': " + "%s (mysql_stmt_errno=%d returned=%d)", + start_lineno, query, + mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); + + /* Read result from each column */ + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + /* FIXME is string terminated? */ + const char *val= (const char *)bind[col_idx].buffer; + ulonglong len= *bind[col_idx].length; + if (col_idx < max_replace_column && replace_column[col_idx]) + { + val= replace_column[col_idx]; + len= strlen(val); + } + if (*bind[col_idx].is_null) + { + val= "NULL"; + len= 4; + } + if (!display_result_vertically) + { + if (col_idx) /* No tab before first col */ + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, len); + } + else + { + dynstr_append(ds, field[col_idx].name); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, len); + dynstr_append_mem(ds, "\n", 1); + } + } + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } + + if ((err= mysql_stmt_fetch(stmt)) != MYSQL_NO_DATA) + die("At line %u: fetch didn't end with MYSQL_NO_DATA from statement " + "'%s': %s (mysql_stmt_errno=%d returned=%d)", + start_lineno, query, + mysql_stmt_error(stmt), mysql_stmt_errno(stmt), err); + + free_replace_column(); + + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + /* Free data for output */ + my_free((gptr)bind[col_idx].buffer, MYF(MY_WME | MY_FAE)); + } + /* Free array with bind structs, lengths and NULL flags */ + my_free((gptr)bind , MYF(MY_WME | MY_FAE)); + my_free((gptr)length , MYF(MY_WME | MY_FAE)); + my_free((gptr)is_null , MYF(MY_WME | MY_FAE)); + } + + /* Add all warnings to the result */ + run_query_stmt_handle_warnings(mysql, ds); + + if (!disable_info) + { + char buf[40]; + sprintf(buf,"affected rows: %lu\n",(ulong) mysql_affected_rows(mysql)); + dynstr_append(ds, buf); + if (mysql_info(mysql)) + { + dynstr_append(ds, "info: "); + dynstr_append(ds, mysql_info(mysql)); + dynstr_append_mem(ds, "\n", 1); + } + } + } + run_query_stmt_handle_warnings(mysql, ds); + + if (record) + { + if (!q->record_file[0] && !result_file) + die("At line %u: Missing result file", start_lineno); + if (!result_file) + str_to_file(q->record_file, ds->str, ds->length); + } + else if (q->record_file[0]) + { + error= check_result(ds, q->record_file, q->require_file); + } + if (res) + mysql_free_result(res); /* Free normal result set with meta data */ + last_result= 0; /* FIXME have no idea what this is about... */ + + if (err >= 1) + mysql_error(mysql); /* FIXME strange, has no effect... */ + +end: + free_replace(); + last_result=0; + if (ds == &ds_tmp) + dynstr_free(&ds_tmp); + if (q->type == Q_EVAL) + dynstr_free(&eval_query); + mysql_stmt_close(stmt); + DBUG_RETURN(error); +} + + +/****************************************************************************\ + * Broken out sub functions to run_query_stmt() +\****************************************************************************/ + +static void run_query_display_metadata(MYSQL_FIELD *field, uint num_fields, + DYNAMIC_STRING *ds) +{ + MYSQL_FIELD *field_end; + dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" + "Column_alias\tName\tType\tLength\tMax length\tIs_null\t" + "Flags\tDecimals\tCharsetnr\n"); + + for (field_end= field+num_fields ; + field < field_end ; + field++) + { + char buff[22]; + dynstr_append_mem(ds, field->catalog, + field->catalog_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->db, field->db_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_table, + field->org_table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->table, + field->table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_name, + field->org_name_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->name, field->name_length); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->type, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->length, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->max_length, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, (char*) (IS_NOT_NULL(field->flags) ? + "N" : "Y"), 1); + dynstr_append_mem(ds, "\t", 1); + + int10_to_str((int) field->flags, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->decimals, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\t", 1); + int10_to_str((int) field->charsetnr, buff, 10); + dynstr_append(ds, buff); + dynstr_append_mem(ds, "\n", 1); + } +} + + +static void run_query_stmt_handle_warnings(MYSQL *mysql, DYNAMIC_STRING *ds) +{ + uint count; + DBUG_ENTER("run_query_stmt_handle_warnings"); + + if (!disable_warnings && (count= mysql_warning_count(mysql))) + { + if (mysql_real_query(mysql, "SHOW WARNINGS", 13) == 0) + { + MYSQL_RES *warn_res= mysql_store_result(mysql); + if (!warn_res) + verbose_msg("Warning count is %u but didn't get any warnings\n", + count); + else + { + dynstr_append_mem(ds, "Warnings:\n", 10); + append_result(ds, warn_res); + mysql_free_result(warn_res); + } + } + } + DBUG_VOID_RETURN; +} + + +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("At line %u: query '%s' failed: %d: %s", start_lineno, 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) + { + verbose_msg("query '%s' failed with wrong errno %d instead of %d...", + q->query, mysql_stmt_errno(stmt), q->expected_errno[0]); + 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; +} + +/****************************************************************************\ + * Functions to match SQL statements that can be prepared +\****************************************************************************/ + +static void ps_init_re(void) +{ + const char *ps_re_str = + "^(" + "[[:space:]]*REPLACE[[:space:]]|" + "[[:space:]]*INSERT[[:space:]]|" + "[[:space:]]*UPDATE[[:space:]]|" + "[[:space:]]*DELETE[[:space:]]|" + "[[:space:]]*SELECT[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+TABLE[[:space:]]|" + "[[:space:]]*DO[[:space:]]|" + "[[:space:]]*SET[[:space:]]+OPTION[[:space:]]|" + "[[:space:]]*DELETE[[:space:]]+MULTI[[:space:]]|" + "[[:space:]]*UPDATE[[:space:]]+MULTI[[:space:]]|" + "[[:space:]]*INSERT[[:space:]]+SELECT[[:space:]])"; + + int err= regcomp(&ps_re, ps_re_str, (REG_EXTENDED | REG_ICASE | REG_NOSUB), + &my_charset_latin1); + if (err) + { + char erbuf[100]; + int len= regerror(err, &ps_re, erbuf, sizeof(erbuf)); + fprintf(stderr, "error %s, %d/%d `%s'\n", + ps_eprint(err), len, (int)sizeof(erbuf), erbuf); + exit(1); + } +} + + +static int ps_match_re(char *stmt_str) +{ + int err= regexec(&ps_re, stmt_str, (size_t)0, NULL, 0); + + if (err == 0) + return 1; + else if (err == REG_NOMATCH) + return 0; + else + { + char erbuf[100]; + int len= regerror(err, &ps_re, erbuf, sizeof(erbuf)); + fprintf(stderr, "error %s, %d/%d `%s'\n", + ps_eprint(err), len, (int)sizeof(erbuf), erbuf); + exit(1); + } +} + +static char *ps_eprint(int err) +{ + static char epbuf[100]; + size_t len= regerror(REG_ITOA|err, (regex_t *)NULL, epbuf, sizeof(epbuf)); + assert(len <= sizeof(epbuf)); + return(epbuf); +} + + +static void ps_free_reg(void) +{ + regfree(&ps_re); +} + +/****************************************************************************/ + void get_query_type(struct st_query* q) { char save; @@ -2599,6 +3333,9 @@ int main(int argc, char **argv) { DBUG_ENTER("main"); + /* Use all time until exit if no explicit 'start_timer' */ + timer_start= timer_now(); + save_file[0]=0; TMPDIR[0]=0; memset(cons, 0, sizeof(cons)); @@ -2632,12 +3369,17 @@ int main(int argc, char **argv) if (manager_host) init_manager(); #endif + if (ps_protocol) + { + ps_protocol_enabled= 1; + ps_init_re(); + } if (!( mysql_init(&cur_con->mysql))) die("Failed in mysql_init()"); if (opt_compress) mysql_options(&cur_con->mysql,MYSQL_OPT_COMPRESS,NullS); mysql_options(&cur_con->mysql, MYSQL_OPT_LOCAL_INFILE, 0); - mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_NAME, "latin1"); + mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_NAME, charset_name); #ifdef HAVE_OPENSSL if (opt_use_ssl) @@ -2777,7 +3519,7 @@ int main(int argc, char **argv) require_file=0; break; case Q_ERROR: - global_expected_errors=get_ints(global_expected_errno,q); + global_expected_errors=get_errcodes(global_expected_errno,q); break; case Q_REQUIRE: get_file_name(save_file,q); @@ -2811,8 +3553,27 @@ int main(int argc, char **argv) (void) mysql_ping(&cur_con->mysql); break; case Q_EXEC: - (void) do_exec(q); + do_exec(q); + break; + case Q_START_TIMER: + /* Overwrite possible earlier start of timer */ + timer_start= timer_now(); + break; + case Q_END_TIMER: + /* End timer before ending mysqltest */ + timer_output(); + got_end_timer= TRUE; + break; + case Q_CHARACTER_SET: + set_charset(q); break; + case Q_DISABLE_PS_PROTOCOL: + ps_protocol_enabled= 0; + break; + case Q_ENABLE_PS_PROTOCOL: + ps_protocol_enabled= ps_protocol; + break; + case Q_EXIT: abort_flag= 1; break; @@ -2850,6 +3611,8 @@ int main(int argc, char **argv) printf("ok\n"); } + if (!got_end_timer) + timer_output(); /* No end_timer cmd, end it */ free_used_memory(); exit(error ? 1 : 0); return error ? 1 : 0; /* Keep compiler happy */ @@ -2903,6 +3666,45 @@ static int read_server_arguments(const char *name) return 0; } +/****************************************************************************\ + * + * A primitive timer that give results in milliseconds if the + * --timer-file=<filename> is given. The timer result is written + * to that file when the result is available. To not confuse + * mysql-test-run with an old obsolete result, we remove the file + * before executing any commands. The time we measure is + * + * - If no explicit 'start_timer' or 'end_timer' is given in the + * test case, the timer measure how long we execute in mysqltest. + * + * - If only 'start_timer' is given we measure how long we execute + * from that point until we terminate mysqltest. + * + * - If only 'end_timer' is given we measure how long we execute + * from that we enter mysqltest to the 'end_timer' is command is + * executed. + * + * - If both 'start_timer' and 'end_timer' are given we measure + * the time between executing the two commands. + * +\****************************************************************************/ + +static void timer_output(void) +{ + if (timer_file) + { + char buf[32], *end; + ulonglong timer= timer_now() - timer_start; + end= longlong2str(timer, buf, 10); + str_to_file(timer_file,buf, (int) (end-buf)); + } +} + +static ulonglong timer_now(void) +{ + return my_getsystime() / 10000; +} + /**************************************************************************** * Handle replacement of strings ****************************************************************************/ |