From 3bbeb698662357b7e81f872ff0bb668c8b05fe77 Mon Sep 17 00:00:00 2001 From: Magnus Svensson Date: Mon, 4 Aug 2008 21:54:44 +0200 Subject: Bug #38181 Please print more debug info when tests fail --- client/CMakeLists.txt | 4 +- client/Makefile.am | 4 +- client/mysqltest.c | 9063 ------------------- client/mysqltest.cc | 9101 ++++++++++++++++++++ libmysqld/examples/CMakeLists.txt | 2 +- libmysqld/examples/Makefile.am | 2 +- mysql-test/include/analyze-sync_with_master.test | 6 + .../include/analyze_failure_sync_with_master.test | 15 - mysql-test/include/mysqltest-x.inc | 1 + mysql-test/lib/mtr_cases.pm | 12 +- mysql-test/lib/mtr_report.pm | 50 +- mysql-test/mysql-test-run.pl | 184 +- mysql-test/r/mysqltest.result | 14 +- mysql-test/t/fix_priv_tables.test | 3 +- mysql-test/t/mysqltest.test | 20 +- mysql-test/t/openssl_1.test | 8 +- 16 files changed, 9355 insertions(+), 9134 deletions(-) delete mode 100644 client/mysqltest.c create mode 100644 client/mysqltest.cc create mode 100644 mysql-test/include/analyze-sync_with_master.test delete mode 100644 mysql-test/include/analyze_failure_sync_with_master.test diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index cbd4a1f306b..e96437d40d0 100755 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -32,8 +32,8 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ADD_EXECUTABLE(mysql completion_hash.cc mysql.cc readline.cc sql_string.cc ../mysys/my_conio.c) TARGET_LINK_LIBRARIES(mysql mysqlclient_notls wsock32) -ADD_EXECUTABLE(mysqltest mysqltest.c) -SET_SOURCE_FILES_PROPERTIES(mysqltest.c PROPERTIES COMPILE_FLAGS "-DTHREADS") +ADD_EXECUTABLE(mysqltest mysqltest.cc) +SET_SOURCE_FILES_PROPERTIES(mysqltest.cc PROPERTIES COMPILE_FLAGS "-DTHREADS") TARGET_LINK_LIBRARIES(mysqltest mysqlclient mysys regex wsock32 dbug) ADD_EXECUTABLE(mysqlcheck mysqlcheck.c) diff --git a/client/Makefile.am b/client/Makefile.am index 940766ac66c..94ad6c15d99 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -86,8 +86,8 @@ mysqlslap_LDADD = $(CXXLDFLAGS) $(CLIENT_THREAD_LIBS) \ $(LIBMYSQLCLIENT_LA) \ $(top_builddir)/mysys/libmysys.a -mysqltest_SOURCES= mysqltest.c -mysqltest_CFLAGS= -DTHREAD -UUNDEF_THREADS_HACK +mysqltest_SOURCES= mysqltest.cc +mysqltest_CXXFLAGS= -DTHREAD -UUNDEF_THREADS_HACK mysqltest_LDADD = $(CXXLDFLAGS) $(CLIENT_THREAD_LIBS) \ @CLIENT_EXTRA_LDFLAGS@ \ $(LIBMYSQLCLIENT_LA) \ diff --git a/client/mysqltest.c b/client/mysqltest.c deleted file mode 100644 index f700ea9479d..00000000000 --- a/client/mysqltest.c +++ /dev/null @@ -1,9063 +0,0 @@ -/* Copyright (C) 2000 MySQL AB - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -/* - mysqltest - - Tool used for executing a .test file - - See the "MySQL Test framework manual" for more information - http://dev.mysql.com/doc/mysqltest/en/index.html - - Please keep the test framework tools identical in all versions! - - Written by: - Sasha Pachev - Matt Wagner - Monty - Jani - Holyfoot -*/ - -#define MTEST_VERSION "3.3" - -#include "client_priv.h" -#include -#include -#include -#include -#include -#include -#include -#include "my_regex.h" /* Our own version of regex */ -#ifdef HAVE_SYS_WAIT_H -#include -#endif -#ifdef __WIN__ -#include -#endif -#include -#include - -#ifdef __WIN__ -#include -#define SIGNAL_FMT "exception 0x%x" -#else -#define SIGNAL_FMT "signal %d" -#endif - -/* Use cygwin for --exec and --system before 5.0 */ -#if MYSQL_VERSION_ID < 50000 -#define USE_CYGWIN -#endif - -#define MAX_VAR_NAME_LENGTH 256 -#define MAX_COLUMNS 256 -#define MAX_EMBEDDED_SERVER_ARGS 64 -#define MAX_DELIMITER_LENGTH 16 - -/* Flags controlling send and reap */ -#define QUERY_SEND_FLAG 1 -#define QUERY_REAP_FLAG 2 - -enum { - OPT_SKIP_SAFEMALLOC=OPT_MAX_CLIENT_OPTION, - OPT_PS_PROTOCOL, OPT_SP_PROTOCOL, OPT_CURSOR_PROTOCOL, OPT_VIEW_PROTOCOL, - OPT_MAX_CONNECT_RETRIES, OPT_MARK_PROGRESS, OPT_LOG_DIR, OPT_TAIL_LINES -}; - -static int record= 0, opt_sleep= -1; -static char *opt_db= 0, *opt_pass= 0; -const char *opt_user= 0, *opt_host= 0, *unix_sock= 0, *opt_basedir= "./"; -const char *opt_logdir= ""; -const char *opt_include= 0, *opt_charsets_dir; -static int opt_port= 0; -static int opt_max_connect_retries; -static my_bool opt_compress= 0, silent= 0, verbose= 0; -static my_bool debug_info_flag= 0, debug_check_flag= 0; -static my_bool tty_password= 0; -static my_bool opt_mark_progress= 0; -static my_bool ps_protocol= 0, ps_protocol_enabled= 0; -static my_bool sp_protocol= 0, sp_protocol_enabled= 0; -static my_bool view_protocol= 0, view_protocol_enabled= 0; -static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; -static my_bool parsing_disabled= 0; -static my_bool display_result_vertically= FALSE, - display_metadata= FALSE, display_result_sorted= FALSE; -static my_bool disable_query_log= 0, disable_result_log= 0; -static my_bool disable_warnings= 0; -static my_bool disable_info= 1; -static my_bool abort_on_error= 1; -static my_bool server_initialized= 0; -static my_bool is_windows= 0; -static char **default_argv; -static const char *load_default_groups[]= { "mysqltest", "client", 0 }; -static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer; - -static uint start_lineno= 0; /* Start line of current command */ -static uint my_end_arg= 0; - -/* Number of lines of the result to include in failure report */ -static uint opt_tail_lines= 0; - -static char delimiter[MAX_DELIMITER_LENGTH]= ";"; -static uint delimiter_length= 1; - -static char TMPDIR[FN_REFLEN]; - -/* Block stack */ -enum block_cmd { - cmd_none, - cmd_if, - cmd_while -}; - -struct st_block -{ - int line; /* Start line of block */ - my_bool ok; /* Should block be executed */ - enum block_cmd cmd; /* Command owning the block */ -}; - -static struct st_block block_stack[32]; -static struct st_block *cur_block, *block_stack_end; - -/* Open file stack */ -struct st_test_file -{ - FILE* file; - const char *file_name; - uint lineno; /* Current line in file */ -}; - -static struct st_test_file file_stack[16]; -static struct st_test_file* cur_file; -static struct st_test_file* file_stack_end; - - -static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ - -static const char *embedded_server_groups[]= -{ - "server", - "embedded", - "mysqltest_SERVER", - NullS -}; - -static int embedded_server_arg_count=0; -static char *embedded_server_args[MAX_EMBEDDED_SERVER_ARGS]; - -/* - Timer related variables - See the timer_output() definition for details -*/ -static char *timer_file = NULL; -static ulonglong timer_start; -static void timer_output(void); -static ulonglong timer_now(void); - -static ulonglong progress_start= 0; - -/* Precompiled re's */ -static my_regex_t ps_re; /* the query can be run using PS protocol */ -static my_regex_t sp_re; /* the query can be run as a SP */ -static my_regex_t view_re; /* the query can be run as a view*/ - -static void init_re(void); -static int match_re(my_regex_t *, char *); -static void free_re(void); - -DYNAMIC_ARRAY q_lines; - -#include "sslopt-vars.h" - -struct -{ - int read_lines,current_line; -} parser; - -struct -{ - char file[FN_REFLEN]; - ulong pos; -} master_pos; - -/* if set, all results are concated and compared against this file */ -const char *result_file_name= 0; - -typedef struct st_var -{ - char *name; - int name_len; - char *str_val; - int str_val_len; - int int_val; - int alloced_len; - int int_dirty; /* do not update string if int is updated until first read */ - int alloced; - char *env_s; -} VAR; - -/*Perl/shell-like variable registers */ -VAR var_reg[10]; - -HASH var_hash; - -struct st_connection -{ - MYSQL mysql; - /* Used when creating views and sp, to avoid implicit commit */ - MYSQL* util_mysql; - char *name; - size_t name_len; - MYSQL_STMT* stmt; - -#ifdef EMBEDDED_LIBRARY - const char *cur_query; - int cur_query_len; - pthread_mutex_t mutex; - pthread_cond_t cond; - int query_done; -#endif /*EMBEDDED_LIBRARY*/ -}; -struct st_connection connections[128]; -struct st_connection* cur_con= NULL, *next_con, *connections_end; - -/* - List of commands in mysqltest - Must match the "command_names" array - Add new commands before Q_UNKNOWN! -*/ -enum enum_commands { - Q_CONNECTION=1, Q_QUERY, - Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, - Q_INC, Q_DEC, - Q_SOURCE, Q_DISCONNECT, - Q_LET, Q_ECHO, - Q_WHILE, Q_END_BLOCK, - Q_SYSTEM, Q_RESULT, - Q_REQUIRE, Q_SAVE_MASTER_POS, - Q_SYNC_WITH_MASTER, - Q_SYNC_SLAVE_WITH_MASTER, - Q_ERROR, - Q_SEND, Q_REAP, - Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN, - Q_PING, Q_EVAL, - Q_RPL_PROBE, Q_ENABLE_RPL_PARSE, - Q_DISABLE_RPL_PARSE, Q_EVAL_RESULT, - Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG, - Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG, - Q_WAIT_FOR_SLAVE_TO_STOP, - Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS, - Q_ENABLE_INFO, Q_DISABLE_INFO, - Q_ENABLE_METADATA, Q_DISABLE_METADATA, - Q_EXEC, Q_DELIMITER, - Q_DISABLE_ABORT_ON_ERROR, Q_ENABLE_ABORT_ON_ERROR, - Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, - Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_SORTED_RESULT, - Q_START_TIMER, Q_END_TIMER, - Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, - Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, - Q_IF, - Q_DISABLE_PARSING, Q_ENABLE_PARSING, - Q_REPLACE_REGEX, Q_REMOVE_FILE, Q_FILE_EXIST, - Q_WRITE_FILE, Q_COPY_FILE, Q_PERL, Q_DIE, Q_EXIT, Q_SKIP, - Q_CHMOD_FILE, Q_APPEND_FILE, Q_CAT_FILE, Q_DIFF_FILES, - Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, - Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, - - Q_UNKNOWN, /* Unknown command. */ - Q_COMMENT, /* Comments, ignored. */ - Q_COMMENT_WITH_COMMAND -}; - - -const char *command_names[]= -{ - "connection", - "query", - "connect", - "sleep", - "real_sleep", - "inc", - "dec", - "source", - "disconnect", - "let", - "echo", - "while", - "end", - "system", - "result", - "require", - "save_master_pos", - "sync_with_master", - "sync_slave_with_master", - "error", - "send", - "reap", - "dirty_close", - "replace_result", - "replace_column", - "ping", - "eval", - "rpl_probe", - "enable_rpl_parse", - "disable_rpl_parse", - "eval_result", - /* Enable/disable that the _query_ is logged to result file */ - "enable_query_log", - "disable_query_log", - /* Enable/disable that the _result_ from a query is logged to result file */ - "enable_result_log", - "disable_result_log", - "wait_for_slave_to_stop", - "enable_warnings", - "disable_warnings", - "enable_info", - "disable_info", - "enable_metadata", - "disable_metadata", - "exec", - "delimiter", - "disable_abort_on_error", - "enable_abort_on_error", - "vertical_results", - "horizontal_results", - "query_vertical", - "query_horizontal", - "sorted_result", - "start_timer", - "end_timer", - "character_set", - "disable_ps_protocol", - "enable_ps_protocol", - "disable_reconnect", - "enable_reconnect", - "if", - "disable_parsing", - "enable_parsing", - "replace_regex", - "remove_file", - "file_exists", - "write_file", - "copy_file", - "perl", - "die", - - /* Don't execute any more commands, compare result */ - "exit", - "skip", - "chmod", - "append_file", - "cat_file", - "diff_files", - "send_quit", - "change_user", - "mkdir", - "rmdir", - "send_shutdown", - "shutdown_server", - - 0 -}; - - -/* - The list of error codes to --error are stored in an internal array of - structs. This struct can hold numeric SQL error codes, error names 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 returns 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 -}; - -struct st_match_err -{ - enum match_err_type type; - union - { - uint errnum; - char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ - } code; -}; - -struct st_expected_errors -{ - struct st_match_err err[10]; - uint count; -}; -static struct st_expected_errors saved_expected_errors; - -struct st_command -{ - char *query, *query_buf,*first_argument,*last_argument,*end; - int first_word_len, query_len; - my_bool abort_on_error; - struct st_expected_errors expected_errors; - char require_file[FN_REFLEN]; - enum enum_commands type; -}; - -TYPELIB command_typelib= {array_elements(command_names),"", - command_names, 0}; - -DYNAMIC_STRING ds_res, ds_progress, ds_warning_messages; - -char builtin_echo[FN_REFLEN]; - -void die(const char *fmt, ...) - ATTRIBUTE_FORMAT(printf, 1, 2); -void abort_not_supported_test(const char *fmt, ...) - ATTRIBUTE_FORMAT(printf, 1, 2); -void verbose_msg(const char *fmt, ...) - ATTRIBUTE_FORMAT(printf, 1, 2); -void warning_msg(const char *fmt, ...) - ATTRIBUTE_FORMAT(printf, 1, 2); -void log_msg(const char *fmt, ...) - ATTRIBUTE_FORMAT(printf, 1, 2); - -VAR* var_from_env(const char *, const char *); -VAR* var_init(VAR* v, const char *name, int name_len, const char *val, - int val_len); -void var_free(void* v); -VAR* var_get(const char *var_name, const char** var_name_end, - my_bool raw, my_bool ignore_not_existing); -void eval_expr(VAR* v, const char *p, const char** p_end); -my_bool match_delimiter(int c, const char *delim, uint length); -void dump_result_to_reject_file(char *buf, int size); -void dump_result_to_log_file(char *buf, int size); -void dump_warning_messages(); -void dump_progress(); - -void do_eval(DYNAMIC_STRING *query_eval, const char *query, - const char *query_end, my_bool pass_through_escape_chars); -void str_to_file(const char *fname, char *str, int size); -void str_to_file2(const char *fname, char *str, int size, my_bool append); - -void fix_win_paths(const char *val, int len); - -#ifdef __WIN__ -void free_tmp_sh_file(); -void free_win_path_patterns(); -#endif - - -/* For replace_column */ -static char *replace_column[MAX_COLUMNS]; -static uint max_replace_column= 0; -void do_get_replace_column(struct st_command*); -void free_replace_column(); - -/* For replace */ -void do_get_replace(struct st_command *command); -void free_replace(); - -/* For replace_regex */ -void do_get_replace_regex(struct st_command *command); -void free_replace_regex(); - - -void free_all_replace(){ - free_replace(); - free_replace_regex(); - free_replace_column(); -} - - -/* Disable functions that only exist in MySQL 4.0 */ -#if MYSQL_VERSION_ID < 40000 -void mysql_enable_rpl_parse(MYSQL* mysql __attribute__((unused))) {} -void mysql_disable_rpl_parse(MYSQL* mysql __attribute__((unused))) {} -int mysql_rpl_parse_enabled(MYSQL* mysql __attribute__((unused))) { return 1; } -my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; } -#endif -void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, - int len); -void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); -void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val); -void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING* ds_input); - -void handle_error(struct st_command*, - unsigned int err_errno, const char *err_error, - const char *err_sqlstate, DYNAMIC_STRING *ds); -void handle_no_error(struct st_command*); - -#ifdef EMBEDDED_LIBRARY - -/* attributes of the query thread */ -pthread_attr_t cn_thd_attrib; - -/* - send_one_query executes query in separate thread, which is - necessary in embedded library to run 'send' in proper way. - This implementation doesn't handle errors returned - by mysql_send_query. It's technically possible, though - I don't see where it is needed. -*/ -pthread_handler_t send_one_query(void *arg) -{ - struct st_connection *cn= (struct st_connection*)arg; - - mysql_thread_init(); - VOID(mysql_send_query(&cn->mysql, cn->cur_query, cn->cur_query_len)); - - mysql_thread_end(); - pthread_mutex_lock(&cn->mutex); - cn->query_done= 1; - VOID(pthread_cond_signal(&cn->cond)); - pthread_mutex_unlock(&cn->mutex); - pthread_exit(0); - return 0; -} - -static int do_send_query(struct st_connection *cn, const char *q, int q_len, - int flags) -{ - pthread_t tid; - - if (flags & QUERY_REAP_FLAG) - return mysql_send_query(&cn->mysql, q, q_len); - - if (pthread_mutex_init(&cn->mutex, NULL) || - pthread_cond_init(&cn->cond, NULL)) - die("Error in the thread library"); - - cn->cur_query= q; - cn->cur_query_len= q_len; - cn->query_done= 0; - if (pthread_create(&tid, &cn_thd_attrib, send_one_query, (void*)cn)) - die("Cannot start new thread for query"); - - return 0; -} - -static void wait_query_thread_end(struct st_connection *con) -{ - if (!con->query_done) - { - pthread_mutex_lock(&con->mutex); - while (!con->query_done) - pthread_cond_wait(&con->cond, &con->mutex); - pthread_mutex_unlock(&con->mutex); - } -} - -#else /*EMBEDDED_LIBRARY*/ - -#define do_send_query(cn,q,q_len,flags) mysql_send_query(&cn->mysql, q, q_len) - -#endif /*EMBEDDED_LIBRARY*/ - -void do_eval(DYNAMIC_STRING *query_eval, const char *query, - const char *query_end, my_bool pass_through_escape_chars) -{ - const char *p; - register char c, next_c; - register int escaped = 0; - VAR *v; - DBUG_ENTER("do_eval"); - - for (p= query; (c= *p) && p < query_end; ++p) - { - switch(c) { - case '$': - if (escaped) - { - escaped= 0; - dynstr_append_mem(query_eval, p, 1); - } - else - { - if (!(v= var_get(p, &p, 0, 0))) - die("Bad variable in eval"); - dynstr_append_mem(query_eval, v->str_val, v->str_val_len); - } - break; - case '\\': - next_c= *(p+1); - if (escaped) - { - escaped= 0; - dynstr_append_mem(query_eval, p, 1); - } - else if (next_c == '\\' || next_c == '$' || next_c == '"') - { - /* Set escaped only if next char is \, " or $ */ - escaped= 1; - - if (pass_through_escape_chars) - { - /* The escape char should be added to the output string. */ - dynstr_append_mem(query_eval, p, 1); - } - } - else - dynstr_append_mem(query_eval, p, 1); - break; - default: - escaped= 0; - dynstr_append_mem(query_eval, p, 1); - break; - } - } -#ifdef __WIN__ - fix_win_paths(query_eval->str, query_eval->length); -#endif - DBUG_VOID_RETURN; -} - - -/* - Run query and dump the result to stdout in vertical format - - NOTE! This function should be safe to call when an error - has occured and thus any further errors will be ignored(although logged) - - SYNOPSIS - show_query - mysql - connection to use - query - query to run - -*/ - -static void show_query(MYSQL* mysql, const char* query) -{ - MYSQL_RES* res; - DBUG_ENTER("show_query"); - - if (!mysql) - DBUG_VOID_RETURN; - - if (mysql_query(mysql, query)) - { - log_msg("Error running query '%s': %d %s", - query, mysql_errno(mysql), mysql_error(mysql)); - DBUG_VOID_RETURN; - } - - if ((res= mysql_store_result(mysql)) == NULL) - { - /* No result set returned */ - DBUG_VOID_RETURN; - } - - { - MYSQL_ROW row; - unsigned int i; - unsigned int row_num= 0; - unsigned int num_fields= mysql_num_fields(res); - MYSQL_FIELD *fields= mysql_fetch_fields(res); - - fprintf(stderr, "=== %s ===\n", query); - while ((row= mysql_fetch_row(res))) - { - unsigned long *lengths= mysql_fetch_lengths(res); - row_num++; - - fprintf(stderr, "---- %d. ----\n", row_num); - for(i= 0; i < num_fields; i++) - { - fprintf(stderr, "%s\t%.*s\n", - fields[i].name, - (int)lengths[i], row[i] ? row[i] : "NULL"); - } - } - for (i= 0; i < strlen(query)+8; i++) - fprintf(stderr, "="); - fprintf(stderr, "\n\n"); - } - mysql_free_result(res); - - DBUG_VOID_RETURN; -} - - -/* - Show any warnings just before the error. Since the last error - is added to the warning stack, only print @@warning_count-1 warnings. - - NOTE! This function should be safe to call when an error - has occured and this any further errors will be ignored(although logged) - - SYNOPSIS - show_warnings_before_error - mysql - connection to use - -*/ - -static void show_warnings_before_error(MYSQL* mysql) -{ - MYSQL_RES* res; - const char* query= "SHOW WARNINGS"; - DBUG_ENTER("show_warnings_before_error"); - - if (!mysql) - DBUG_VOID_RETURN; - - if (mysql_query(mysql, query)) - { - log_msg("Error running query '%s': %d %s", - query, mysql_errno(mysql), mysql_error(mysql)); - DBUG_VOID_RETURN; - } - - if ((res= mysql_store_result(mysql)) == NULL) - { - /* No result set returned */ - DBUG_VOID_RETURN; - } - - if (mysql_num_rows(res) <= 1) - { - /* Don't display the last row, it's "last error" */ - } - else - { - MYSQL_ROW row; - unsigned int row_num= 0; - unsigned int num_fields= mysql_num_fields(res); - - fprintf(stderr, "\nWarnings from just before the error:\n"); - while ((row= mysql_fetch_row(res))) - { - unsigned int i; - unsigned long *lengths= mysql_fetch_lengths(res); - - if (++row_num >= mysql_num_rows(res)) - { - /* Don't display the last row, it's "last error" */ - break; - } - - for(i= 0; i < num_fields; i++) - { - fprintf(stderr, "%.*s ", (int)lengths[i], - row[i] ? row[i] : "NULL"); - } - fprintf(stderr, "\n"); - } - } - mysql_free_result(res); - - DBUG_VOID_RETURN; -} - - -enum arg_type -{ - ARG_STRING, - ARG_REST -}; - -struct command_arg { - const char *argname; /* Name of argument */ - enum arg_type type; /* Type of argument */ - my_bool required; /* Argument required */ - DYNAMIC_STRING *ds; /* Storage for argument */ - const char *description; /* Description of the argument */ -}; - - -void check_command_args(struct st_command *command, - const char *arguments, - const struct command_arg *args, - int num_args, const char delimiter_arg) -{ - int i; - const char *ptr= arguments; - const char *start; - DBUG_ENTER("check_command_args"); - DBUG_PRINT("enter", ("num_args: %d", num_args)); - - for (i= 0; i < num_args; i++) - { - const struct command_arg *arg= &args[i]; - - switch (arg->type) { - /* A string */ - case ARG_STRING: - /* Skip leading spaces */ - while (*ptr && *ptr == ' ') - ptr++; - start= ptr; - /* Find end of arg, terminated by "delimiter_arg" */ - while (*ptr && *ptr != delimiter_arg) - ptr++; - if (ptr > start) - { - init_dynamic_string(arg->ds, 0, ptr-start, 32); - do_eval(arg->ds, start, ptr, FALSE); - } - else - { - /* Empty string */ - init_dynamic_string(arg->ds, "", 0, 0); - } - command->last_argument= (char*)ptr; - - /* Step past the delimiter */ - if (*ptr && *ptr == delimiter_arg) - ptr++; - DBUG_PRINT("info", ("val: %s", arg->ds->str)); - break; - - /* Rest of line */ - case ARG_REST: - start= ptr; - init_dynamic_string(arg->ds, 0, command->query_len, 256); - do_eval(arg->ds, start, command->end, FALSE); - command->last_argument= command->end; - DBUG_PRINT("info", ("val: %s", arg->ds->str)); - break; - - default: - DBUG_ASSERT("Unknown argument type"); - break; - } - - /* Check required arg */ - if (arg->ds->length == 0 && arg->required) - die("Missing required argument '%s' to command '%.*s'", arg->argname, - command->first_word_len, command->query); - - } - /* Check for too many arguments passed */ - ptr= command->last_argument; - while(ptr <= command->end) - { - if (*ptr && *ptr != ' ') - die("Extra argument '%s' passed to '%.*s'", - ptr, command->first_word_len, command->query); - ptr++; - } - DBUG_VOID_RETURN; -} - - -void handle_command_error(struct st_command *command, uint error) -{ - DBUG_ENTER("handle_command_error"); - DBUG_PRINT("enter", ("error: %d", error)); - if (error != 0) - { - uint i; - - if (command->abort_on_error) - die("command \"%.*s\" failed with error %d", - command->first_word_len, command->query, error); - for (i= 0; i < command->expected_errors.count; i++) - { - DBUG_PRINT("info", ("expected error: %d", - command->expected_errors.err[i].code.errnum)); - if ((command->expected_errors.err[i].type == ERR_ERRNO) && - (command->expected_errors.err[i].code.errnum == error)) - { - DBUG_PRINT("info", ("command \"%.*s\" failed with expected error: %d", - command->first_word_len, command->query, error)); - DBUG_VOID_RETURN; - } - } - die("command \"%.*s\" failed with wrong error: %d", - command->first_word_len, command->query, error); - } - else if (command->expected_errors.err[0].type == ERR_ERRNO && - command->expected_errors.err[0].code.errnum != 0) - { - /* Error code we wanted was != 0, i.e. not an expected success */ - die("command \"%.*s\" succeeded - should have failed with errno %d...", - command->first_word_len, command->query, - command->expected_errors.err[0].code.errnum); - } - DBUG_VOID_RETURN; -} - - -void close_connections() -{ - DBUG_ENTER("close_connections"); - for (--next_con; next_con >= connections; --next_con) - { - if (next_con->stmt) - mysql_stmt_close(next_con->stmt); - next_con->stmt= 0; - mysql_close(&next_con->mysql); - if (next_con->util_mysql) - mysql_close(next_con->util_mysql); - my_free(next_con->name, MYF(MY_ALLOW_ZERO_PTR)); - } - DBUG_VOID_RETURN; -} - - -void close_statements() -{ - struct st_connection *con; - DBUG_ENTER("close_statements"); - for (con= connections; con < next_con; con++) - { - if (con->stmt) - mysql_stmt_close(con->stmt); - con->stmt= 0; - } - DBUG_VOID_RETURN; -} - - -void close_files() -{ - DBUG_ENTER("close_files"); - for (; cur_file >= file_stack; cur_file--) - { - if (cur_file->file && cur_file->file != stdin) - { - DBUG_PRINT("info", ("closing file: %s", cur_file->file_name)); - my_fclose(cur_file->file, MYF(0)); - } - my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); - cur_file->file_name= 0; - } - DBUG_VOID_RETURN; -} - - -void free_used_memory() -{ - uint i; - DBUG_ENTER("free_used_memory"); - - close_connections(); - close_files(); - hash_free(&var_hash); - - for (i= 0 ; i < q_lines.elements ; i++) - { - struct st_command **q= dynamic_element(&q_lines, i, struct st_command**); - my_free((*q)->query_buf,MYF(MY_ALLOW_ZERO_PTR)); - my_free((*q),MYF(0)); - } - for (i= 0; i < 10; i++) - { - if (var_reg[i].alloced_len) - my_free(var_reg[i].str_val, MYF(MY_WME)); - } - while (embedded_server_arg_count > 1) - my_free(embedded_server_args[--embedded_server_arg_count],MYF(0)); - delete_dynamic(&q_lines); - dynstr_free(&ds_res); - dynstr_free(&ds_progress); - dynstr_free(&ds_warning_messages); - free_all_replace(); - my_free(opt_pass,MYF(MY_ALLOW_ZERO_PTR)); - free_defaults(default_argv); - free_re(); -#ifdef __WIN__ - free_tmp_sh_file(); - free_win_path_patterns(); -#endif - - /* Only call mysql_server_end if mysql_server_init has been called */ - if (server_initialized) - mysql_server_end(); - - /* Don't use DBUG after mysql_server_end() */ - return; -} - - -static void cleanup_and_exit(int exit_code) -{ - free_used_memory(); - my_end(my_end_arg); - - if (!silent) { - switch (exit_code) { - case 1: - printf("not ok\n"); - break; - case 0: - printf("ok\n"); - break; - case 62: - printf("skipped\n"); - break; - default: - printf("unknown exit code: %d\n", exit_code); - DBUG_ASSERT(0); - } - } - - exit(exit_code); -} - -void die(const char *fmt, ...) -{ - static int dying= 0; - va_list args; - DBUG_ENTER("die"); - DBUG_PRINT("enter", ("start_lineno: %d", start_lineno)); - - /* - Protect against dying twice - first time 'die' is called, try to write log files - second time, just exit - */ - if (dying) - cleanup_and_exit(1); - dying= 1; - - /* Print the error message */ - fprintf(stderr, "mysqltest: "); - if (cur_file && cur_file != file_stack) - fprintf(stderr, "In included file \"%s\": ", - cur_file->file_name); - if (start_lineno > 0) - fprintf(stderr, "At line %u: ", start_lineno); - if (fmt) - { - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - } - else - fprintf(stderr, "unknown error"); - fprintf(stderr, "\n"); - fflush(stderr); - - /* Show results from queries just before failure */ - if (ds_res.length && opt_tail_lines) - { - int tail_lines= opt_tail_lines; - char* show_from= ds_res.str + ds_res.length - 1; - while(show_from > ds_res.str && tail_lines > 0 ) - { - show_from--; - if (*show_from == '\n') - tail_lines--; - } - fprintf(stderr, "\nThe result from queries just before the failure was:\n"); - if (show_from > ds_res.str) - fprintf(stderr, "< snip >"); - fprintf(stderr, "%s", show_from); - fflush(stderr); - } - - /* Dump the result that has been accumulated so far to .log file */ - if (result_file_name && ds_res.length) - dump_result_to_log_file(ds_res.str, ds_res.length); - - /* Dump warning messages */ - if (result_file_name && ds_warning_messages.length) - dump_warning_messages(); - - /* - Help debugging by displaying any warnings that might have - been produced prior to the error - */ - if (cur_con) - show_warnings_before_error(&cur_con->mysql); - - cleanup_and_exit(1); -} - - -void abort_not_supported_test(const char *fmt, ...) -{ - va_list args; - struct st_test_file* err_file= cur_file; - DBUG_ENTER("abort_not_supported_test"); - - /* Print include filestack */ - fprintf(stderr, "The test '%s' is not supported by this installation\n", - file_stack->file_name); - fprintf(stderr, "Detected in file %s at line %d\n", - err_file->file_name, err_file->lineno); - while (err_file != file_stack) - { - err_file--; - fprintf(stderr, "included from %s at line %d\n", - err_file->file_name, err_file->lineno); - } - - /* Print error message */ - va_start(args, fmt); - if (fmt) - { - fprintf(stderr, "reason: "); - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); - fflush(stderr); - } - va_end(args); - - cleanup_and_exit(62); -} - - -void abort_not_in_this_version() -{ - die("Not available in this version of mysqltest"); -} - - -void verbose_msg(const char *fmt, ...) -{ - va_list args; - DBUG_ENTER("verbose_msg"); - if (!verbose) - DBUG_VOID_RETURN; - - va_start(args, fmt); - fprintf(stderr, "mysqltest: "); - if (cur_file && cur_file != file_stack) - fprintf(stderr, "In included file \"%s\": ", - cur_file->file_name); - if (start_lineno != 0) - fprintf(stderr, "At line %u: ", start_lineno); - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); - va_end(args); - - DBUG_VOID_RETURN; -} - - -void warning_msg(const char *fmt, ...) -{ - va_list args; - char buff[512]; - size_t len; - DBUG_ENTER("warning_msg"); - - va_start(args, fmt); - dynstr_append(&ds_warning_messages, "mysqltest: "); - if (start_lineno != 0) - { - dynstr_append(&ds_warning_messages, "Warning detected "); - if (cur_file && cur_file != file_stack) - { - len= my_snprintf(buff, sizeof(buff), "in included file %s ", - cur_file->file_name); - dynstr_append_mem(&ds_warning_messages, - buff, len); - } - len= my_snprintf(buff, sizeof(buff), "at line %d: ", - start_lineno); - dynstr_append_mem(&ds_warning_messages, - buff, len); - } - - len= my_vsnprintf(buff, sizeof(buff), fmt, args); - dynstr_append_mem(&ds_warning_messages, buff, len); - - dynstr_append(&ds_warning_messages, "\n"); - va_end(args); - - DBUG_VOID_RETURN; -} - - -void log_msg(const char *fmt, ...) -{ - va_list args; - char buff[1024]; - size_t len; - DBUG_ENTER("log_msg"); - - va_start(args, fmt); - len= my_vsnprintf(buff, sizeof(buff)-1, fmt, args); - va_end(args); - - dynstr_append_mem(&ds_res, buff, len); - dynstr_append(&ds_res, "\n"); - - DBUG_VOID_RETURN; -} - - -/* - Read a file and append it to ds - - SYNOPSIS - cat_file - ds - pointer to dynamic string where to add the files content - filename - name of the file to read - -*/ - -void cat_file(DYNAMIC_STRING* ds, const char* filename) -{ - int fd; - uint len; - char buff[512]; - - if ((fd= my_open(filename, O_RDONLY, MYF(0))) < 0) - die("Failed to open file '%s'", filename); - while((len= my_read(fd, (uchar*)&buff, - sizeof(buff), MYF(0))) > 0) - { - char *p= buff, *start= buff; - while (p < buff+len) - { - /* Convert cr/lf to lf */ - if (*p == '\r' && *(p+1) && *(p+1)== '\n') - { - /* Add fake newline instead of cr and output the line */ - *p= '\n'; - p++; /* Step past the "fake" newline */ - dynstr_append_mem(ds, start, p-start); - p++; /* Step past the "fake" newline */ - start= p; - } - else - p++; - } - /* Output any chars that migh be left */ - dynstr_append_mem(ds, start, p-start); - } - my_close(fd, MYF(0)); -} - - -/* - Run the specified command with popen - - SYNOPSIS - run_command - cmd - command to execute(should be properly quoted - ds_res- pointer to dynamic string where to store the result - -*/ - -static int run_command(char* cmd, - DYNAMIC_STRING *ds_res) -{ - char buf[512]= {0}; - FILE *res_file; - int error; - - if (!(res_file= popen(cmd, "r"))) - die("popen(\"%s\", \"r\") failed", cmd); - - while (fgets(buf, sizeof(buf), res_file)) - { - DBUG_PRINT("info", ("buf: %s", buf)); - if(ds_res) - { - /* Save the output of this command in the supplied string */ - dynstr_append(ds_res, buf); - } - else - { - /* Print it directly on screen */ - fprintf(stdout, "%s", buf); - } - } - - error= pclose(res_file); - return WEXITSTATUS(error); -} - - -/* - Run the specified tool with variable number of arguments - - SYNOPSIS - run_tool - tool_path - the name of the tool to run - ds_res - pointer to dynamic string where to store the result - ... - variable number of arguments that will be properly - quoted and appended after the tool's name - -*/ - -static int run_tool(const char *tool_path, DYNAMIC_STRING *ds_res, ...) -{ - int ret; - const char* arg; - va_list args; - DYNAMIC_STRING ds_cmdline; - - DBUG_ENTER("run_tool"); - DBUG_PRINT("enter", ("tool_path: %s", tool_path)); - - if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN)) - die("Out of memory"); - - dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS); - dynstr_append(&ds_cmdline, " "); - - va_start(args, ds_res); - - while ((arg= va_arg(args, char *))) - { - /* Options should be os quoted */ - if (strncmp(arg, "--", 2) == 0) - dynstr_append_os_quoted(&ds_cmdline, arg, NullS); - else - dynstr_append(&ds_cmdline, arg); - dynstr_append(&ds_cmdline, " "); - } - - va_end(args); - -#ifdef __WIN__ - dynstr_append(&ds_cmdline, "\""); -#endif - - DBUG_PRINT("info", ("Running: %s", ds_cmdline.str)); - ret= run_command(ds_cmdline.str, ds_res); - DBUG_PRINT("exit", ("ret: %d", ret)); - dynstr_free(&ds_cmdline); - DBUG_RETURN(ret); -} - - -/* - Show the diff of two files using the systems builtin diff - command. If no such diff command exist, just dump the content - of the two files and inform about how to get "diff" - - SYNOPSIS - show_diff - ds - pointer to dynamic string where to add the diff(may be NULL) - filename1 - name of first file - filename2 - name of second file - -*/ - -void show_diff(DYNAMIC_STRING* ds, - const char* filename1, const char* filename2) -{ - - const char* diff_failed= 0; - DYNAMIC_STRING ds_tmp; - - if (init_dynamic_string(&ds_tmp, "", 256, 256)) - die("Out of memory"); - - /* First try with unified diff */ - if (run_tool("diff", - &ds_tmp, /* Get output from diff in ds_tmp */ - "-u", - filename1, - filename2, - "2>&1", - NULL) > 1) /* Most "diff" tools return >1 if error */ - { - dynstr_set(&ds_tmp, ""); - - /* Fallback to context diff with "diff -c" */ - if (run_tool("diff", - &ds_tmp, /* Get output from diff in ds_tmp */ - "-c", - filename1, - filename2, - "2>&1", - NULL) > 1) /* Most "diff" tools return >1 if error */ - { - dynstr_set(&ds_tmp, ""); - - /* Fallback to plain "diff" */ - if (run_tool("diff", - &ds_tmp, /* Get output from diff in ds_tmp */ - filename1, - filename2, - "2>&1", - NULL) > 1) /* Most "diff" tools return >1 if error */ - { - dynstr_set(&ds_tmp, ""); - - diff_failed= "Could not execute 'diff -u', 'diff -c' or 'diff'"; - } - } - } - - if (diff_failed) - { - /* - Fallback to dump both files to result file and inform - about installing "diff" - */ - dynstr_append(&ds_tmp, "\n"); - dynstr_append(&ds_tmp, diff_failed); - dynstr_append(&ds_tmp, -"\n" -"The two files differ but it was not possible to execute 'diff' in\n" -"order to show only the difference. Instead the whole content of the\n" -"two files was shown for you to diff manually.\n\n" -"To get a better report you should install 'diff' on your system, which you\n" -"for example can get from http://www.gnu.org/software/diffutils/diffutils.html\n" -#ifdef __WIN__ -"or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n" -#endif -"\n"); - - dynstr_append(&ds_tmp, " --- "); - dynstr_append(&ds_tmp, filename1); - dynstr_append(&ds_tmp, " >>>\n"); - cat_file(&ds_tmp, filename1); - dynstr_append(&ds_tmp, "<<<\n --- "); - dynstr_append(&ds_tmp, filename1); - dynstr_append(&ds_tmp, " >>>\n"); - cat_file(&ds_tmp, filename2); - dynstr_append(&ds_tmp, "<<<<\n"); - } - - if (ds) - { - /* Add the diff to output */ - dynstr_append_mem(ds, ds_tmp.str, ds_tmp.length); - } - else - { - /* Print diff directly to stdout */ - fprintf(stderr, "%s\n", ds_tmp.str); - } - - dynstr_free(&ds_tmp); - -} - - -enum compare_files_result_enum { - RESULT_OK= 0, - RESULT_CONTENT_MISMATCH= 1, - RESULT_LENGTH_MISMATCH= 2 -}; - -/* - Compare two files, given a fd to the first file and - name of the second file - - SYNOPSIS - compare_files2 - fd - Open file descriptor of the first file - filename2 - Name of second file - - RETURN VALUES - According to the values in "compare_files_result_enum" - -*/ - -int compare_files2(File fd, const char* filename2) -{ - int error= RESULT_OK; - File fd2; - uint len, len2; - char buff[512], buff2[512]; - - if ((fd2= my_open(filename2, O_RDONLY, MYF(0))) < 0) - { - my_close(fd, MYF(0)); - die("Failed to open second file: '%s'", filename2); - } - while((len= my_read(fd, (uchar*)&buff, - sizeof(buff), MYF(0))) > 0) - { - if ((len2= my_read(fd2, (uchar*)&buff2, - sizeof(buff2), MYF(0))) < len) - { - /* File 2 was smaller */ - error= RESULT_LENGTH_MISMATCH; - break; - } - if (len2 > len) - { - /* File 1 was smaller */ - error= RESULT_LENGTH_MISMATCH; - break; - } - if ((memcmp(buff, buff2, len))) - { - /* Content of this part differed */ - error= RESULT_CONTENT_MISMATCH; - break; - } - } - if (!error && my_read(fd2, (uchar*)&buff2, - sizeof(buff2), MYF(0)) > 0) - { - /* File 1 was smaller */ - error= RESULT_LENGTH_MISMATCH; - } - - my_close(fd2, MYF(0)); - - return error; -} - - -/* - Compare two files, given their filenames - - SYNOPSIS - compare_files - filename1 - Name of first file - filename2 - Name of second file - - RETURN VALUES - See 'compare_files2' - -*/ - -int compare_files(const char* filename1, const char* filename2) -{ - File fd; - int error; - - if ((fd= my_open(filename1, O_RDONLY, MYF(0))) < 0) - die("Failed to open first file: '%s'", filename1); - - error= compare_files2(fd, filename2); - - my_close(fd, MYF(0)); - - return error; -} - - -/* - Compare content of the string in ds to content of file fname - - SYNOPSIS - dyn_string_cmp - ds - Dynamic string containing the string o be compared - fname - Name of file to compare with - - RETURN VALUES - See 'compare_files2' -*/ - -int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) -{ - int error; - File fd; - char temp_file_path[FN_REFLEN]; - - DBUG_ENTER("dyn_string_cmp"); - DBUG_PRINT("enter", ("fname: %s", fname)); - - if ((fd= create_temp_file(temp_file_path, TMPDIR, - "tmp", O_CREAT | O_SHARE | O_RDWR, - MYF(MY_WME))) < 0) - die("Failed to create temporary file for ds"); - - /* Write ds to temporary file and set file pos to beginning*/ - if (my_write(fd, (uchar *) ds->str, ds->length, - MYF(MY_FNABP | MY_WME)) || - my_seek(fd, 0, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) - { - my_close(fd, MYF(0)); - /* Remove the temporary file */ - my_delete(temp_file_path, MYF(0)); - die("Failed to write file '%s'", temp_file_path); - } - - error= compare_files2(fd, fname); - - my_close(fd, MYF(0)); - /* Remove the temporary file */ - my_delete(temp_file_path, MYF(0)); - - DBUG_RETURN(error); -} - - -/* - Check the content of ds against result file - - SYNOPSIS - check_result - ds - content to be checked - - RETURN VALUES - error - the function will not return - -*/ - -void check_result(DYNAMIC_STRING* ds) -{ - const char* mess= "Result content mismatch\n"; - - DBUG_ENTER("check_result"); - DBUG_ASSERT(result_file_name); - DBUG_PRINT("enter", ("result_file_name: %s", result_file_name)); - - if (access(result_file_name, F_OK) != 0) - die("The specified result file does not exist: '%s'", result_file_name); - - switch (dyn_string_cmp(ds, result_file_name)) { - case RESULT_OK: - break; /* ok */ - case RESULT_LENGTH_MISMATCH: - mess= "Result length mismatch\n"; - /* Fallthrough */ - case RESULT_CONTENT_MISMATCH: - { - /* - Result mismatched, dump results to .reject file - and then show the diff - */ - char reject_file[FN_REFLEN]; - size_t reject_length; - dirname_part(reject_file, result_file_name, &reject_length); - - if (access(reject_file, W_OK) == 0) - { - /* Result file directory is writable, save reject file there */ - fn_format(reject_file, result_file_name, NULL, - ".reject", MY_REPLACE_EXT); - } - else - { - /* Put reject file in opt_logdir */ - fn_format(reject_file, result_file_name, opt_logdir, - ".reject", MY_REPLACE_DIR | MY_REPLACE_EXT); - } - str_to_file(reject_file, ds->str, ds->length); - - dynstr_set(ds, NULL); /* Don't create a .log file */ - - show_diff(NULL, result_file_name, reject_file); - die(mess); - break; - } - default: /* impossible */ - die("Unknown error code from dyn_string_cmp()"); - } - - DBUG_VOID_RETURN; -} - - -/* - Check the content of ds against a require file - If match fails, abort the test with special error code - indicating that test is not supported - - SYNOPSIS - check_require - ds - content to be checked - fname - name of file to check against - - RETURN VALUES - error - the function will not return - -*/ - -void check_require(DYNAMIC_STRING* ds, const char *fname) -{ - DBUG_ENTER("check_require"); - - if (dyn_string_cmp(ds, fname)) - { - char reason[FN_REFLEN]; - fn_format(reason, fname, "", "", MY_REPLACE_EXT | MY_REPLACE_DIR); - abort_not_supported_test("Test requires: '%s'", reason); - } - DBUG_VOID_RETURN; -} - - -/* - Remove surrounding chars from string - - Return 1 if first character is found but not last -*/ -static int strip_surrounding(char* str, char c1, char c2) -{ - char* ptr= str; - - /* Check if the first non space character is c1 */ - while(*ptr && my_isspace(charset_info, *ptr)) - ptr++; - if (*ptr == c1) - { - /* Replace it with a space */ - *ptr= ' '; - - /* Last non space charecter should be c2 */ - ptr= strend(str)-1; - while(*ptr && my_isspace(charset_info, *ptr)) - ptr--; - if (*ptr == c2) - { - /* Replace it with \0 */ - *ptr= 0; - } - else - { - /* Mismatch detected */ - return 1; - } - } - return 0; -} - - -static void strip_parentheses(struct st_command *command) -{ - if (strip_surrounding(command->first_argument, '(', ')')) - die("%.*s - argument list started with '%c' must be ended with '%c'", - command->first_word_len, command->query, '(', ')'); -} - - -static uchar *get_var_key(const uchar* var, size_t *len, - my_bool __attribute__((unused)) t) -{ - register char* key; - key = ((VAR*)var)->name; - *len = ((VAR*)var)->name_len; - return (uchar*)key; -} - - -VAR *var_init(VAR *v, const char *name, int name_len, const char *val, - int val_len) -{ - int val_alloc_len; - VAR *tmp_var; - if (!name_len && name) - name_len = strlen(name); - if (!val_len && val) - val_len = strlen(val) ; - val_alloc_len = val_len + 16; /* room to grow */ - if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(sizeof(*tmp_var) - + name_len+1, MYF(MY_WME)))) - die("Out of memory"); - - tmp_var->name = (name) ? (char*) tmp_var + sizeof(*tmp_var) : 0; - tmp_var->alloced = (v == 0); - - if (!(tmp_var->str_val = my_malloc(val_alloc_len+1, MYF(MY_WME)))) - die("Out of memory"); - - memcpy(tmp_var->name, name, name_len); - if (val) - { - memcpy(tmp_var->str_val, val, val_len); - tmp_var->str_val[val_len]= 0; - } - tmp_var->name_len = name_len; - tmp_var->str_val_len = val_len; - tmp_var->alloced_len = val_alloc_len; - tmp_var->int_val = (val) ? atoi(val) : 0; - tmp_var->int_dirty = 0; - tmp_var->env_s = 0; - return tmp_var; -} - - -void var_free(void *v) -{ - my_free(((VAR*) v)->str_val, MYF(MY_WME)); - if (((VAR*)v)->alloced) - my_free(v, MYF(MY_WME)); -} - - -VAR* var_from_env(const char *name, const char *def_val) -{ - const char *tmp; - VAR *v; - if (!(tmp = getenv(name))) - tmp = def_val; - - v = var_init(0, name, strlen(name), tmp, strlen(tmp)); - my_hash_insert(&var_hash, (uchar*)v); - return v; -} - - -VAR* var_get(const char *var_name, const char **var_name_end, my_bool raw, - my_bool ignore_not_existing) -{ - int digit; - VAR *v; - DBUG_ENTER("var_get"); - DBUG_PRINT("enter", ("var_name: %s",var_name)); - - if (*var_name != '$') - goto err; - digit = *++var_name - '0'; - if (digit < 0 || digit >= 10) - { - const char *save_var_name = var_name, *end; - uint length; - end = (var_name_end) ? *var_name_end : 0; - while (my_isvar(charset_info,*var_name) && var_name != end) - var_name++; - if (var_name == save_var_name) - { - if (ignore_not_existing) - DBUG_RETURN(0); - die("Empty variable"); - } - length= (uint) (var_name - save_var_name); - if (length >= MAX_VAR_NAME_LENGTH) - die("Too long variable name: %s", save_var_name); - - if (!(v = (VAR*) hash_search(&var_hash, (const uchar*) save_var_name, - length))) - { - char buff[MAX_VAR_NAME_LENGTH+1]; - strmake(buff, save_var_name, length); - v= var_from_env(buff, ""); - } - var_name--; /* Point at last character */ - } - else - v = var_reg + digit; - - if (!raw && v->int_dirty) - { - sprintf(v->str_val, "%d", v->int_val); - v->int_dirty = 0; - v->str_val_len = strlen(v->str_val); - } - if (var_name_end) - *var_name_end = var_name ; - DBUG_RETURN(v); -err: - if (var_name_end) - *var_name_end = 0; - die("Unsupported variable name: %s", var_name); - DBUG_RETURN(0); -} - - -VAR *var_obtain(const char *name, int len) -{ - VAR* v; - if ((v = (VAR*)hash_search(&var_hash, (const uchar *) name, len))) - return v; - v = var_init(0, name, len, "", 0); - my_hash_insert(&var_hash, (uchar*)v); - return v; -} - - -/* - - if variable starts with a $ it is regarded as a local test varable - - if not it is treated as a environment variable, and the corresponding - environment variable will be updated -*/ - -void var_set(const char *var_name, const char *var_name_end, - const char *var_val, const char *var_val_end) -{ - int digit, env_var= 0; - VAR *v; - DBUG_ENTER("var_set"); - DBUG_PRINT("enter", ("var_name: '%.*s' = '%.*s' (length: %d)", - (int) (var_name_end - var_name), var_name, - (int) (var_val_end - var_val), var_val, - (int) (var_val_end - var_val))); - - if (*var_name != '$') - env_var= 1; - else - var_name++; - - digit= *var_name - '0'; - if (!(digit < 10 && digit >= 0)) - { - v= var_obtain(var_name, (uint) (var_name_end - var_name)); - } - else - v= var_reg + digit; - - eval_expr(v, var_val, (const char**) &var_val_end); - - if (env_var) - { - char buf[1024], *old_env_s= v->env_s; - if (v->int_dirty) - { - sprintf(v->str_val, "%d", v->int_val); - v->int_dirty= 0; - v->str_val_len= strlen(v->str_val); - } - my_snprintf(buf, sizeof(buf), "%.*s=%.*s", - v->name_len, v->name, - v->str_val_len, v->str_val); - if (!(v->env_s= my_strdup(buf, MYF(MY_WME)))) - die("Out of memory"); - putenv(v->env_s); - my_free(old_env_s, MYF(MY_ALLOW_ZERO_PTR)); - } - DBUG_VOID_RETURN; -} - - -void var_set_string(const char* name, const char* value) -{ - var_set(name, name + strlen(name), value, value + strlen(value)); -} - - -void var_set_int(const char* name, int value) -{ - char buf[21]; - my_snprintf(buf, sizeof(buf), "%d", value); - var_set_string(name, buf); -} - - -/* - Store an integer (typically the returncode of the last SQL) - statement in the mysqltest builtin variable $mysql_errno -*/ - -void var_set_errno(int sql_errno) -{ - var_set_int("$mysql_errno", sql_errno); -} - - -/* - Update $mysql_get_server_version variable with version - of the currently connected server -*/ - -void var_set_mysql_get_server_version(MYSQL* mysql) -{ - var_set_int("$mysql_get_server_version", mysql_get_server_version(mysql)); -} - - -/* - Set variable from the result of a query - - SYNOPSIS - var_query_set() - var variable to set from query - query start of query string to execute - query_end end of the query string to execute - - - DESCRIPTION - let @ = `` - - Execute the query and assign the first row of result to var as - a tab separated strings - - Also assign each column of the result set to - variable "$_" - Thus the tab separated output can be read from $ and - and each individual column can be read as $_ - -*/ - -void var_query_set(VAR *var, const char *query, const char** query_end) -{ - char *end = (char*)((query_end && *query_end) ? - *query_end : query + strlen(query)); - MYSQL_RES *res; - MYSQL_ROW row; - MYSQL* mysql = &cur_con->mysql; - DYNAMIC_STRING ds_query; - DBUG_ENTER("var_query_set"); - LINT_INIT(res); - - while (end > query && *end != '`') - --end; - if (query == end) - die("Syntax error in query, missing '`'"); - ++query; - - /* Eval the query, thus replacing all environment variables */ - init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); - do_eval(&ds_query, query, end, FALSE); - - if (mysql_real_query(mysql, ds_query.str, ds_query.length)) - die("Error running query '%s': %d %s", ds_query.str, - mysql_errno(mysql), mysql_error(mysql)); - if (!(res= mysql_store_result(mysql))) - die("Query '%s' didn't return a result set", ds_query.str); - dynstr_free(&ds_query); - - if ((row= mysql_fetch_row(res)) && row[0]) - { - /* - Concatenate all fields in the first row with tab in between - and assign that string to the $variable - */ - DYNAMIC_STRING result; - uint i; - ulong *lengths; - - init_dynamic_string(&result, "", 512, 512); - lengths= mysql_fetch_lengths(res); - for (i= 0; i < mysql_num_fields(res); i++) - { - if (row[i]) - { - /* Add column to tab separated string */ - dynstr_append_mem(&result, row[i], lengths[i]); - } - dynstr_append_mem(&result, "\t", 1); - } - end= result.str + result.length-1; - eval_expr(var, result.str, (const char**) &end); - dynstr_free(&result); - } - else - eval_expr(var, "", 0); - - mysql_free_result(res); - DBUG_VOID_RETURN; -} - - -/* - Set variable from the result of a field in a query - - This function is useful when checking for a certain value - in the output from a query that can't be restricted to only - return some values. A very good example of that is most SHOW - commands. - - SYNOPSIS - var_set_query_get_value() - - DESCRIPTION - let $variable= query_get_value(,,); - - - The query that should be sent to the server - - Name of the column that holds the field be compared - against the expected value - - Number of the row that holds the field to be - compared against the expected value - -*/ - -void var_set_query_get_value(struct st_command *command, VAR *var) -{ - long row_no; - int col_no= -1; - MYSQL_RES* res; - MYSQL* mysql= &cur_con->mysql; - - static DYNAMIC_STRING ds_query; - static DYNAMIC_STRING ds_col; - static DYNAMIC_STRING ds_row; - const struct command_arg query_get_value_args[] = { - {"query", ARG_STRING, TRUE, &ds_query, "Query to run"}, - {"column name", ARG_STRING, TRUE, &ds_col, "Name of column"}, - {"row number", ARG_STRING, TRUE, &ds_row, "Number for row"} - }; - - DBUG_ENTER("var_set_query_get_value"); - LINT_INIT(res); - - strip_parentheses(command); - DBUG_PRINT("info", ("query: %s", command->query)); - check_command_args(command, command->first_argument, query_get_value_args, - sizeof(query_get_value_args)/sizeof(struct command_arg), - ','); - - DBUG_PRINT("info", ("query: %s", ds_query.str)); - DBUG_PRINT("info", ("col: %s", ds_col.str)); - - /* Convert row number to int */ - if (!str2int(ds_row.str, 10, (long) 0, (long) INT_MAX, &row_no)) - die("Invalid row number: '%s'", ds_row.str); - DBUG_PRINT("info", ("row: %s, row_no: %ld", ds_row.str, row_no)); - dynstr_free(&ds_row); - - /* Remove any surrounding "'s from the query - if there is any */ - if (strip_surrounding(ds_query.str, '"', '"')) - die("Mismatched \"'s around query '%s'", ds_query.str); - - /* Run the query */ - if (mysql_real_query(mysql, ds_query.str, ds_query.length)) - die("Error running query '%s': %d %s", ds_query.str, - mysql_errno(mysql), mysql_error(mysql)); - if (!(res= mysql_store_result(mysql))) - die("Query '%s' didn't return a result set", ds_query.str); - - { - /* Find column number from the given column name */ - uint i; - uint num_fields= mysql_num_fields(res); - MYSQL_FIELD *fields= mysql_fetch_fields(res); - - for (i= 0; i < num_fields; i++) - { - if (strcmp(fields[i].name, ds_col.str) == 0 && - strlen(fields[i].name) == ds_col.length) - { - col_no= i; - break; - } - } - if (col_no == -1) - { - mysql_free_result(res); - die("Could not find column '%s' in the result of '%s'", - ds_col.str, ds_query.str); - } - DBUG_PRINT("info", ("Found column %d with name '%s'", - i, fields[i].name)); - } - dynstr_free(&ds_col); - - { - /* Get the value */ - MYSQL_ROW row; - long rows= 0; - const char* value= "No such row"; - - while ((row= mysql_fetch_row(res))) - { - if (++rows == row_no) - { - - DBUG_PRINT("info", ("At row %ld, column %d is '%s'", - row_no, col_no, row[col_no])); - /* Found the row to get */ - if (row[col_no]) - value= row[col_no]; - else - value= "NULL"; - - break; - } - } - eval_expr(var, value, 0); - } - dynstr_free(&ds_query); - mysql_free_result(res); - - DBUG_VOID_RETURN; -} - - -void var_copy(VAR *dest, VAR *src) -{ - dest->int_val= src->int_val; - dest->int_dirty= src->int_dirty; - - /* Alloc/realloc data for str_val in dest */ - if (dest->alloced_len < src->alloced_len && - !(dest->str_val= dest->str_val - ? my_realloc(dest->str_val, src->alloced_len, MYF(MY_WME)) - : my_malloc(src->alloced_len, MYF(MY_WME)))) - die("Out of memory"); - else - dest->alloced_len= src->alloced_len; - - /* Copy str_val data to dest */ - dest->str_val_len= src->str_val_len; - if (src->str_val_len) - memcpy(dest->str_val, src->str_val, src->str_val_len); -} - - -void eval_expr(VAR *v, const char *p, const char **p_end) -{ - - DBUG_ENTER("eval_expr"); - DBUG_PRINT("enter", ("p: '%s'", p)); - - if (*p == '$') - { - VAR *vp; - if ((vp= var_get(p, p_end, 0, 0))) - var_copy(v, vp); - DBUG_VOID_RETURN; - } - - if (*p == '`') - { - var_query_set(v, p, p_end); - DBUG_VOID_RETURN; - } - - { - /* Check if this is a "let $var= query_get_value()" */ - const char* get_value_str= "query_get_value"; - const size_t len= strlen(get_value_str); - if (strncmp(p, get_value_str, len)==0) - { - struct st_command command; - memset(&command, 0, sizeof(command)); - command.query= (char*)p; - command.first_word_len= len; - command.first_argument= command.query + len; - command.end= (char*)*p_end; - var_set_query_get_value(&command, v); - DBUG_VOID_RETURN; - } - } - - { - int new_val_len = (p_end && *p_end) ? - (int) (*p_end - p) : (int) strlen(p); - if (new_val_len + 1 >= v->alloced_len) - { - static int MIN_VAR_ALLOC= 32; - v->alloced_len = (new_val_len < MIN_VAR_ALLOC - 1) ? - MIN_VAR_ALLOC : new_val_len + 1; - if (!(v->str_val = - v->str_val ? my_realloc(v->str_val, v->alloced_len+1, - MYF(MY_WME)) : - my_malloc(v->alloced_len+1, MYF(MY_WME)))) - die("Out of memory"); - } - v->str_val_len = new_val_len; - memcpy(v->str_val, p, new_val_len); - v->str_val[new_val_len] = 0; - v->int_val=atoi(p); - DBUG_PRINT("info", ("atoi on '%s', returns: %d", p, v->int_val)); - v->int_dirty=0; - } - DBUG_VOID_RETURN; -} - - -int open_file(const char *name) -{ - char buff[FN_REFLEN]; - size_t length; - DBUG_ENTER("open_file"); - DBUG_PRINT("enter", ("name: %s", name)); - - /* Extract path from current file and try it as base first */ - if (dirname_part(buff, cur_file->file_name, &length)) - { - strxmov(buff, buff, name, NullS); - if (access(buff, F_OK) == 0){ - DBUG_PRINT("info", ("The file exists")); - name= buff; - } - } - if (!test_if_hard_path(name)) - { - strxmov(buff, opt_basedir, name, NullS); - name=buff; - } - fn_format(buff, name, "", "", MY_UNPACK_FILENAME); - - if (cur_file == file_stack_end) - die("Source directives are nesting too deep"); - cur_file++; - if (!(cur_file->file = my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) - { - cur_file--; - die("Could not open '%s' for reading, errno: %d", buff, errno); - } - cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); - cur_file->lineno=1; - DBUG_RETURN(0); -} - - -/* - Source and execute the given file - - SYNOPSIS - do_source() - query called command - - DESCRIPTION - source - - Open the file and execute it - -*/ - -void do_source(struct st_command *command) -{ - static DYNAMIC_STRING ds_filename; - const struct command_arg source_args[] = { - { "filename", ARG_STRING, TRUE, &ds_filename, "File to source" } - }; - DBUG_ENTER("do_source"); - - check_command_args(command, command->first_argument, source_args, - sizeof(source_args)/sizeof(struct command_arg), - ' '); - - /* - If this file has already been sourced, don't source it again. - It's already available in the q_lines cache. - */ - if (parser.current_line < (parser.read_lines - 1)) - ; /* Do nothing */ - else - { - DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str)); - open_file(ds_filename.str); - } - - dynstr_free(&ds_filename); - return; -} - - -#if defined __WIN__ - -#ifdef USE_CYGWIN -/* Variables used for temporary sh files used for emulating Unix on Windows */ -char tmp_sh_name[64], tmp_sh_cmd[70]; -#endif - -void init_tmp_sh_file() -{ -#ifdef USE_CYGWIN - /* Format a name for the tmp sh file that is unique for this process */ - my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); - /* Format the command to execute in order to run the script */ - my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); -#endif -} - - -void free_tmp_sh_file() -{ -#ifdef USE_CYGWIN - my_delete(tmp_sh_name, MYF(0)); -#endif -} -#endif - - -FILE* my_popen(DYNAMIC_STRING *ds_cmd, const char *mode) -{ -#if defined __WIN__ && defined USE_CYGWIN - /* Dump the command into a sh script file and execute with popen */ - str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); - return popen(tmp_sh_cmd, mode); -#else - return popen(ds_cmd->str, mode); -#endif -} - - -static void init_builtin_echo(void) -{ -#ifdef __WIN__ - size_t echo_length; - - /* Look for "echo.exe" in same dir as mysqltest was started from */ - dirname_part(builtin_echo, my_progname, &echo_length); - fn_format(builtin_echo, ".\\echo.exe", - builtin_echo, "", MYF(MY_REPLACE_DIR)); - - /* Make sure echo.exe exists */ - if (access(builtin_echo, F_OK) != 0) - builtin_echo[0]= 0; - return; - -#else - - builtin_echo[0]= 0; - return; - -#endif -} - - -/* - Replace a substring - - SYNOPSIS - replace - ds_str The string to search and perform the replace in - search_str The string to search for - search_len Length of the string to search for - replace_str The string to replace with - replace_len Length of the string to replace with - - RETURN - 0 String replaced - 1 Could not find search_str in str -*/ - -static int replace(DYNAMIC_STRING *ds_str, - const char *search_str, ulong search_len, - const char *replace_str, ulong replace_len) -{ - DYNAMIC_STRING ds_tmp; - const char *start= strstr(ds_str->str, search_str); - if (!start) - return 1; - init_dynamic_string(&ds_tmp, "", - ds_str->length + replace_len, 256); - dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str); - dynstr_append_mem(&ds_tmp, replace_str, replace_len); - dynstr_append(&ds_tmp, start + search_len); - dynstr_set(ds_str, ds_tmp.str); - dynstr_free(&ds_tmp); - return 0; -} - - -/* - Execute given command. - - SYNOPSIS - do_exec() - query called command - - DESCRIPTION - exec - - Execute the text between exec and end of line in a subprocess. - The error code returned from the subprocess is checked against the - expected error array, previously set with the --error command. - It can thus be used to execute a command that shall fail. - - NOTE - Although mysqltest is executed from cygwin shell, the command will be - executed in "cmd.exe". Thus commands like "rm" etc can NOT be used, use - mysqltest commmand(s) like "remove_file" for that -*/ - -void do_exec(struct st_command *command) -{ - int error; - char buf[512]; - FILE *res_file; - char *cmd= command->first_argument; - DYNAMIC_STRING ds_cmd; - DBUG_ENTER("do_exec"); - DBUG_PRINT("enter", ("cmd: '%s'", cmd)); - - /* Skip leading space */ - while (*cmd && my_isspace(charset_info, *cmd)) - cmd++; - if (!*cmd) - die("Missing argument in exec"); - command->last_argument= command->end; - - init_dynamic_string(&ds_cmd, 0, command->query_len+256, 256); - /* Eval the command, thus replacing all environment variables */ - do_eval(&ds_cmd, cmd, command->end, !is_windows); - - /* Check if echo should be replaced with "builtin" echo */ - if (builtin_echo[0] && strncmp(cmd, "echo", 4) == 0) - { - /* Replace echo with our "builtin" echo */ - replace(&ds_cmd, "echo", 4, builtin_echo, strlen(builtin_echo)); - } - -#ifdef __WIN__ -#ifndef USE_CYGWIN - /* Replace /dev/null with NUL */ - while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) - ; - /* Replace "closed stdout" with non existing output fd */ - while(replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0) - ; -#endif -#endif - - DBUG_PRINT("info", ("Executing '%s' as '%s'", - command->first_argument, ds_cmd.str)); - - if (!(res_file= my_popen(&ds_cmd, "r")) && command->abort_on_error) - { - dynstr_free(&ds_cmd); - die("popen(\"%s\", \"r\") failed", command->first_argument); - } - - while (fgets(buf, sizeof(buf), res_file)) - { - if (disable_result_log) - { - buf[strlen(buf)-1]=0; - DBUG_PRINT("exec_result",("%s", buf)); - } - else - { - replace_dynstr_append(&ds_res, buf); - } - } - error= pclose(res_file); - if (error > 0) - { - uint status= WEXITSTATUS(error), i; - my_bool ok= 0; - - if (command->abort_on_error) - { - log_msg("exec of '%s' failed, error: %d, status: %d, errno: %d", - ds_cmd.str, error, status, errno); - dynstr_free(&ds_cmd); - die("command \"%s\" failed", command->first_argument); - } - - DBUG_PRINT("info", - ("error: %d, status: %d", error, status)); - for (i= 0; i < command->expected_errors.count; i++) - { - DBUG_PRINT("info", ("expected error: %d", - command->expected_errors.err[i].code.errnum)); - if ((command->expected_errors.err[i].type == ERR_ERRNO) && - (command->expected_errors.err[i].code.errnum == status)) - { - ok= 1; - DBUG_PRINT("info", ("command \"%s\" failed with expected error: %d", - command->first_argument, status)); - } - } - if (!ok) - { - dynstr_free(&ds_cmd); - die("command \"%s\" failed with wrong error: %d", - command->first_argument, status); - } - } - else if (command->expected_errors.err[0].type == ERR_ERRNO && - command->expected_errors.err[0].code.errnum != 0) - { - /* Error code we wanted was != 0, i.e. not an expected success */ - log_msg("exec of '%s failed, error: %d, errno: %d", - ds_cmd.str, error, errno); - dynstr_free(&ds_cmd); - die("command \"%s\" succeeded - should have failed with errno %d...", - command->first_argument, command->expected_errors.err[0].code.errnum); - } - - dynstr_free(&ds_cmd); - DBUG_VOID_RETURN; -} - -enum enum_operator -{ - DO_DEC, - DO_INC -}; - - -/* - Decrease or increase the value of a variable - - SYNOPSIS - do_modify_var() - query called command - operator operation to perform on the var - - DESCRIPTION - dec $var_name - inc $var_name - -*/ - -int do_modify_var(struct st_command *command, - enum enum_operator operator) -{ - const char *p= command->first_argument; - VAR* v; - if (!*p) - die("Missing argument to %.*s", command->first_word_len, command->query); - if (*p != '$') - die("The argument to %.*s must be a variable (start with $)", - command->first_word_len, command->query); - v= var_get(p, &p, 1, 0); - switch (operator) { - case DO_DEC: - v->int_val--; - break; - case DO_INC: - v->int_val++; - break; - default: - die("Invalid operator to do_modify_var"); - break; - } - v->int_dirty= 1; - command->last_argument= (char*)++p; - return 0; -} - - -/* - Wrapper for 'system' function - - NOTE - If mysqltest is executed from cygwin shell, the command will be - executed in the "windows command interpreter" cmd.exe and we prepend "sh" - to make it be executed by cygwins "bash". Thus commands like "rm", - "mkdir" as well as shellscripts can executed by "system" in Windows. - -*/ - -int my_system(DYNAMIC_STRING* ds_cmd) -{ -#if defined __WIN__ && defined USE_CYGWIN - /* Dump the command into a sh script file and execute with system */ - str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); - return system(tmp_sh_cmd); -#else - return system(ds_cmd->str); -#endif -} - - -/* - SYNOPSIS - do_system - command called command - - DESCRIPTION - system - - Eval the query to expand any $variables in the command. - Execute the command with the "system" command. - -*/ - -void do_system(struct st_command *command) -{ - DYNAMIC_STRING ds_cmd; - DBUG_ENTER("do_system"); - - if (strlen(command->first_argument) == 0) - die("Missing arguments to system, nothing to do!"); - - init_dynamic_string(&ds_cmd, 0, command->query_len + 64, 256); - - /* Eval the system command, thus replacing all environment variables */ - do_eval(&ds_cmd, command->first_argument, command->end, !is_windows); - -#ifdef __WIN__ -#ifndef USE_CYGWIN - /* Replace /dev/null with NUL */ - while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) - ; -#endif -#endif - - - DBUG_PRINT("info", ("running system command '%s' as '%s'", - command->first_argument, ds_cmd.str)); - if (my_system(&ds_cmd)) - { - if (command->abort_on_error) - die("system command '%s' failed", command->first_argument); - - /* If ! abort_on_error, log message and continue */ - dynstr_append(&ds_res, "system command '"); - replace_dynstr_append(&ds_res, command->first_argument); - dynstr_append(&ds_res, "' failed\n"); - } - - command->last_argument= command->end; - dynstr_free(&ds_cmd); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_remove_file - command called command - - DESCRIPTION - remove_file - Remove the file -*/ - -void do_remove_file(struct st_command *command) -{ - int error; - static DYNAMIC_STRING ds_filename; - const struct command_arg rm_args[] = { - { "filename", ARG_STRING, TRUE, &ds_filename, "File to delete" } - }; - DBUG_ENTER("do_remove_file"); - - check_command_args(command, command->first_argument, - rm_args, sizeof(rm_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("removing file: %s", ds_filename.str)); - error= my_delete(ds_filename.str, MYF(0)) != 0; - handle_command_error(command, error); - dynstr_free(&ds_filename); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_copy_file - command command handle - - DESCRIPTION - copy_file - Copy to - - NOTE! Will fail if exists -*/ - -void do_copy_file(struct st_command *command) -{ - int error; - static DYNAMIC_STRING ds_from_file; - static DYNAMIC_STRING ds_to_file; - const struct command_arg copy_file_args[] = { - { "from_file", ARG_STRING, TRUE, &ds_from_file, "Filename to copy from" }, - { "to_file", ARG_STRING, TRUE, &ds_to_file, "Filename to copy to" } - }; - DBUG_ENTER("do_copy_file"); - - check_command_args(command, command->first_argument, - copy_file_args, - sizeof(copy_file_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str)); - error= (my_copy(ds_from_file.str, ds_to_file.str, - MYF(MY_DONT_OVERWRITE_FILE)) != 0); - handle_command_error(command, error); - dynstr_free(&ds_from_file); - dynstr_free(&ds_to_file); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_chmod_file - command command handle - - DESCRIPTION - chmod - Change file permission of - -*/ - -void do_chmod_file(struct st_command *command) -{ - long mode= 0; - static DYNAMIC_STRING ds_mode; - static DYNAMIC_STRING ds_file; - const struct command_arg chmod_file_args[] = { - { "mode", ARG_STRING, TRUE, &ds_mode, "Mode of file(octal) ex. 0660"}, - { "filename", ARG_STRING, TRUE, &ds_file, "Filename of file to modify" } - }; - DBUG_ENTER("do_chmod_file"); - - check_command_args(command, command->first_argument, - chmod_file_args, - sizeof(chmod_file_args)/sizeof(struct command_arg), - ' '); - - /* Parse what mode to set */ - if (ds_mode.length != 4 || - str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS) - die("You must write a 4 digit octal number for mode"); - - DBUG_PRINT("info", ("chmod %o %s", (uint)mode, ds_file.str)); - handle_command_error(command, chmod(ds_file.str, mode)); - dynstr_free(&ds_mode); - dynstr_free(&ds_file); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_file_exists - command called command - - DESCRIPTION - fiile_exist - Check if file exists -*/ - -void do_file_exist(struct st_command *command) -{ - int error; - static DYNAMIC_STRING ds_filename; - const struct command_arg file_exist_args[] = { - { "filename", ARG_STRING, TRUE, &ds_filename, "File to check if it exist" } - }; - DBUG_ENTER("do_file_exist"); - - check_command_args(command, command->first_argument, - file_exist_args, - sizeof(file_exist_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str)); - error= (access(ds_filename.str, F_OK) != 0); - handle_command_error(command, error); - dynstr_free(&ds_filename); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_mkdir - command called command - - DESCRIPTION - mkdir - Create the directory -*/ - -void do_mkdir(struct st_command *command) -{ - int error; - static DYNAMIC_STRING ds_dirname; - const struct command_arg mkdir_args[] = { - {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to create"} - }; - DBUG_ENTER("do_mkdir"); - - check_command_args(command, command->first_argument, - mkdir_args, sizeof(mkdir_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("creating directory: %s", ds_dirname.str)); - error= my_mkdir(ds_dirname.str, 0777, MYF(0)) != 0; - handle_command_error(command, error); - dynstr_free(&ds_dirname); - DBUG_VOID_RETURN; -} - -/* - SYNOPSIS - do_rmdir - command called command - - DESCRIPTION - rmdir - Remove the empty directory -*/ - -void do_rmdir(struct st_command *command) -{ - int error; - static DYNAMIC_STRING ds_dirname; - const struct command_arg rmdir_args[] = { - {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to remove"} - }; - DBUG_ENTER("do_rmdir"); - - check_command_args(command, command->first_argument, - rmdir_args, sizeof(rmdir_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("removing directory: %s", ds_dirname.str)); - error= rmdir(ds_dirname.str) != 0; - handle_command_error(command, error); - dynstr_free(&ds_dirname); - DBUG_VOID_RETURN; -} - - -/* - Read characters from line buffer or file. This is needed to allow - my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file - - NOTE: - This works as long as one doesn't change files (with 'source file_name') - when there is things pushed into the buffer. This should however not - happen for any tests in the test suite. -*/ - -int my_getc(FILE *file) -{ - if (line_buffer_pos == line_buffer) - return fgetc(file); - return *--line_buffer_pos; -} - - -void my_ungetc(int c) -{ - *line_buffer_pos++= (char) c; -} - - -void read_until_delimiter(DYNAMIC_STRING *ds, - DYNAMIC_STRING *ds_delimiter) -{ - char c; - DBUG_ENTER("read_until_delimiter"); - DBUG_PRINT("enter", ("delimiter: %s, length: %u", - ds_delimiter->str, (uint) ds_delimiter->length)); - - if (ds_delimiter->length > MAX_DELIMITER_LENGTH) - die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH); - - /* Read from file until delimiter is found */ - while (1) - { - c= my_getc(cur_file->file); - - if (c == '\n') - { - cur_file->lineno++; - - /* Skip newline from the same line as the command */ - if (start_lineno == (cur_file->lineno - 1)) - continue; - } - else if (start_lineno == cur_file->lineno) - { - /* - No characters except \n are allowed on - the same line as the command - */ - die("Trailing characters found after command"); - } - - if (feof(cur_file->file)) - die("End of file encountered before '%s' delimiter was found", - ds_delimiter->str); - - if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) - { - DBUG_PRINT("exit", ("Found delimiter '%s'", ds_delimiter->str)); - break; - } - dynstr_append_mem(ds, (const char*)&c, 1); - } - DBUG_PRINT("exit", ("ds: %s", ds->str)); - DBUG_VOID_RETURN; -} - - -void do_write_file_command(struct st_command *command, my_bool append) -{ - static DYNAMIC_STRING ds_content; - static DYNAMIC_STRING ds_filename; - static DYNAMIC_STRING ds_delimiter; - const struct command_arg write_file_args[] = { - { "filename", ARG_STRING, TRUE, &ds_filename, "File to write to" }, - { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } - }; - DBUG_ENTER("do_write_file"); - - check_command_args(command, - command->first_argument, - write_file_args, - sizeof(write_file_args)/sizeof(struct command_arg), - ' '); - - /* If no delimiter was provided, use EOF */ - if (ds_delimiter.length == 0) - dynstr_set(&ds_delimiter, "EOF"); - - if (!append && access(ds_filename.str, F_OK) == 0) - { - /* The file should not be overwritten */ - die("File already exist: '%s'", ds_filename.str); - } - - init_dynamic_string(&ds_content, "", 1024, 1024); - read_until_delimiter(&ds_content, &ds_delimiter); - DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str)); - str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); - dynstr_free(&ds_content); - dynstr_free(&ds_filename); - dynstr_free(&ds_delimiter); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_write_file - command called command - - DESCRIPTION - write_file []; - - <...> - < what to write line n> - EOF - - --write_file ; - - <...> - < what to write line n> - EOF - - Write everything between the "write_file" command and 'delimiter' - to "file_name" - - NOTE! Will fail if exists - - Default is EOF - -*/ - -void do_write_file(struct st_command *command) -{ - do_write_file_command(command, FALSE); -} - - -/* - SYNOPSIS - do_append_file - command called command - - DESCRIPTION - append_file []; - - <...> - < what to write line n> - EOF - - --append_file ; - - <...> - < what to write line n> - EOF - - Append everything between the "append_file" command - and 'delimiter' to "file_name" - - Default is EOF - -*/ - -void do_append_file(struct st_command *command) -{ - do_write_file_command(command, TRUE); -} - - -/* - SYNOPSIS - do_cat_file - command called command - - DESCRIPTION - cat_file ; - - Print the given file to result log - -*/ - -void do_cat_file(struct st_command *command) -{ - static DYNAMIC_STRING ds_filename; - const struct command_arg cat_file_args[] = { - { "filename", ARG_STRING, TRUE, &ds_filename, "File to read from" } - }; - DBUG_ENTER("do_cat_file"); - - check_command_args(command, - command->first_argument, - cat_file_args, - sizeof(cat_file_args)/sizeof(struct command_arg), - ' '); - - DBUG_PRINT("info", ("Reading from, file: %s", ds_filename.str)); - - cat_file(&ds_res, ds_filename.str); - - dynstr_free(&ds_filename); - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_diff_files - command called command - - DESCRIPTION - diff_files ; - - Fails if the two files differ. - -*/ - -void do_diff_files(struct st_command *command) -{ - int error= 0; - static DYNAMIC_STRING ds_filename; - static DYNAMIC_STRING ds_filename2; - const struct command_arg diff_file_args[] = { - { "file1", ARG_STRING, TRUE, &ds_filename, "First file to diff" }, - { "file2", ARG_STRING, TRUE, &ds_filename2, "Second file to diff" } - }; - DBUG_ENTER("do_diff_files"); - - check_command_args(command, - command->first_argument, - diff_file_args, - sizeof(diff_file_args)/sizeof(struct command_arg), - ' '); - - if ((error= compare_files(ds_filename.str, ds_filename2.str))) - { - /* Compare of the two files failed, append them to output - so the failure can be analyzed - */ - show_diff(&ds_res, ds_filename.str, ds_filename2.str); - } - - dynstr_free(&ds_filename); - dynstr_free(&ds_filename2); - handle_command_error(command, error); - DBUG_VOID_RETURN; -} - - -struct st_connection * find_connection_by_name(const char *name) -{ - struct st_connection *con; - for (con= connections; con < next_con; con++) - { - if (!strcmp(con->name, name)) - { - return con; - } - } - return 0; /* Connection not found */ -} - - -/* - SYNOPSIS - do_send_quit - command called command - - DESCRIPTION - Sends a simple quit command to the server for the named connection. - -*/ - -void do_send_quit(struct st_command *command) -{ - char *p= command->first_argument, *name; - struct st_connection *con; - - DBUG_ENTER("do_send_quit"); - DBUG_PRINT("enter",("name: '%s'",p)); - - if (!*p) - die("Missing connection name in send_quit"); - name= p; - while (*p && !my_isspace(charset_info,*p)) - p++; - - if (*p) - *p++= 0; - command->last_argument= p; - - if (!(con= find_connection_by_name(name))) - die("connection '%s' not found in connection pool", name); - - simple_command(&con->mysql,COM_QUIT,0,0,1); - - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_change_user - command called command - - DESCRIPTION - change_user [], [], [] - - user to change to - - user password - - default database - - Changes the user and causes the database specified by db to become - the default (current) database for the the current connection. - -*/ - -void do_change_user(struct st_command *command) -{ - MYSQL *mysql = &cur_con->mysql; - /* static keyword to make the NetWare compiler happy. */ - static DYNAMIC_STRING ds_user, ds_passwd, ds_db; - const struct command_arg change_user_args[] = { - { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, - { "password", ARG_STRING, FALSE, &ds_passwd, "Password used when connecting" }, - { "database", ARG_STRING, FALSE, &ds_db, "Database to select after connect" }, - }; - - DBUG_ENTER("do_change_user"); - - check_command_args(command, command->first_argument, - change_user_args, - sizeof(change_user_args)/sizeof(struct command_arg), - ','); - - if (cur_con->stmt) - { - mysql_stmt_close(cur_con->stmt); - cur_con->stmt= NULL; - } - - if (!ds_user.length) - dynstr_set(&ds_user, mysql->user); - - if (!ds_passwd.length) - dynstr_set(&ds_passwd, mysql->passwd); - - if (!ds_db.length) - dynstr_set(&ds_db, mysql->db); - - DBUG_PRINT("info",("connection: '%s' user: '%s' password: '%s' database: '%s'", - cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); - - if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) - die("change user failed: %s", mysql_error(mysql)); - - dynstr_free(&ds_user); - dynstr_free(&ds_passwd); - dynstr_free(&ds_db); - - DBUG_VOID_RETURN; -} - - -/* - SYNOPSIS - do_perl - command command handle - - DESCRIPTION - perl []; - - <...> - - EOF - - Execute everything after "perl" until as perl. - Useful for doing more advanced things - but still being able to execute it on all platforms. - - Default is EOF -*/ - -void do_perl(struct st_command *command) -{ - int error; - File fd; - FILE *res_file; - char buf[FN_REFLEN]; - char temp_file_path[FN_REFLEN]; - static DYNAMIC_STRING ds_script; - static DYNAMIC_STRING ds_delimiter; - const struct command_arg perl_args[] = { - { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } - }; - DBUG_ENTER("do_perl"); - - check_command_args(command, - command->first_argument, - perl_args, - sizeof(perl_args)/sizeof(struct command_arg), - ' '); - - /* If no delimiter was provided, use EOF */ - if (ds_delimiter.length == 0) - dynstr_set(&ds_delimiter, "EOF"); - - init_dynamic_string(&ds_script, "", 1024, 1024); - read_until_delimiter(&ds_script, &ds_delimiter); - - DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); - - /* Create temporary file name */ - if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), - "tmp", O_CREAT | O_SHARE | O_RDWR, - MYF(MY_WME))) < 0) - die("Failed to create temporary file for perl command"); - my_close(fd, MYF(0)); - - str_to_file(temp_file_path, ds_script.str, ds_script.length); - - /* Format the "perl " command */ - my_snprintf(buf, sizeof(buf), "perl %s", temp_file_path); - - if (!(res_file= popen(buf, "r")) && command->abort_on_error) - die("popen(\"%s\", \"r\") failed", buf); - - while (fgets(buf, sizeof(buf), res_file)) - { - if (disable_result_log) - { - buf[strlen(buf)-1]=0; - DBUG_PRINT("exec_result",("%s", buf)); - } - else - { - replace_dynstr_append(&ds_res, buf); - } - } - error= pclose(res_file); - - /* Remove the temporary file */ - my_delete(temp_file_path, MYF(0)); - - handle_command_error(command, WEXITSTATUS(error)); - dynstr_free(&ds_script); - dynstr_free(&ds_delimiter); - DBUG_VOID_RETURN; -} - - -/* - Print the content between echo and to result file. - Evaluate all variables in the string before printing, allow - for variable names to be escaped using \ - - SYNOPSIS - do_echo() - command called command - - DESCRIPTION - echo text - Print the text after echo until end of command to result file - - echo $ - Print the content of the variable to result file - - echo Some text $ - Print "Some text" plus the content of the variable to - result file - - echo Some text \$ - Print "Some text" plus $ to result file -*/ - -int do_echo(struct st_command *command) -{ - DYNAMIC_STRING ds_echo; - DBUG_ENTER("do_echo"); - - init_dynamic_string(&ds_echo, "", command->query_len, 256); - do_eval(&ds_echo, command->first_argument, command->end, FALSE); - dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length); - dynstr_append_mem(&ds_res, "\n", 1); - dynstr_free(&ds_echo); - command->last_argument= command->end; - DBUG_RETURN(0); -} - - -void do_wait_for_slave_to_stop(struct st_command *c __attribute__((unused))) -{ - static int SLAVE_POLL_INTERVAL= 300000; - MYSQL* mysql = &cur_con->mysql; - for (;;) - { - MYSQL_RES *res; - MYSQL_ROW row; - int done; - LINT_INIT(res); - - if (mysql_query(mysql,"show status like 'Slave_running'") || - !(res=mysql_store_result(mysql))) - die("Query failed while probing slave for stop: %s", - mysql_error(mysql)); - if (!(row=mysql_fetch_row(res)) || !row[1]) - { - mysql_free_result(res); - die("Strange result from query while probing slave for stop"); - } - done = !strcmp(row[1],"OFF"); - mysql_free_result(res); - if (done) - break; - my_sleep(SLAVE_POLL_INTERVAL); - } - return; -} - - -void do_sync_with_master2(long offset) -{ - MYSQL_RES *res; - MYSQL_ROW row; - MYSQL *mysql= &cur_con->mysql; - char query_buf[FN_REFLEN+128]; - int tries= 0; - int rpl_parse; - - if (!master_pos.file[0]) - die("Calling 'sync_with_master' without calling 'save_master_pos'"); - rpl_parse= mysql_rpl_parse_enabled(mysql); - mysql_disable_rpl_parse(mysql); - - sprintf(query_buf, "select master_pos_wait('%s', %ld)", master_pos.file, - master_pos.pos + offset); - -wait_for_position: - - if (mysql_query(mysql, query_buf)) - die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), - mysql_error(mysql)); - - if (!(res= mysql_store_result(mysql))) - die("mysql_store_result() returned NULL for '%s'", query_buf); - if (!(row= mysql_fetch_row(res))) - { - mysql_free_result(res); - die("empty result in %s", query_buf); - } - if (!row[0]) - { - /* - It may be that the slave SQL thread has not started yet, though START - SLAVE has been issued ? - */ - mysql_free_result(res); - if (tries++ == 30) - { - show_query(mysql, "SHOW MASTER STATUS"); - show_query(mysql, "SHOW SLAVE STATUS"); - die("could not sync with master ('%s' returned NULL)", query_buf); - } - sleep(1); /* So at most we will wait 30 seconds and make 31 tries */ - goto wait_for_position; - } - mysql_free_result(res); - if (rpl_parse) - mysql_enable_rpl_parse(mysql); - - return; -} - - -void do_sync_with_master(struct st_command *command) -{ - long offset= 0; - char *p= command->first_argument; - const char *offset_start= p; - if (*offset_start) - { - for (; my_isdigit(charset_info, *p); p++) - offset = offset * 10 + *p - '0'; - - if(*p && !my_isspace(charset_info, *p)) - die("Invalid integer argument \"%s\"", offset_start); - command->last_argument= p; - } - do_sync_with_master2(offset); - return; -} - - -/* - when ndb binlog is on, this call will wait until last updated epoch - (locally in the mysqld) has been received into the binlog -*/ -int do_save_master_pos() -{ - MYSQL_RES *res; - MYSQL_ROW row; - MYSQL *mysql = &cur_con->mysql; - const char *query; - int rpl_parse; - DBUG_ENTER("do_save_master_pos"); - - rpl_parse = mysql_rpl_parse_enabled(mysql); - mysql_disable_rpl_parse(mysql); - -#ifdef HAVE_NDB_BINLOG - /* - Wait for ndb binlog to be up-to-date with all changes - done on the local mysql server - */ - { - ulong have_ndbcluster; - if (mysql_query(mysql, query= "show variables like 'have_ndbcluster'")) - die("'%s' failed: %d %s", query, - mysql_errno(mysql), mysql_error(mysql)); - if (!(res= mysql_store_result(mysql))) - die("mysql_store_result() returned NULL for '%s'", query); - if (!(row= mysql_fetch_row(res))) - die("Query '%s' returned empty result", query); - - have_ndbcluster= strcmp("YES", row[1]) == 0; - mysql_free_result(res); - - if (have_ndbcluster) - { - ulonglong start_epoch= 0, handled_epoch= 0, - latest_epoch=0, latest_trans_epoch=0, - latest_handled_binlog_epoch= 0, latest_received_binlog_epoch= 0, - latest_applied_binlog_epoch= 0; - int count= 0; - int do_continue= 1; - while (do_continue) - { - const char binlog[]= "binlog"; - const char latest_epoch_str[]= - "latest_epoch="; - const char latest_trans_epoch_str[]= - "latest_trans_epoch="; - const char latest_received_binlog_epoch_str[]= - "latest_received_binlog_epoch"; - const char latest_handled_binlog_epoch_str[]= - "latest_handled_binlog_epoch="; - const char latest_applied_binlog_epoch_str[]= - "latest_applied_binlog_epoch="; - if (count) - sleep(1); - if (mysql_query(mysql, query= "show engine ndb status")) - die("failed in '%s': %d %s", query, - mysql_errno(mysql), mysql_error(mysql)); - if (!(res= mysql_store_result(mysql))) - die("mysql_store_result() returned NULL for '%s'", query); - while ((row= mysql_fetch_row(res))) - { - if (strcmp(row[1], binlog) == 0) - { - const char *status= row[2]; - - /* latest_epoch */ - while (*status && strncmp(status, latest_epoch_str, - sizeof(latest_epoch_str)-1)) - status++; - if (*status) - { - status+= sizeof(latest_epoch_str)-1; - latest_epoch= strtoull(status, (char**) 0, 10); - } - else - die("result does not contain '%s' in '%s'", - latest_epoch_str, query); - /* latest_trans_epoch */ - while (*status && strncmp(status, latest_trans_epoch_str, - sizeof(latest_trans_epoch_str)-1)) - status++; - if (*status) - { - status+= sizeof(latest_trans_epoch_str)-1; - latest_trans_epoch= strtoull(status, (char**) 0, 10); - } - else - die("result does not contain '%s' in '%s'", - latest_trans_epoch_str, query); - /* latest_received_binlog_epoch */ - while (*status && - strncmp(status, latest_received_binlog_epoch_str, - sizeof(latest_received_binlog_epoch_str)-1)) - status++; - if (*status) - { - status+= sizeof(latest_received_binlog_epoch_str)-1; - latest_received_binlog_epoch= strtoull(status, (char**) 0, 10); - } - else - die("result does not contain '%s' in '%s'", - latest_received_binlog_epoch_str, query); - /* latest_handled_binlog */ - while (*status && - strncmp(status, latest_handled_binlog_epoch_str, - sizeof(latest_handled_binlog_epoch_str)-1)) - status++; - if (*status) - { - status+= sizeof(latest_handled_binlog_epoch_str)-1; - latest_handled_binlog_epoch= strtoull(status, (char**) 0, 10); - } - else - die("result does not contain '%s' in '%s'", - latest_handled_binlog_epoch_str, query); - /* latest_applied_binlog_epoch */ - while (*status && - strncmp(status, latest_applied_binlog_epoch_str, - sizeof(latest_applied_binlog_epoch_str)-1)) - status++; - if (*status) - { - status+= sizeof(latest_applied_binlog_epoch_str)-1; - latest_applied_binlog_epoch= strtoull(status, (char**) 0, 10); - } - else - die("result does not contain '%s' in '%s'", - latest_applied_binlog_epoch_str, query); - if (count == 0) - start_epoch= latest_trans_epoch; - break; - } - } - if (!row) - die("result does not contain '%s' in '%s'", - binlog, query); - if (latest_handled_binlog_epoch > handled_epoch) - count= 0; - handled_epoch= latest_handled_binlog_epoch; - count++; - if (latest_handled_binlog_epoch >= start_epoch) - do_continue= 0; - else if (count > 30) - { - break; - } - mysql_free_result(res); - } - } - } -#endif - if (mysql_query(mysql, query= "show master status")) - die("failed in 'show master status': %d %s", - mysql_errno(mysql), mysql_error(mysql)); - - if (!(res = mysql_store_result(mysql))) - die("mysql_store_result() retuned NULL for '%s'", query); - if (!(row = mysql_fetch_row(res))) - die("empty result in show master status"); - strnmov(master_pos.file, row[0], sizeof(master_pos.file)-1); - master_pos.pos = strtoul(row[1], (char**) 0, 10); - mysql_free_result(res); - - if (rpl_parse) - mysql_enable_rpl_parse(mysql); - - DBUG_RETURN(0); -} - - -/* - Assign the variable with - - SYNOPSIS - do_let() - query called command - - DESCRIPTION - let $= - - - is the string string found between the $ and = - - is the content between the = and , it may span - multiple line and contain any characters except - - is a string containing of one or more chars, default is ; - - RETURN VALUES - Program will die if error detected -*/ - -void do_let(struct st_command *command) -{ - char *p= command->first_argument; - char *var_name, *var_name_end; - DYNAMIC_STRING let_rhs_expr; - DBUG_ENTER("do_let"); - - init_dynamic_string(&let_rhs_expr, "", 512, 2048); - - /* Find */ - if (!*p) - die("Missing arguments to let"); - var_name= p; - while (*p && (*p != '=') && !my_isspace(charset_info,*p)) - p++; - var_name_end= p; - if (var_name == var_name_end || - (var_name+1 == var_name_end && *var_name == '$')) - die("Missing variable name in let"); - while (my_isspace(charset_info,*p)) - p++; - if (*p++ != '=') - die("Missing assignment operator in let"); - - /* Find start of */ - while (*p && my_isspace(charset_info,*p)) - p++; - - do_eval(&let_rhs_expr, p, command->end, FALSE); - - command->last_argument= command->end; - /* Assign var_val to var_name */ - var_set(var_name, var_name_end, let_rhs_expr.str, - (let_rhs_expr.str + let_rhs_expr.length)); - dynstr_free(&let_rhs_expr); - DBUG_VOID_RETURN; -} - - -int do_rpl_probe(struct st_command *command __attribute__((unused))) -{ - DBUG_ENTER("do_rpl_probe"); - if (mysql_rpl_probe(&cur_con->mysql)) - die("Failed in mysql_rpl_probe(): '%s'", mysql_error(&cur_con->mysql)); - DBUG_RETURN(0); -} - - -int do_enable_rpl_parse(struct st_command *command __attribute__((unused))) -{ - mysql_enable_rpl_parse(&cur_con->mysql); - return 0; -} - - -int do_disable_rpl_parse(struct st_command *command __attribute__((unused))) -{ - mysql_disable_rpl_parse(&cur_con->mysql); - return 0; -} - - -/* - Sleep the number of specified seconds - - SYNOPSIS - do_sleep() - q called command - real_sleep use the value from opt_sleep as number of seconds to sleep - if real_sleep is false - - DESCRIPTION - sleep - real_sleep - - The difference between the sleep and real_sleep commands is that sleep - uses the delay from the --sleep command-line option if there is one. - (If the --sleep option is not given, the sleep command uses the delay - specified by its argument.) The real_sleep command always uses the - delay specified by its argument. The logic is that sometimes delays are - cpu-dependent, and --sleep can be used to set this delay. real_sleep is - used for cpu-independent delays. -*/ - -int do_sleep(struct st_command *command, my_bool real_sleep) -{ - int error= 0; - char *p= command->first_argument; - char *sleep_start, *sleep_end= command->end; - double sleep_val; - - while (my_isspace(charset_info, *p)) - p++; - if (!*p) - die("Missing argument to %.*s", command->first_word_len, command->query); - sleep_start= p; - /* Check that arg starts with a digit, not handled by my_strtod */ - if (!my_isdigit(charset_info, *sleep_start)) - die("Invalid argument to %.*s \"%s\"", command->first_word_len, - command->query,command->first_argument); - sleep_val= my_strtod(sleep_start, &sleep_end, &error); - if (error) - die("Invalid argument to %.*s \"%s\"", command->first_word_len, - command->query, command->first_argument); - - /* Fixed sleep time selected by --sleep option */ - if (opt_sleep >= 0 && !real_sleep) - sleep_val= opt_sleep; - - DBUG_PRINT("info", ("sleep_val: %f", sleep_val)); - if (sleep_val) - my_sleep((ulong) (sleep_val * 1000000L)); - command->last_argument= sleep_end; - return 0; -} - - -void do_get_file_name(struct st_command *command, - char* dest, uint dest_max_len) -{ - char *p= command->first_argument, *name; - if (!*p) - die("Missing file name argument"); - name= p; - while (*p && !my_isspace(charset_info,*p)) - p++; - if (*p) - *p++= 0; - command->last_argument= p; - strmake(dest, name, dest_max_len - 1); -} - - -void do_set_charset(struct st_command *command) -{ - char *charset_name= command->first_argument; - char *p; - - if (!charset_name || !*charset_name) - die("Missing charset name in 'character_set'"); - /* Remove end space */ - p= charset_name; - while (*p && !my_isspace(charset_info,*p)) - p++; - if(*p) - *p++= 0; - command->last_argument= p; - charset_info= get_charset_by_csname(charset_name,MY_CS_PRIMARY,MYF(MY_WME)); - if (!charset_info) - abort_not_supported_test("Test requires charset '%s'", charset_name); -} - - -/* - Run query and return one field in the result set from the - first row and -*/ - -int query_get_string(MYSQL* mysql, const char* query, - int column, DYNAMIC_STRING* ds) -{ - MYSQL_RES *res= NULL; - MYSQL_ROW row; - - if (mysql_query(mysql, query)) - die("'%s' failed: %d %s", query, - mysql_errno(mysql), mysql_error(mysql)); - if ((res= mysql_store_result(mysql)) == NULL) - die("Failed to store result: %d %s", - mysql_errno(mysql), mysql_error(mysql)); - - if ((row= mysql_fetch_row(res)) == NULL) - { - mysql_free_result(res); - ds= 0; - return 1; - } - init_dynamic_string(ds, (row[column] ? row[column] : "NULL"), ~0, 32); - mysql_free_result(res); - return 0; -} - - -static int my_kill(int pid, int sig) -{ -#ifdef __WIN__ - HANDLE proc; - if ((proc= OpenProcess(PROCESS_TERMINATE, FALSE, pid)) == NULL) - return -1; - if (sig == 0) - { - CloseHandle(proc); - return 0; - } - (void)TerminateProcess(proc, 201); - CloseHandle(proc); - return 1; -#else - return kill(pid, sig); -#endif -} - - - -/* - Shutdown the server of current connection and - make sure it goes away within seconds - - NOTE! Currently only works with local server - - SYNOPSIS - do_shutdown_server() - command called command - - DESCRIPTION - shutdown [] - -*/ - -void do_shutdown_server(struct st_command *command) -{ - int timeout=60, pid; - DYNAMIC_STRING ds_pidfile_name; - MYSQL* mysql = &cur_con->mysql; - static DYNAMIC_STRING ds_timeout; - const struct command_arg shutdown_args[] = { - {"timeout", ARG_STRING, FALSE, &ds_timeout, "Timeout before killing server"} - }; - DBUG_ENTER("do_shutdown_server"); - - check_command_args(command, command->first_argument, shutdown_args, - sizeof(shutdown_args)/sizeof(struct command_arg), - ' '); - - if (ds_timeout.length) - { - timeout= atoi(ds_timeout.str); - if (timeout == 0) - die("Illegal argument for timeout: '%s'", ds_timeout.str); - } - dynstr_free(&ds_timeout); - - /* Get the servers pid_file name and use it to read pid */ - if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1, - &ds_pidfile_name)) - die("Failed to get pid_file from server"); - - /* Read the pid from the file */ - { - int fd; - char buff[32]; - - if ((fd= my_open(ds_pidfile_name.str, O_RDONLY, MYF(0))) < 0) - die("Failed to open file '%s'", ds_pidfile_name.str); - dynstr_free(&ds_pidfile_name); - - if (my_read(fd, (uchar*)&buff, - sizeof(buff), MYF(0)) <= 0){ - my_close(fd, MYF(0)); - die("pid file was empty"); - } - my_close(fd, MYF(0)); - - pid= atoi(buff); - if (pid == 0) - die("Pidfile didn't contain a valid number"); - } - DBUG_PRINT("info", ("Got pid %d", pid)); - - /* Tell server to shutdown if timeout > 0*/ - if (timeout && mysql_shutdown(mysql, SHUTDOWN_DEFAULT)) - die("mysql_shutdown failed"); - - /* Check that server dies */ - while(timeout--){ - if (my_kill(0, pid) < 0){ - DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); - break; - } - DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); - my_sleep(1); - } - - /* Kill the server */ - DBUG_PRINT("info", ("Killing server, pid: %d", pid)); - (void)my_kill(9, pid); - - DBUG_VOID_RETURN; - -} - - -#if MYSQL_VERSION_ID >= 50000 -/* List of error names to error codes, available from 5.0 */ -typedef struct -{ - const char *name; - uint code; -} st_error; - -static st_error global_error_names[] = -{ -#include - { 0, 0 } -}; - -uint get_errcode_from_name(char *error_name, char *error_end) -{ - /* SQL error as string */ - st_error *e= global_error_names; - - DBUG_ENTER("get_errcode_from_name"); - DBUG_PRINT("enter", ("error_name: %s", error_name)); - - /* Loop through the array of known error names */ - for (; e->name; e++) - { - /* - If we get a match, we need to check the length of the name we - matched against in case it was longer than what we are checking - (as in ER_WRONG_VALUE vs. ER_WRONG_VALUE_COUNT). - */ - if (!strncmp(error_name, e->name, (int) (error_end - error_name)) && - (uint) strlen(e->name) == (uint) (error_end - error_name)) - { - DBUG_RETURN(e->code); - } - } - if (!e->name) - die("Unknown SQL error name '%s'", error_name); - DBUG_RETURN(0); -} -#else -uint get_errcode_from_name(char *error_name __attribute__((unused)), - char *error_end __attribute__((unused))) -{ - abort_not_in_this_version(); - return 0; /* Never reached */ -} -#endif - - - -void do_get_errcodes(struct st_command *command) -{ - struct st_match_err *to= saved_expected_errors.err; - char *p= command->first_argument; - uint count= 0; - - DBUG_ENTER("do_get_errcodes"); - - if (!*p) - die("Missing argument(s) to 'error'"); - - do - { - char *end; - - /* Skip leading spaces */ - while (*p && *p == ' ') - p++; - - /* Find end */ - end= p; - while (*end && *end != ',' && *end != ' ') - end++; - - if (*p == 'S') - { - char *to_ptr= to->code.sqlstate; - - /* - SQLSTATE string - - Must be SQLSTATE_LENGTH long - - May contain only digits[0-9] and _uppercase_ letters - */ - p++; /* Step past the S */ - if ((end - p) != SQLSTATE_LENGTH) - die("The sqlstate must be exactly %d chars long", SQLSTATE_LENGTH); - - /* Check sqlstate string validity */ - while (*p && p < end) - { - if (my_isdigit(charset_info, *p) || my_isupper(charset_info, *p)) - *to_ptr++= *p++; - else - die("The sqlstate may only consist of digits[0-9] " \ - "and _uppercase_ letters"); - } - - *to_ptr= 0; - to->type= ERR_SQLSTATE; - DBUG_PRINT("info", ("ERR_SQLSTATE: %s", to->code.sqlstate)); - } - else if (*p == 's') - { - die("The sqlstate definition must start with an uppercase S"); - } - else if (*p == 'E') - { - /* Error name string */ - - DBUG_PRINT("info", ("Error name: %s", p)); - to->code.errnum= get_errcode_from_name(p, end); - to->type= ERR_ERRNO; - DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); - } - else if (*p == 'e') - { - die("The error name definition must start with an uppercase E"); - } - else - { - long val; - char *start= p; - /* Check that the string passed to str2int only contain digits */ - while (*p && p != end) - { - if (!my_isdigit(charset_info, *p)) - die("Invalid argument to error: '%s' - "\ - "the errno may only consist of digits[0-9]", - command->first_argument); - p++; - } - - /* Convert the sting to int */ - if (!str2int(start, 10, (long) INT_MIN, (long) INT_MAX, &val)) - die("Invalid argument to error: '%s'", command->first_argument); - - to->code.errnum= (uint) val; - to->type= ERR_ERRNO; - DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); - } - to++; - count++; - - if (count >= (sizeof(saved_expected_errors.err) / - sizeof(struct st_match_err))) - die("Too many errorcodes specified"); - - /* Set pointer to the end of the last error code */ - p= end; - - /* Find next ',' */ - while (*p && *p != ',') - p++; - - if (*p) - p++; /* Step past ',' */ - - } while (*p); - - command->last_argument= p; - to->type= ERR_EMPTY; /* End of data */ - - DBUG_PRINT("info", ("Expected errors: %d", count)); - saved_expected_errors.count= count; - DBUG_VOID_RETURN; -} - - -/* - Get a string; Return ptr to end of string - Strings may be surrounded by " or ' - - If string is a '$variable', return the value of the variable. -*/ - -char *get_string(char **to_ptr, char **from_ptr, - struct st_command *command) -{ - char c, sep; - char *to= *to_ptr, *from= *from_ptr, *start=to; - DBUG_ENTER("get_string"); - - /* Find separator */ - if (*from == '"' || *from == '\'') - sep= *from++; - else - sep=' '; /* Separated with space */ - - for ( ; (c=*from) ; from++) - { - if (c == '\\' && from[1]) - { /* Escaped character */ - /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */ - switch (*++from) { - case 'n': - *to++= '\n'; - break; - case 't': - *to++= '\t'; - break; - case 'r': - *to++ = '\r'; - break; - case 'b': - *to++ = '\b'; - break; - case 'Z': /* ^Z must be escaped on Win32 */ - *to++='\032'; - break; - default: - *to++ = *from; - break; - } - } - else if (c == sep) - { - if (c == ' ' || c != *++from) - break; /* Found end of string */ - *to++=c; /* Copy duplicated separator */ - } - else - *to++=c; - } - if (*from != ' ' && *from) - die("Wrong string argument in %s", command->query); - - while (my_isspace(charset_info,*from)) /* Point to next string */ - from++; - - *to =0; /* End of string marker */ - *to_ptr= to+1; /* Store pointer to end */ - *from_ptr= from; - - /* Check if this was a variable */ - if (*start == '$') - { - const char *end= to; - VAR *var=var_get(start, &end, 0, 1); - if (var && to == (char*) end+1) - { - DBUG_PRINT("info",("var: '%s' -> '%s'", start, var->str_val)); - DBUG_RETURN(var->str_val); /* return found variable value */ - } - } - DBUG_RETURN(start); -} - - -void set_reconnect(MYSQL* mysql, int val) -{ - my_bool reconnect= val; - DBUG_ENTER("set_reconnect"); - DBUG_PRINT("info", ("val: %d", val)); -#if MYSQL_VERSION_ID < 50000 - mysql->reconnect= reconnect; -#else - mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); -#endif - DBUG_VOID_RETURN; -} - - -int select_connection_name(const char *name) -{ - DBUG_ENTER("select_connection_name"); - DBUG_PRINT("enter",("name: '%s'", name)); - - if (!(cur_con= find_connection_by_name(name))) - die("connection '%s' not found in connection pool", name); - - /* Update $mysql_get_server_version to that of current connection */ - var_set_mysql_get_server_version(&cur_con->mysql); - - DBUG_RETURN(0); -} - - -int select_connection(struct st_command *command) -{ - char *name; - char *p= command->first_argument; - DBUG_ENTER("select_connection"); - - if (!*p) - die("Missing connection name in connect"); - name= p; - while (*p && !my_isspace(charset_info,*p)) - p++; - if (*p) - *p++= 0; - command->last_argument= p; - DBUG_RETURN(select_connection_name(name)); -} - - -void do_close_connection(struct st_command *command) -{ - char *p= command->first_argument, *name; - struct st_connection *con; - - DBUG_ENTER("close_connection"); - DBUG_PRINT("enter",("name: '%s'",p)); - - if (!*p) - die("Missing connection name in disconnect"); - name= p; - while (*p && !my_isspace(charset_info,*p)) - p++; - - if (*p) - *p++= 0; - command->last_argument= p; - - if (!(con= find_connection_by_name(name))) - die("connection '%s' not found in connection pool", name); - - DBUG_PRINT("info", ("Closing connection %s", con->name)); -#ifndef EMBEDDED_LIBRARY - if (command->type == Q_DIRTY_CLOSE) - { - if (con->mysql.net.vio) - { - vio_delete(con->mysql.net.vio); - con->mysql.net.vio = 0; - } - } -#else - /* - As query could be still executed in a separate theread - we need to check if the query's thread was finished and probably wait - (embedded-server specific) - */ - wait_query_thread_end(con); -#endif /*EMBEDDED_LIBRARY*/ - if (con->stmt) - mysql_stmt_close(con->stmt); - con->stmt= 0; - - mysql_close(&con->mysql); - - if (con->util_mysql) - mysql_close(con->util_mysql); - con->util_mysql= 0; - - my_free(con->name, MYF(0)); - - /* - When the connection is closed set name to "-closed_connection-" - to make it possible to reuse the connection name. - */ - if (!(con->name = my_strdup("-closed_connection-", MYF(MY_WME)))) - die("Out of memory"); - - DBUG_VOID_RETURN; -} - - -/* - Connect to a server doing several retries if needed. - - SYNOPSIS - safe_connect() - con - connection structure to be used - host, user, pass, - connection parameters - db, port, sock - - NOTE - - Sometimes in a test the client starts before - the server - to solve the problem, we try again - after some sleep if connection fails the first - time - - This function will try to connect to the given server - "opt_max_connect_retries" times and sleep "connection_retry_sleep" - seconds between attempts before finally giving up. - This helps in situation when the client starts - before the server (which happens sometimes). - It will only ignore connection errors during these retries. - -*/ - -void safe_connect(MYSQL* mysql, const char *name, const char *host, - const char *user, const char *pass, const char *db, - int port, const char *sock) -{ - int failed_attempts= 0; - static ulong connection_retry_sleep= 100000; /* Microseconds */ - - DBUG_ENTER("safe_connect"); - while(!mysql_real_connect(mysql, host,user, pass, db, port, sock, - CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) - { - /* - Connect failed - - Only allow retry if this was an error indicating the server - could not be contacted. Error code differs depending - on protocol/connection type - */ - - if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR || - mysql_errno(mysql) == CR_CONNECTION_ERROR) && - failed_attempts < opt_max_connect_retries) - { - verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts, - opt_max_connect_retries, mysql_errno(mysql), - mysql_error(mysql)); - my_sleep(connection_retry_sleep); - } - else - { - if (failed_attempts > 0) - die("Could not open connection '%s' after %d attempts: %d %s", name, - failed_attempts, mysql_errno(mysql), mysql_error(mysql)); - else - die("Could not open connection '%s': %d %s", name, - mysql_errno(mysql), mysql_error(mysql)); - } - failed_attempts++; - } - DBUG_VOID_RETURN; -} - - -/* - Connect to a server and handle connection errors in case they occur. - - SYNOPSIS - connect_n_handle_errors() - q - context of connect "query" (command) - con - connection structure to be used - host, user, pass, - connection parameters - db, port, sock - - 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 VALUES - 1 - Connected - 0 - Not connected - -*/ - -int connect_n_handle_errors(struct st_command *command, - MYSQL* con, const char* host, - const char* user, const char* pass, - const char* db, int port, const char* sock) -{ - DYNAMIC_STRING *ds; - - ds= &ds_res; - - /* Only log if an error is expected */ - if (!command->abort_on_error && - !disable_query_log) - { - /* - Log the connect to result log - */ - 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); - replace_dynstr_append_uint(ds, port); - 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)) - { - var_set_errno(mysql_errno(con)); - handle_error(command, mysql_errno(con), mysql_error(con), - mysql_sqlstate(con), ds); - return 0; /* Not connected */ - } - - var_set_errno(0); - handle_no_error(command); - return 1; /* Connected */ -} - - -/* - Open a new connection to MySQL Server with the parameters - specified. Make the new connection the current connection. - - SYNOPSIS - do_connect() - q called command - - DESCRIPTION - connect(,,,[,[,[,[]]]]); - connect ,,,[,[,[,[]]]]; - - - name of the new connection - - hostname of server - - user to connect as - - password used when connecting - - initial db when connected - - server port - - server socket - - options to use for the connection - * SSL - use SSL if available - * COMPRESS - use compression if available - -*/ - -void do_connect(struct st_command *command) -{ - int con_port= opt_port; - char *con_options; - my_bool con_ssl= 0, con_compress= 0; - struct st_connection* con_slot; - - static DYNAMIC_STRING ds_connection_name; - static DYNAMIC_STRING ds_host; - static DYNAMIC_STRING ds_user; - static DYNAMIC_STRING ds_password; - static DYNAMIC_STRING ds_database; - static DYNAMIC_STRING ds_port; - static DYNAMIC_STRING ds_sock; - static DYNAMIC_STRING ds_options; - const struct command_arg connect_args[] = { - { "connection name", ARG_STRING, TRUE, &ds_connection_name, "Name of the connection" }, - { "host", ARG_STRING, TRUE, &ds_host, "Host to connect to" }, - { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, - { "passsword", ARG_STRING, FALSE, &ds_password, "Password used when connecting" }, - { "database", ARG_STRING, FALSE, &ds_database, "Database to select after connect" }, - { "port", ARG_STRING, FALSE, &ds_port, "Port to connect to" }, - { "socket", ARG_STRING, FALSE, &ds_sock, "Socket to connect with" }, - { "options", ARG_STRING, FALSE, &ds_options, "Options to use while connecting" } - }; - - DBUG_ENTER("do_connect"); - DBUG_PRINT("enter",("connect: %s", command->first_argument)); - - strip_parentheses(command); - check_command_args(command, command->first_argument, connect_args, - sizeof(connect_args)/sizeof(struct command_arg), - ','); - - /* Port */ - if (ds_port.length) - { - con_port= atoi(ds_port.str); - if (con_port == 0) - die("Illegal argument for port: '%s'", ds_port.str); - } - - /* Sock */ - if (ds_sock.length) - { - /* - If the socket is specified just as a name without path - append tmpdir in front - */ - if (*ds_sock.str != FN_LIBCHAR) - { - char buff[FN_REFLEN]; - fn_format(buff, ds_sock.str, TMPDIR, "", 0); - dynstr_set(&ds_sock, buff); - } - } - else - { - /* No socket specified, use default */ - dynstr_set(&ds_sock, unix_sock); - } - DBUG_PRINT("info", ("socket: %s", ds_sock.str)); - - - /* Options */ - con_options= ds_options.str; - while (*con_options) - { - char* end; - /* Step past any spaces in beginning of option*/ - while (*con_options && my_isspace(charset_info, *con_options)) - con_options++; - /* Find end of this option */ - end= con_options; - while (*end && !my_isspace(charset_info, *end)) - end++; - if (!strncmp(con_options, "SSL", 3)) - con_ssl= 1; - else if (!strncmp(con_options, "COMPRESS", 8)) - con_compress= 1; - else - die("Illegal option to connect: %.*s", - (int) (end - con_options), con_options); - /* Process next option */ - con_options= end; - } - - if (find_connection_by_name(ds_connection_name.str)) - die("Connection %s already exists", ds_connection_name.str); - - if (next_con != connections_end) - con_slot= next_con; - else - { - if (!(con_slot= find_connection_by_name("-closed_connection-"))) - die("Connection limit exhausted, you can have max %d connections", - (int) (sizeof(connections)/sizeof(struct st_connection))); - } - -#ifdef EMBEDDED_LIBRARY - con_slot->query_done= 1; -#endif - if (!mysql_init(&con_slot->mysql)) - die("Failed on mysql_init()"); - if (opt_compress || con_compress) - mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS); - mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, 0); - mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, - charset_info->csname); - if (opt_charsets_dir) - mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_DIR, - opt_charsets_dir); - -#ifdef HAVE_OPENSSL - if (opt_use_ssl || con_ssl) - { - mysql_ssl_set(&con_slot->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, - opt_ssl_capath, opt_ssl_cipher); -#if MYSQL_VERSION_ID >= 50000 - /* Turn on ssl_verify_server_cert only if host is "localhost" */ - opt_ssl_verify_server_cert= !strcmp(ds_host.str, "localhost"); - mysql_options(&con_slot->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, - &opt_ssl_verify_server_cert); -#endif - } -#endif - - /* Use default db name */ - if (ds_database.length == 0) - dynstr_set(&ds_database, opt_db); - - /* Special database to allow one to connect without a database name */ - if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*")) - dynstr_set(&ds_database, ""); - - if (connect_n_handle_errors(command, &con_slot->mysql, - ds_host.str,ds_user.str, - ds_password.str, ds_database.str, - con_port, ds_sock.str)) - { - DBUG_PRINT("info", ("Inserting connection %s in connection pool", - ds_connection_name.str)); - if (!(con_slot->name= my_strdup(ds_connection_name.str, MYF(MY_WME)))) - die("Out of memory"); - con_slot->name_len= strlen(con_slot->name); - cur_con= con_slot; - - if (con_slot == next_con) - next_con++; /* if we used the next_con slot, advance the pointer */ - } - - /* Update $mysql_get_server_version to that of current connection */ - var_set_mysql_get_server_version(&cur_con->mysql); - - dynstr_free(&ds_connection_name); - dynstr_free(&ds_host); - dynstr_free(&ds_user); - dynstr_free(&ds_password); - dynstr_free(&ds_database); - dynstr_free(&ds_port); - dynstr_free(&ds_sock); - dynstr_free(&ds_options); - DBUG_VOID_RETURN; -} - - -int do_done(struct st_command *command) -{ - /* Check if empty block stack */ - if (cur_block == block_stack) - { - if (*command->query != '}') - die("Stray 'end' command - end of block before beginning"); - die("Stray '}' - end of block before beginning"); - } - - /* Test if inner block has been executed */ - if (cur_block->ok && cur_block->cmd == cmd_while) - { - /* Pop block from stack, re-execute outer block */ - cur_block--; - parser.current_line = cur_block->line; - } - else - { - /* Pop block from stack, goto next line */ - cur_block--; - parser.current_line++; - } - return 0; -} - - -/* - Process start of a "if" or "while" statement - - SYNOPSIS - do_block() - cmd Type of block - q called command - - DESCRIPTION - if ([!]) - { - - } - - while ([!]) - { - - } - - Evaluates the and if it evaluates to - greater than zero executes the following code block. - A '!' can be used before the to indicate it should - be executed if it evaluates to zero. - -*/ - -void do_block(enum block_cmd cmd, struct st_command* command) -{ - char *p= command->first_argument; - const char *expr_start, *expr_end; - VAR v; - const char *cmd_name= (cmd == cmd_while ? "while" : "if"); - my_bool not_expr= FALSE; - DBUG_ENTER("do_block"); - DBUG_PRINT("enter", ("%s", cmd_name)); - - /* Check stack overflow */ - if (cur_block == block_stack_end) - die("Nesting too deeply"); - - /* Set way to find outer block again, increase line counter */ - cur_block->line= parser.current_line++; - - /* If this block is ignored */ - if (!cur_block->ok) - { - /* Inner block should be ignored too */ - cur_block++; - cur_block->cmd= cmd; - cur_block->ok= FALSE; - DBUG_VOID_RETURN; - } - - /* Parse and evaluate test expression */ - expr_start= strchr(p, '('); - if (!expr_start++) - die("missing '(' in %s", cmd_name); - - /* Check for ! */ - if (*expr_start == '!') - { - not_expr= TRUE; - expr_start++; /* Step past the '!' */ - } - /* Find ending ')' */ - expr_end= strrchr(expr_start, ')'); - if (!expr_end) - die("missing ')' in %s", cmd_name); - p= (char*)expr_end+1; - - while (*p && my_isspace(charset_info, *p)) - p++; - if (*p && *p != '{') - die("Missing '{' after %s. Found \"%s\"", cmd_name, p); - - var_init(&v,0,0,0,0); - eval_expr(&v, expr_start, &expr_end); - - /* Define inner block */ - cur_block++; - cur_block->cmd= cmd; - cur_block->ok= (v.int_val ? TRUE : FALSE); - - if (not_expr) - cur_block->ok = !cur_block->ok; - - DBUG_PRINT("info", ("OK: %d", cur_block->ok)); - - var_free(&v); - DBUG_VOID_RETURN; -} - - -void do_delimiter(struct st_command* command) -{ - char* p= command->first_argument; - DBUG_ENTER("do_delimiter"); - DBUG_PRINT("enter", ("first_argument: %s", command->first_argument)); - - while (*p && my_isspace(charset_info, *p)) - p++; - - if (!(*p)) - die("Can't set empty delimiter"); - - strmake(delimiter, p, sizeof(delimiter) - 1); - delimiter_length= strlen(delimiter); - - DBUG_PRINT("exit", ("delimiter: %s", delimiter)); - command->last_argument= p + delimiter_length; - DBUG_VOID_RETURN; -} - - -my_bool match_delimiter(int c, const char *delim, uint length) -{ - uint i; - char tmp[MAX_DELIMITER_LENGTH]; - - if (c != *delim) - return 0; - - for (i= 1; i < length && - (c= my_getc(cur_file->file)) == *(delim + i); - i++) - tmp[i]= c; - - if (i == length) - return 1; /* Found delimiter */ - - /* didn't find delimiter, push back things that we read */ - my_ungetc(c); - while (i > 1) - my_ungetc(tmp[--i]); - return 0; -} - - -my_bool end_of_query(int c) -{ - return match_delimiter(c, delimiter, delimiter_length); -} - - -/* - Read one "line" from the file - - SYNOPSIS - read_line - buf buffer for the read line - size size of the buffer i.e max size to read - - DESCRIPTION - This function actually reads several lines and adds them to the - buffer buf. It continues to read until it finds what it believes - is a complete query. - - Normally that means it will read lines until it reaches the - "delimiter" that marks end of query. Default delimiter is ';' - The function should be smart enough not to detect delimiter's - found inside strings surrounded with '"' and '\'' escaped strings. - - If the first line in a query starts with '#' or '-' this line is treated - as a comment. A comment is always terminated when end of line '\n' is - reached. - -*/ - -int read_line(char *buf, int size) -{ - char c, last_quote; - char *p= buf, *buf_end= buf + size - 1; - int skip_char= 0; - enum {R_NORMAL, R_Q, R_SLASH_IN_Q, - R_COMMENT, R_LINE_START} state= R_LINE_START; - DBUG_ENTER("read_line"); - LINT_INIT(last_quote); - - start_lineno= cur_file->lineno; - DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno)); - for (; p < buf_end ;) - { - skip_char= 0; - c= my_getc(cur_file->file); - if (feof(cur_file->file)) - { - found_eof: - if (cur_file->file != stdin) - { - my_fclose(cur_file->file, MYF(0)); - cur_file->file= 0; - } - my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); - cur_file->file_name= 0; - if (cur_file == file_stack) - { - /* We're back at the first file, check if - all { have matching } - */ - if (cur_block != block_stack) - die("Missing end of block"); - - *p= 0; - DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno)); - DBUG_RETURN(1); - } - cur_file--; - start_lineno= cur_file->lineno; - continue; - } - - if (c == '\n') - { - /* Line counting is independent of state */ - cur_file->lineno++; - - /* Convert cr/lf to lf */ - if (p != buf && *(p-1) == '\r') - p--; - } - - switch(state) { - case R_NORMAL: - if (end_of_query(c)) - { - *p= 0; - DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", - delimiter, cur_file->lineno)); - DBUG_RETURN(0); - } - else if ((c == '{' && - (!my_strnncoll_simple(charset_info, (const uchar*) "while", 5, - (uchar*) buf, min(5, p - buf), 0) || - !my_strnncoll_simple(charset_info, (const uchar*) "if", 2, - (uchar*) buf, min(2, p - buf), 0)))) - { - /* Only if and while commands can be terminated by { */ - *p++= c; - *p= 0; - DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d", - cur_file->lineno)); - DBUG_RETURN(0); - } - else if (c == '\'' || c == '"' || c == '`') - { - last_quote= c; - state= R_Q; - } - break; - - case R_COMMENT: - if (c == '\n') - { - /* Comments are terminated by newline */ - *p= 0; - DBUG_PRINT("exit", ("Found newline in comment at line: %d", - cur_file->lineno)); - DBUG_RETURN(0); - } - break; - - case R_LINE_START: - if (c == '#' || c == '-') - { - /* A # or - in the first position of the line - this is a comment */ - state = R_COMMENT; - } - else if (my_isspace(charset_info, c)) - { - /* Skip all space at begining of line */ - if (c == '\n') - { - /* Query hasn't started yet */ - start_lineno= cur_file->lineno; - DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d", - start_lineno)); - } - skip_char= 1; - } - else if (end_of_query(c)) - { - *p= 0; - DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", - delimiter, cur_file->lineno)); - DBUG_RETURN(0); - } - else if (c == '}') - { - /* A "}" need to be by itself in the begining of a line to terminate */ - *p++= c; - *p= 0; - DBUG_PRINT("exit", ("Found '}' in begining of a line at line: %d", - cur_file->lineno)); - DBUG_RETURN(0); - } - else if (c == '\'' || c == '"' || c == '`') - { - last_quote= c; - state= R_Q; - } - else - state= R_NORMAL; - break; - - case R_Q: - if (c == last_quote) - state= R_NORMAL; - else if (c == '\\') - state= R_SLASH_IN_Q; - break; - - case R_SLASH_IN_Q: - state= R_Q; - break; - - } - - if (!skip_char) - { - /* 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->file)) - goto found_eof; - c= my_getc(cur_file->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; - } - } - die("The input buffer is too small for this query.x\n" \ - "check your query or increase MAX_QUERY and recompile"); - DBUG_RETURN(0); -} - - -/* - Convert the read query to result format version 1 - - That is: After newline, all spaces need to be skipped - unless the previous char was a quote - - This is due to an old bug that has now been fixed, but the - version 1 output format is preserved by using this function - -*/ - -void convert_to_format_v1(char* query) -{ - int last_c_was_quote= 0; - char *p= query, *to= query; - char *end= strend(query); - char last_c; - - while (p <= end) - { - if (*p == '\n' && !last_c_was_quote) - { - *to++ = *p++; /* Save the newline */ - - /* Skip any spaces on next line */ - while (*p && my_isspace(charset_info, *p)) - p++; - - last_c_was_quote= 0; - } - else if (*p == '\'' || *p == '"' || *p == '`') - { - last_c= *p; - *to++ = *p++; - - /* Copy anything until the next quote of same type */ - while (*p && *p != last_c) - *to++ = *p++; - - *to++ = *p++; - - last_c_was_quote= 1; - } - else - { - *to++ = *p++; - last_c_was_quote= 0; - } - } -} - - -/* - Check a command that is about to be sent (or should have been - sent if parsing was enabled) to mysql server for - suspicious things and generate warnings. -*/ - -void scan_command_for_warnings(struct st_command *command) -{ - const char *ptr= command->query; - DBUG_ENTER("scan_command_for_warnings"); - DBUG_PRINT("enter", ("query: %s", command->query)); - - while(*ptr) - { - /* - Look for query's that lines that start with a -- comment - and has a mysqltest command - */ - if (ptr[0] == '\n' && - ptr[1] && ptr[1] == '-' && - ptr[2] && ptr[2] == '-' && - ptr[3]) - { - uint type; - char save; - char *end, *start= (char*)ptr+3; - /* Skip leading spaces */ - while (*start && my_isspace(charset_info, *start)) - start++; - end= start; - /* Find end of command(next space) */ - while (*end && !my_isspace(charset_info, *end)) - end++; - save= *end; - *end= 0; - DBUG_PRINT("info", ("Checking '%s'", start)); - type= find_type(start, &command_typelib, 1+2); - if (type) - warning_msg("Embedded mysqltest command '--%s' detected in " - "query '%s' was this intentional? ", - start, command->query); - *end= save; - } - - ptr++; - } - DBUG_VOID_RETURN; -} - -/* - Check for unexpected "junk" after the end of query - This is normally caused by missing delimiters or when - switching between different delimiters -*/ - -void check_eol_junk_line(const char *line) -{ - const char *p= line; - DBUG_ENTER("check_eol_junk_line"); - DBUG_PRINT("enter", ("line: %s", line)); - - /* Check for extra delimiter */ - if (*p && !strncmp(p, delimiter, delimiter_length)) - die("Extra delimiter \"%s\" found", delimiter); - - /* Allow trailing # comment */ - if (*p && *p != '#') - { - if (*p == '\n') - die("Missing delimiter"); - die("End of line junk detected: \"%s\"", p); - } - DBUG_VOID_RETURN; -} - -void check_eol_junk(const char *eol) -{ - const char *p= eol; - DBUG_ENTER("check_eol_junk"); - DBUG_PRINT("enter", ("eol: %s", eol)); - - /* Skip past all spacing chars and comments */ - while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) - { - /* Skip past comments started with # and ended with newline */ - if (*p && *p == '#') - { - p++; - while (*p && *p != '\n') - p++; - } - - /* Check this line */ - if (*p && *p == '\n') - check_eol_junk_line(p); - - if (*p) - p++; - } - - check_eol_junk_line(p); - - DBUG_VOID_RETURN; -} - - - -/* - Create a command from a set of lines - - SYNOPSIS - read_command() - command_ptr pointer where to return the new query - - DESCRIPTION - Converts lines returned by read_line into a command, this involves - parsing the first word in the read line to find the command type. - - A -- comment may contain a valid query as the first word after the - comment start. Thus it's always checked to see if that is the case. - The advantage with this approach is to be able to execute commands - terminated by new line '\n' regardless how many "delimiter" it contain. -*/ - -#define MAX_QUERY (256*1024*2) /* 256K -- a test in sp-big is >128K */ -static char read_command_buf[MAX_QUERY]; - -int read_command(struct st_command** command_ptr) -{ - char *p= read_command_buf; - struct st_command* command; - DBUG_ENTER("read_command"); - - if (parser.current_line < parser.read_lines) - { - get_dynamic(&q_lines, (uchar*) command_ptr, parser.current_line) ; - DBUG_RETURN(0); - } - if (!(*command_ptr= command= - (struct st_command*) my_malloc(sizeof(*command), - MYF(MY_WME|MY_ZEROFILL))) || - insert_dynamic(&q_lines, (uchar*) &command)) - die(NullS); - command->type= Q_UNKNOWN; - - read_command_buf[0]= 0; - if (read_line(read_command_buf, sizeof(read_command_buf))) - { - check_eol_junk(read_command_buf); - DBUG_RETURN(1); - } - - convert_to_format_v1(read_command_buf); - - DBUG_PRINT("info", ("query: %s", read_command_buf)); - if (*p == '#') - { - command->type= Q_COMMENT; - } - else if (p[0] == '-' && p[1] == '-') - { - command->type= Q_COMMENT_WITH_COMMAND; - p+= 2; /* Skip past -- */ - } - - /* Skip leading spaces */ - while (*p && my_isspace(charset_info, *p)) - p++; - - if (!(command->query_buf= command->query= my_strdup(p, MYF(MY_WME)))) - die("Out of memory"); - - /* Calculate first word length(the command), terminated by space or ( */ - p= command->query; - while (*p && !my_isspace(charset_info, *p) && *p != '(') - p++; - command->first_word_len= (uint) (p - command->query); - DBUG_PRINT("info", ("first_word: %.*s", - command->first_word_len, command->query)); - - /* Skip spaces between command and first argument */ - while (*p && my_isspace(charset_info, *p)) - p++; - command->first_argument= p; - - command->end= strend(command->query); - command->query_len= (command->end - command->query); - parser.read_lines++; - DBUG_RETURN(0); -} - - -static struct my_option my_long_options[] = -{ - {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, - 0, 0, 0, 0, 0, 0}, - {"basedir", 'b', "Basedir for tests.", (uchar**) &opt_basedir, - (uchar**) &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"character-sets-dir", OPT_CHARSETS_DIR, - "Directory where character sets are.", (uchar**) &opt_charsets_dir, - (uchar**) &opt_charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"compress", 'C', "Use the compressed server/client protocol.", - (uchar**) &opt_compress, (uchar**) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, - 0, 0, 0}, - {"cursor-protocol", OPT_CURSOR_PROTOCOL, "Use cursors for prepared statements.", - (uchar**) &cursor_protocol, (uchar**) &cursor_protocol, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"database", 'D', "Database to use.", (uchar**) &opt_db, (uchar**) &opt_db, 0, - GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, -#ifdef DBUG_OFF - {"debug", '#', "This is a non-debug version. Catch this and exit", - 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, -#else - {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", - 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, -#endif - {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", - (uchar**) &debug_check_flag, (uchar**) &debug_check_flag, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", - (uchar**) &debug_info_flag, (uchar**) &debug_info_flag, - 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"host", 'h', "Connect to host.", (uchar**) &opt_host, (uchar**) &opt_host, 0, - GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"include", 'i', "Include SQL before each test case.", (uchar**) &opt_include, - (uchar**) &opt_include, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"logdir", OPT_LOG_DIR, "Directory for log files", (uchar**) &opt_logdir, - (uchar**) &opt_logdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"mark-progress", OPT_MARK_PROGRESS, - "Write linenumber and elapsed time to .progress ", - (uchar**) &opt_mark_progress, (uchar**) &opt_mark_progress, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"max-connect-retries", OPT_MAX_CONNECT_RETRIES, - "Max number of connection attempts when connecting to server", - (uchar**) &opt_max_connect_retries, (uchar**) &opt_max_connect_retries, 0, - GET_INT, REQUIRED_ARG, 500, 1, 10000, 0, 0, 0}, - {"password", 'p', "Password to use when connecting to server.", - 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, - {"port", 'P', "Port number to use for connection or 0 for default to, in " - "order of preference, my.cnf, $MYSQL_TCP_PORT, " -#if MYSQL_PORT_DEFAULT == 0 - "/etc/services, " -#endif - "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", - (uchar**) &opt_port, - (uchar**) &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"ps-protocol", OPT_PS_PROTOCOL, "Use prepared statements protocol for communication", - (uchar**) &ps_protocol, (uchar**) &ps_protocol, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"quiet", 's', "Suppress all normal output.", (uchar**) &silent, - (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"record", 'r', "Record output of test_file into result file.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"result-file", 'R', "Read/Store result from/in this file.", - (uchar**) &result_file_name, (uchar**) &result_file_name, 0, - GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"server-arg", 'A', "Send option value to embedded server as a parameter.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"server-file", 'F', "Read embedded server arguments from file.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"silent", 's', "Suppress all normal output. Synonym for --quiet.", - (uchar**) &silent, (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"skip-safemalloc", OPT_SKIP_SAFEMALLOC, - "Don't use the memory allocation checking.", 0, 0, 0, GET_NO_ARG, NO_ARG, - 0, 0, 0, 0, 0, 0}, - {"sleep", 'T', "Sleep always this many seconds on sleep commands.", - (uchar**) &opt_sleep, (uchar**) &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, -1, 0, - 0, 0, 0}, - {"socket", 'S', "Socket file to use for connection.", - (uchar**) &unix_sock, (uchar**) &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, - 0, 0, 0}, - {"sp-protocol", OPT_SP_PROTOCOL, "Use stored procedures for select", - (uchar**) &sp_protocol, (uchar**) &sp_protocol, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"tail-lines", OPT_TAIL_LINES, - "Number of lines of the resul to include in a failure report", - (uchar**) &opt_tail_lines, (uchar**) &opt_tail_lines, 0, - GET_INT, REQUIRED_ARG, 0, 0, 10000, 0, 0, 0}, -#include "sslopt-longopts.h" - {"test-file", 'x', "Read test from/in this file (default stdin).", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"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.", (uchar**) &opt_user, (uchar**) &opt_user, 0, - GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"verbose", 'v', "Write more.", (uchar**) &verbose, (uchar**) &verbose, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"version", 'V', "Output version information and exit.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select", - (uchar**) &view_protocol, (uchar**) &view_protocol, 0, - GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, - { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} -}; - - -#include - -void print_version(void) -{ - printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname,MTEST_VERSION, - MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); -} - -void usage() -{ - print_version(); - printf("MySQL AB, by Sasha, Matt, Monty & Jani\n"); - printf("This software comes with ABSOLUTELY NO WARRANTY\n\n"); - printf("Runs a test against the mysql server and compares output with a results file.\n\n"); - printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname); - my_print_help(my_long_options); - printf(" --no-defaults Don't read default options from any options file.\n"); - my_print_variables(my_long_options); -} - -#include - - -/* - Read arguments for embedded server and put them into - embedded_server_args[] -*/ - -void read_embedded_server_arguments(const char *name) -{ - char argument[1024],buff[FN_REFLEN], *str=0; - FILE *file; - - if (!test_if_hard_path(name)) - { - strxmov(buff, opt_basedir, name, NullS); - name=buff; - } - fn_format(buff, name, "", "", MY_UNPACK_FILENAME); - - if (!embedded_server_arg_count) - { - embedded_server_arg_count=1; - embedded_server_args[0]= (char*) ""; /* Progname */ - } - if (!(file=my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(MY_WME)))) - die("Failed to open file '%s'", buff); - - while (embedded_server_arg_count < MAX_EMBEDDED_SERVER_ARGS && - (str=fgets(argument,sizeof(argument), file))) - { - *(strend(str)-1)=0; /* Remove end newline */ - if (!(embedded_server_args[embedded_server_arg_count]= - (char*) my_strdup(str,MYF(MY_WME)))) - { - my_fclose(file,MYF(0)); - die("Out of memory"); - - } - embedded_server_arg_count++; - } - my_fclose(file,MYF(0)); - if (str) - die("Too many arguments in option file: %s",name); - - return; -} - - -static my_bool -get_one_option(int optid, const struct my_option *opt __attribute__((unused)), - char *argument) -{ - switch(optid) { - case '#': -#ifndef DBUG_OFF - DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace"); - debug_check_flag= 1; -#endif - break; - case 'r': - record = 1; - break; - case 'x': - { - char buff[FN_REFLEN]; - if (!test_if_hard_path(argument)) - { - strxmov(buff, opt_basedir, argument, NullS); - argument= buff; - } - fn_format(buff, argument, "", "", MY_UNPACK_FILENAME); - DBUG_ASSERT(cur_file == file_stack && cur_file->file == 0); - if (!(cur_file->file= - my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) - die("Could not open '%s' for reading, errno: %d", buff, errno); - cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); - cur_file->lineno= 1; - 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, "", "", MY_UNPACK_FILENAME); - timer_file= buff; - unlink(timer_file); /* Ignore error, may not exist */ - break; - } - case 'p': - if (argument) - { - my_free(opt_pass, MYF(MY_ALLOW_ZERO_PTR)); - opt_pass= my_strdup(argument, MYF(MY_FAE)); - while (*argument) *argument++= 'x'; /* Destroy argument */ - tty_password= 0; - } - else - tty_password= 1; - break; -#include - case 't': - strnmov(TMPDIR, argument, sizeof(TMPDIR)); - break; - case 'A': - if (!embedded_server_arg_count) - { - embedded_server_arg_count=1; - embedded_server_args[0]= (char*) ""; - } - if (embedded_server_arg_count == MAX_EMBEDDED_SERVER_ARGS-1 || - !(embedded_server_args[embedded_server_arg_count++]= - my_strdup(argument, MYF(MY_FAE)))) - { - die("Can't use server argument"); - } - break; - case 'F': - read_embedded_server_arguments(argument); - break; - case OPT_SKIP_SAFEMALLOC: -#ifdef SAFEMALLOC - sf_malloc_quick=1; -#endif - break; - case 'V': - print_version(); - exit(0); - case '?': - usage(); - exit(0); - } - return 0; -} - - -int parse_args(int argc, char **argv) -{ - load_defaults("my",load_default_groups,&argc,&argv); - default_argv= argv; - - if ((handle_options(&argc, &argv, my_long_options, get_one_option))) - exit(1); - - if (argc > 1) - { - usage(); - exit(1); - } - if (argc == 1) - opt_db= *argv; - if (tty_password) - opt_pass= get_tty_password(NullS); /* purify tested */ - if (debug_info_flag) - my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; - if (debug_check_flag) - my_end_arg= MY_CHECK_ERROR; - - return 0; -} - -/* - Write the content of str into file - - SYNOPSIS - str_to_file2 - fname - name of file to truncate/create and write to - str - content to write to file - size - size of content witten to file - append - append to file instead of overwriting old file -*/ - -void str_to_file2(const char *fname, char *str, int size, my_bool append) -{ - int fd; - char buff[FN_REFLEN]; - int flags= O_WRONLY | O_CREAT; - if (!test_if_hard_path(fname)) - { - strxmov(buff, opt_basedir, fname, NullS); - fname= buff; - } - fn_format(buff, fname, "", "", MY_UNPACK_FILENAME); - - if (!append) - flags|= O_TRUNC; - if ((fd= my_open(buff, flags, - MYF(MY_WME | MY_FFNF))) < 0) - die("Could not open '%s' for writing, errno: %d", buff, errno); - if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR) - die("Could not find end of file '%s', errno: %d", buff, errno); - if (my_write(fd, (uchar*)str, size, MYF(MY_WME|MY_FNABP))) - die("write failed, errno: %d", errno); - my_close(fd, MYF(0)); -} - -/* - Write the content of str into file - - SYNOPSIS - str_to_file - fname - name of file to truncate/create and write to - str - content to write to file - size - size of content witten to file -*/ - -void str_to_file(const char *fname, char *str, int size) -{ - str_to_file2(fname, str, size, FALSE); -} - - -void dump_result_to_log_file(char *buf, int size) -{ - char log_file[FN_REFLEN]; - str_to_file(fn_format(log_file, result_file_name, opt_logdir, ".log", - *opt_logdir ? MY_REPLACE_DIR | MY_REPLACE_EXT : - MY_REPLACE_EXT), - buf, size); - fprintf(stderr, "\nMore results from queries before failure can be found in %s\n", - log_file); -} - -void dump_progress(void) -{ - char progress_file[FN_REFLEN]; - str_to_file(fn_format(progress_file, result_file_name, - opt_logdir, ".progress", - *opt_logdir ? MY_REPLACE_DIR | MY_REPLACE_EXT : - MY_REPLACE_EXT), - ds_progress.str, ds_progress.length); -} - -void dump_warning_messages(void) -{ - char warn_file[FN_REFLEN]; - - str_to_file(fn_format(warn_file, result_file_name, opt_logdir, ".warnings", - *opt_logdir ? MY_REPLACE_DIR | MY_REPLACE_EXT : - MY_REPLACE_EXT), - ds_warning_messages.str, ds_warning_messages.length); -} - -void check_regerr(my_regex_t* r, int err) -{ - char err_buf[1024]; - - if (err) - { - my_regerror(err,r,err_buf,sizeof(err_buf)); - die("Regex error: %s\n", err_buf); - } -} - - -#ifdef __WIN__ - -DYNAMIC_ARRAY patterns; - -/* - init_win_path_patterns - - DESCRIPTION - Setup string patterns that will be used to detect filenames that - needs to be converted from Win to Unix format - -*/ - -void init_win_path_patterns() -{ - /* List of string patterns to match in order to find paths */ - const char* paths[] = { "$MYSQL_TEST_DIR", - "$MYSQL_TMP_DIR", - "$MYSQLTEST_VARDIR", - "$MASTER_MYSOCK", - "./test/" }; - int num_paths= sizeof(paths)/sizeof(char*); - int i; - char* p; - - DBUG_ENTER("init_win_path_patterns"); - - my_init_dynamic_array(&patterns, sizeof(const char*), 16, 16); - - /* Loop through all paths in the array */ - for (i= 0; i < num_paths; i++) - { - VAR* v; - if (*(paths[i]) == '$') - { - v= var_get(paths[i], 0, 0, 0); - p= my_strdup(v->str_val, MYF(MY_FAE)); - } - else - p= my_strdup(paths[i], MYF(MY_FAE)); - - /* Don't insert zero length strings in patterns array */ - if (strlen(p) == 0) - { - my_free(p, MYF(0)); - continue; - } - - if (insert_dynamic(&patterns, (uchar*) &p)) - die(NullS); - - DBUG_PRINT("info", ("p: %s", p)); - while (*p) - { - if (*p == '/') - *p='\\'; - p++; - } - } - DBUG_VOID_RETURN; -} - -void free_win_path_patterns() -{ - uint i= 0; - for (i=0 ; i < patterns.elements ; i++) - { - const char** pattern= dynamic_element(&patterns, i, const char**); - my_free((char*) *pattern, MYF(0)); - } - delete_dynamic(&patterns); -} - -/* - fix_win_paths - - DESCRIPTION - Search the string 'val' for the patterns that are known to be - strings that contain filenames. Convert all \ to / in the - filenames that are found. - - Ex: - val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' - => $MYSQL_TEST_DIR is found by strstr - => all \ from c:\mysql\m... until next space is converted into / -*/ - -void fix_win_paths(const char *val, int len) -{ - uint i; - char *p; - - DBUG_ENTER("fix_win_paths"); - for (i= 0; i < patterns.elements; i++) - { - const char** pattern= dynamic_element(&patterns, i, const char**); - DBUG_PRINT("info", ("pattern: %s", *pattern)); - - /* Search for the path in string */ - while ((p= strstr(val, *pattern))) - { - DBUG_PRINT("info", ("Found %s in val p: %s", *pattern, p)); - - while (*p && !my_isspace(charset_info, *p)) - { - if (*p == '\\') - *p= '/'; - p++; - } - DBUG_PRINT("info", ("Converted \\ to /, p: %s", p)); - } - } - DBUG_PRINT("exit", (" val: %s, len: %d", val, len)); - DBUG_VOID_RETURN; -} -#endif - - - -/* - Append the result for one field to the dynamic string ds -*/ - -void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, - char* val, ulonglong len, my_bool is_null) -{ - char null[]= "NULL"; - - if (col_idx < max_replace_column && replace_column[col_idx]) - { - val= replace_column[col_idx]; - len= strlen(val); - } - else if (is_null) - { - val= null; - len= 4; - } -#ifdef __WIN__ - else if ((field->type == MYSQL_TYPE_DOUBLE || - field->type == MYSQL_TYPE_FLOAT ) && - field->decimals >= 31) - { - /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */ - char *start= strchr(val, 'e'); - if (start && strlen(start) >= 5 && - (start[1] == '-' || start[1] == '+') && start[2] == '0') - { - start+=2; /* Now points at first '0' */ - if (field->flags & ZEROFILL_FLAG) - { - /* Move all chars before the first '0' one step right */ - memmove(val + 1, val, start - val); - *val= '0'; - } - else - { - /* Move all chars after the first '0' one step left */ - memmove(start, start + 1, strlen(start)); - len--; - } - } - } -#endif - - if (!display_result_vertically) - { - if (col_idx) - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, (int)len); - } - else - { - dynstr_append(ds, field->name); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_mem(ds, val, (int)len); - dynstr_append_mem(ds, "\n", 1); - } -} - - -/* - Append all results to the dynamic string separated with '\t' - Values may be converted with 'replace_column' -*/ - -void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) -{ - MYSQL_ROW row; - uint num_fields= mysql_num_fields(res); - MYSQL_FIELD *fields= mysql_fetch_fields(res); - ulong *lengths; - - while ((row = mysql_fetch_row(res))) - { - uint i; - lengths = mysql_fetch_lengths(res); - for (i = 0; i < num_fields; i++) - append_field(ds, i, &fields[i], - row[i], lengths[i], !row[i]); - if (!display_result_vertically) - dynstr_append_mem(ds, "\n", 1); - } -} - - -/* - Append all results from ps execution to the dynamic string separated - with '\t'. Values may be converted with 'replace_column' -*/ - -void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, - MYSQL_FIELD *fields, uint num_fields) -{ - MYSQL_BIND *my_bind; - my_bool *is_null; - ulong *length; - uint i; - - /* Allocate array with bind structs, lengths and NULL flags */ - my_bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), - MYF(MY_WME | MY_FAE | MY_ZEROFILL)); - length= (ulong*) my_malloc(num_fields * sizeof(ulong), - MYF(MY_WME | MY_FAE)); - is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), - MYF(MY_WME | MY_FAE)); - - /* Allocate data for the result of each field */ - for (i= 0; i < num_fields; i++) - { - uint max_length= fields[i].max_length + 1; - my_bind[i].buffer_type= MYSQL_TYPE_STRING; - my_bind[i].buffer= (char *)my_malloc(max_length, MYF(MY_WME | MY_FAE)); - my_bind[i].buffer_length= max_length; - my_bind[i].is_null= &is_null[i]; - my_bind[i].length= &length[i]; - - DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu", - i, my_bind[i].buffer_type, my_bind[i].buffer_length)); - } - - if (mysql_stmt_bind_result(stmt, my_bind)) - die("mysql_stmt_bind_result failed: %d: %s", - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - - while (mysql_stmt_fetch(stmt) == 0) - { - for (i= 0; i < num_fields; i++) - append_field(ds, i, &fields[i], my_bind[i].buffer, - *my_bind[i].length, *my_bind[i].is_null); - if (!display_result_vertically) - dynstr_append_mem(ds, "\n", 1); - } - - if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) - die("fetch didn't end with MYSQL_NO_DATA from statement: %d %s", - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - - for (i= 0; i < num_fields; i++) - { - /* Free data for output */ - my_free(my_bind[i].buffer, MYF(MY_WME | MY_FAE)); - } - /* Free array with bind structs, lengths and NULL flags */ - my_free(my_bind , MYF(MY_WME | MY_FAE)); - my_free(length , MYF(MY_WME | MY_FAE)); - my_free(is_null , MYF(MY_WME | MY_FAE)); -} - - -/* - Append metadata for fields to output -*/ - -void append_metadata(DYNAMIC_STRING *ds, - MYSQL_FIELD *field, - uint num_fields) -{ - MYSQL_FIELD *field_end; - dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" - "Column_alias\tType\tLength\tMax length\tIs_null\t" - "Flags\tDecimals\tCharsetnr\n"); - - for (field_end= field+num_fields ; - field < field_end ; - field++) - { - 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); - replace_dynstr_append_uint(ds, field->type); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_uint(ds, field->length); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_uint(ds, field->max_length); - 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); - replace_dynstr_append_uint(ds, field->flags); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_uint(ds, field->decimals); - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append_uint(ds, field->charsetnr); - dynstr_append_mem(ds, "\n", 1); - } -} - - -/* - Append affected row count and other info to output -*/ - -void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, - const char *info) -{ - char buf[40], buff2[21]; - sprintf(buf,"affected rows: %s\n", llstr(affected_rows, buff2)); - dynstr_append(ds, buf); - if (info) - { - dynstr_append(ds, "info: "); - dynstr_append(ds, info); - dynstr_append_mem(ds, "\n", 1); - } -} - - -/* - Display the table headings with the names tab separated -*/ - -void append_table_headings(DYNAMIC_STRING *ds, - MYSQL_FIELD *field, - uint num_fields) -{ - uint col_idx; - for (col_idx= 0; col_idx < num_fields; col_idx++) - { - if (col_idx) - dynstr_append_mem(ds, "\t", 1); - replace_dynstr_append(ds, field[col_idx].name); - } - dynstr_append_mem(ds, "\n", 1); -} - -/* - Fetch warnings from server and append to ds - - RETURN VALUE - Number of warnings appended to ds -*/ - -int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) -{ - uint count; - MYSQL_RES *warn_res; - DBUG_ENTER("append_warnings"); - - if (!(count= mysql_warning_count(mysql))) - DBUG_RETURN(0); - - /* - If one day we will support execution of multi-statements - through PS API we should not issue SHOW WARNINGS until - we have not read all results... - */ - DBUG_ASSERT(!mysql_more_results(mysql)); - - if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) - die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); - - if (!(warn_res= mysql_store_result(mysql))) - die("Warning count is %u but didn't get any warnings", - count); - - append_result(ds, warn_res); - mysql_free_result(warn_res); - - DBUG_PRINT("warnings", ("%s", ds->str)); - - DBUG_RETURN(count); -} - - -/* - Run query using MySQL C API - - SYNOPSIS - run_query_normal() - mysql mysql handle - command current command pointer - flags flags indicating if we should SEND and/or REAP - query query string to execute - query_len length query string to execute - ds output buffer where to store result form query -*/ - -void run_query_normal(struct st_connection *cn, struct st_command *command, - int flags, char *query, int query_len, - DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) -{ - MYSQL_RES *res= 0; - MYSQL *mysql= &cn->mysql; - int err= 0, counter= 0; - DBUG_ENTER("run_query_normal"); - DBUG_PRINT("enter",("flags: %d", flags)); - DBUG_PRINT("enter", ("query: '%-.60s'", query)); - - if (flags & QUERY_SEND_FLAG) - { - /* - Send the query - */ - if (do_send_query(cn, query, query_len, flags)) - { - handle_error(command, mysql_errno(mysql), mysql_error(mysql), - mysql_sqlstate(mysql), ds); - goto end; - } - } -#ifdef EMBEDDED_LIBRARY - /* - Here we handle 'reap' command, so we need to check if the - query's thread was finished and probably wait - */ - else if (flags & QUERY_REAP_FLAG) - wait_query_thread_end(cn); -#endif /*EMBEDDED_LIBRARY*/ - if (!(flags & QUERY_REAP_FLAG)) - DBUG_VOID_RETURN; - - do - { - /* - When on first result set, call mysql_read_query_result to retrieve - answer to the query sent earlier - */ - if ((counter==0) && mysql_read_query_result(mysql)) - { - handle_error(command, mysql_errno(mysql), mysql_error(mysql), - mysql_sqlstate(mysql), ds); - goto end; - - } - - /* - Store the result of the query if it will return any fields - */ - if (mysql_field_count(mysql) && ((res= mysql_store_result(mysql)) == 0)) - { - handle_error(command, mysql_errno(mysql), mysql_error(mysql), - mysql_sqlstate(mysql), ds); - goto end; - } - - if (!disable_result_log) - { - ulonglong affected_rows; /* Ok to be undef if 'disable_info' is set */ - LINT_INIT(affected_rows); - - if (res) - { - MYSQL_FIELD *fields= mysql_fetch_fields(res); - uint num_fields= mysql_num_fields(res); - - if (display_metadata) - append_metadata(ds, fields, num_fields); - - if (!display_result_vertically) - append_table_headings(ds, fields, num_fields); - - append_result(ds, res); - } - - /* - Need to call mysql_affected_rows() before the "new" - query to find the warnings - */ - if (!disable_info) - affected_rows= mysql_affected_rows(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)) - { - if (append_warnings(ds_warnings, mysql) || ds_warnings->length) - { - dynstr_append_mem(ds, "Warnings:\n", 10); - dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length); - } - } - - if (!disable_info) - append_info(ds, affected_rows, mysql_info(mysql)); - } - - if (res) - { - mysql_free_result(res); - res= 0; - } - counter++; - } while (!(err= mysql_next_result(mysql))); - if (err > 0) - { - /* We got an error from mysql_next_result, maybe expected */ - handle_error(command, mysql_errno(mysql), mysql_error(mysql), - mysql_sqlstate(mysql), ds); - goto end; - } - DBUG_ASSERT(err == -1); /* Successful and there are no more results */ - - /* If we come here the query is both executed and read successfully */ - handle_no_error(command); - -end: - - /* - We save the return code (mysql_errno(mysql)) from the last call sent - to the server into the mysqltest builtin variable $mysql_errno. This - variable then can be used from the test case itself. - */ - var_set_errno(mysql_errno(mysql)); - DBUG_VOID_RETURN; -} - - -/* - Handle errors which occurred during execution - - SYNOPSIS - handle_error() - 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. -*/ - -void handle_error(struct st_command *command, - unsigned int err_errno, const char *err_error, - const char *err_sqlstate, DYNAMIC_STRING *ds) -{ - uint i; - - DBUG_ENTER("handle_error"); - - if (command->require_file[0]) - { - /* - The query after a "--require" failed. This is fine as long the server - returned a valid reponse. Don't allow 2013 or 2006 to trigger an - abort_not_supported_test - */ - if (err_errno == CR_SERVER_LOST || - err_errno == CR_SERVER_GONE_ERROR) - die("require query '%s' failed: %d: %s", command->query, - err_errno, err_error); - - /* Abort the run of this test, pass the failed query as reason */ - abort_not_supported_test("Query '%s' failed, required functionality " \ - "not supported", command->query); - } - - if (command->abort_on_error) - die("query '%s' failed: %d: %s", command->query, err_errno, err_error); - - DBUG_PRINT("info", ("expected_errors.count: %d", - command->expected_errors.count)); - for (i= 0 ; (uint) i < command->expected_errors.count ; i++) - { - if (((command->expected_errors.err[i].type == ERR_ERRNO) && - (command->expected_errors.err[i].code.errnum == err_errno)) || - ((command->expected_errors.err[i].type == ERR_SQLSTATE) && - (strncmp(command->expected_errors.err[i].code.sqlstate, - err_sqlstate, SQLSTATE_LENGTH) == 0))) - { - if (!disable_result_log) - { - if (command->expected_errors.count == 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 (command->expected_errors.err[0].type == ERR_SQLSTATE || - (command->expected_errors.err[0].type == ERR_ERRNO && - command->expected_errors.err[0].code.errnum != 0)) - dynstr_append(ds,"Got one of the listed errors\n"); - } - /* OK */ - DBUG_VOID_RETURN; - } - } - - DBUG_PRINT("info",("i: %d expected_errors: %d", i, - command->expected_errors.count)); - - if (!disable_result_log) - { - dynstr_append_mem(ds, "ERROR ",6); - replace_dynstr_append(ds, err_sqlstate); - dynstr_append_mem(ds, ": ", 2); - replace_dynstr_append(ds, err_error); - dynstr_append_mem(ds, "\n", 1); - } - - if (i) - { - if (command->expected_errors.err[0].type == ERR_ERRNO) - die("query '%s' failed with wrong errno %d: '%s', instead of %d...", - command->query, err_errno, err_error, - command->expected_errors.err[0].code.errnum); - else - die("query '%s' failed with wrong sqlstate %s: '%s', instead of %s...", - command->query, err_sqlstate, err_error, - command->expected_errors.err[0].code.sqlstate); - } - - DBUG_VOID_RETURN; -} - - -/* - Handle absence of errors after execution - - SYNOPSIS - handle_no_error() - q - context of query - - RETURN VALUE - error - function will not return -*/ - -void handle_no_error(struct st_command *command) -{ - DBUG_ENTER("handle_no_error"); - - if (command->expected_errors.err[0].type == ERR_ERRNO && - command->expected_errors.err[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...", - command->query, command->expected_errors.err[0].code.errnum); - } - else if (command->expected_errors.err[0].type == ERR_SQLSTATE && - strcmp(command->expected_errors.err[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...", - command->query, command->expected_errors.err[0].code.sqlstate); - } - - DBUG_VOID_RETURN; -} - - -/* - Run query using prepared statement C API - - SYNPOSIS - run_query_stmt - mysql - mysql handle - command - currrent command pointer - query - query string to execute - query_len - length query string to execute - ds - output buffer where to store result form query - - RETURN VALUE - error - function will not return -*/ - -void run_query_stmt(MYSQL *mysql, struct st_command *command, - char *query, int query_len, DYNAMIC_STRING *ds, - DYNAMIC_STRING *ds_warnings) -{ - MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ - MYSQL_STMT *stmt; - DYNAMIC_STRING ds_prepare_warnings; - DYNAMIC_STRING ds_execute_warnings; - DBUG_ENTER("run_query_stmt"); - DBUG_PRINT("query", ("'%-.60s'", query)); - - /* - Init a new stmt if it's not already one created for this connection - */ - if(!(stmt= cur_con->stmt)) - { - if (!(stmt= mysql_stmt_init(mysql))) - die("unable to init stmt structure"); - cur_con->stmt= stmt; - } - - /* Init dynamic strings for warnings */ - if (!disable_warnings) - { - init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); - init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); - } - - /* - Prepare the query - */ - if (mysql_stmt_prepare(stmt, query, query_len)) - { - handle_error(command, mysql_stmt_errno(stmt), - mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); - goto end; - } - - /* - Get the warnings from mysql_stmt_prepare and keep them in a - separate string - */ - if (!disable_warnings) - append_warnings(&ds_prepare_warnings, mysql); - - /* - No need to call mysql_stmt_bind_param() because we have no - parameter markers. - */ - -#if MYSQL_VERSION_ID >= 50000 - if (cursor_protocol_enabled) - { - /* - Use cursor when retrieving result - */ - ulong type= CURSOR_TYPE_READ_ONLY; - if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) - die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s", - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - } -#endif - - /* - Execute the query - */ - if (mysql_stmt_execute(stmt)) - { - handle_error(command, mysql_stmt_errno(stmt), - mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); - goto end; - } - - /* - When running in cursor_protocol get the warnings from execute here - and keep them in a separate string for later. - */ - if (cursor_protocol_enabled && !disable_warnings) - append_warnings(&ds_execute_warnings, mysql); - - /* - We instruct that we want to update the "max_length" field in - mysql_stmt_store_result(), this is our only way to know how much - buffer to allocate for result data - */ - { - my_bool one= 1; - if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) - die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s", - mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - } - - /* - If we got here the statement succeeded and was expected to do so, - get data. Note that this can still give errors found during execution! - Store the result of the query if if will return any fields - */ - if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) - { - handle_error(command, mysql_stmt_errno(stmt), - mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); - goto end; - } - - /* If we got here the statement was both executed and read successfully */ - handle_no_error(command); - if (!disable_result_log) - { - /* - Not all statements creates a result set. If there is one we can - now create another normal result set that contains the meta - data. This set can be handled almost like any other non prepared - statement result set. - */ - if ((res= mysql_stmt_result_metadata(stmt)) != NULL) - { - /* Take the column count from meta info */ - MYSQL_FIELD *fields= mysql_fetch_fields(res); - uint num_fields= mysql_num_fields(res); - - if (display_metadata) - append_metadata(ds, fields, num_fields); - - if (!display_result_vertically) - append_table_headings(ds, fields, num_fields); - - append_stmt_result(ds, stmt, fields, num_fields); - - mysql_free_result(res); /* Free normal result set with meta data */ - - /* Clear prepare warnings */ - dynstr_set(&ds_prepare_warnings, NULL); - } - else - { - /* - This is a query without resultset - */ - } - - if (!disable_warnings) - { - /* Get the warnings from execute */ - - /* Append warnings to ds - if there are any */ - if (append_warnings(&ds_execute_warnings, mysql) || - ds_execute_warnings.length || - ds_prepare_warnings.length || - ds_warnings->length) - { - dynstr_append_mem(ds, "Warnings:\n", 10); - if (ds_warnings->length) - dynstr_append_mem(ds, ds_warnings->str, - ds_warnings->length); - if (ds_prepare_warnings.length) - dynstr_append_mem(ds, ds_prepare_warnings.str, - ds_prepare_warnings.length); - if (ds_execute_warnings.length) - dynstr_append_mem(ds, ds_execute_warnings.str, - ds_execute_warnings.length); - } - } - - if (!disable_info) - append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql)); - - } - -end: - if (!disable_warnings) - { - dynstr_free(&ds_prepare_warnings); - dynstr_free(&ds_execute_warnings); - } - - - /* Close the statement if - no reconnect, need new prepare */ - if (mysql->reconnect) - { - mysql_stmt_close(stmt); - cur_con->stmt= NULL; - } - - /* - We save the return code (mysql_stmt_errno(stmt)) from the last call sent - to the server into the mysqltest builtin variable $mysql_errno. This - variable then can be used from the test case itself. - */ - - var_set_errno(mysql_stmt_errno(stmt)); - - DBUG_VOID_RETURN; -} - - - -/* - Create a util connection if one does not already exists - and use that to run the query - This is done to avoid implict commit when creating/dropping objects such - as view, sp etc. -*/ - -int util_query(MYSQL* org_mysql, const char* query){ - - MYSQL* mysql; - DBUG_ENTER("util_query"); - - if(!(mysql= cur_con->util_mysql)) - { - DBUG_PRINT("info", ("Creating util_mysql")); - if (!(mysql= mysql_init(mysql))) - die("Failed in mysql_init()"); - - /* enable local infile, in non-binary builds often disabled by default */ - mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0); - safe_connect(mysql, "util", org_mysql->host, org_mysql->user, - org_mysql->passwd, org_mysql->db, org_mysql->port, - org_mysql->unix_socket); - - cur_con->util_mysql= mysql; - } - - return mysql_query(mysql, query); -} - - - -/* - Run query - - SYNPOSIS - run_query() - mysql mysql handle - command currrent command pointer - - flags control the phased/stages of query execution to be performed - if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG - is on the result will be read - for regular query, both bits must be on -*/ - -void run_query(struct st_connection *cn, struct st_command *command, int flags) -{ - MYSQL *mysql= &cn->mysql; - DYNAMIC_STRING *ds; - DYNAMIC_STRING *save_ds= NULL; - DYNAMIC_STRING ds_result; - DYNAMIC_STRING ds_sorted; - DYNAMIC_STRING ds_warnings; - DYNAMIC_STRING eval_query; - char *query; - int query_len; - my_bool view_created= 0, sp_created= 0; - my_bool complete_query= ((flags & QUERY_SEND_FLAG) && - (flags & QUERY_REAP_FLAG)); - DBUG_ENTER("run_query"); - - init_dynamic_string(&ds_warnings, NULL, 0, 256); - - /* Scan for warning before sending to server */ - scan_command_for_warnings(command); - - /* - Evaluate query if this is an eval command - */ - if (command->type == Q_EVAL) - { - init_dynamic_string(&eval_query, "", command->query_len+256, 1024); - do_eval(&eval_query, command->query, command->end, FALSE); - query = eval_query.str; - query_len = eval_query.length; - } - else - { - query = command->query; - query_len = strlen(query); - } - - /* - When command->require_file is set the output of _this_ query - should be compared with an already existing file - Create a temporary dynamic string to contain the output from - this query. - */ - if (command->require_file[0]) - { - init_dynamic_string(&ds_result, "", 1024, 1024); - ds= &ds_result; - } - else - ds= &ds_res; - - /* - Log the query into the output buffer - */ - if (!disable_query_log && (flags & QUERY_SEND_FLAG)) - { - replace_dynstr_append_mem(ds, query, query_len); - dynstr_append_mem(ds, delimiter, delimiter_length); - dynstr_append_mem(ds, "\n", 1); - } - - if (view_protocol_enabled && - complete_query && - match_re(&view_re, query)) - { - /* - Create the query as a view. - Use replace since view can exist from a failed mysqltest run - */ - DYNAMIC_STRING query_str; - init_dynamic_string(&query_str, - "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ", - query_len+64, 256); - dynstr_append_mem(&query_str, query, query_len); - if (util_query(mysql, query_str.str)) - { - /* - Failed to create the view, this is not fatal - just run the query the normal way - */ - DBUG_PRINT("view_create_error", - ("Failed to create view '%s': %d: %s", query_str.str, - mysql_errno(mysql), mysql_error(mysql))); - - /* Log error to create view */ - verbose_msg("Failed to create view '%s' %d: %s", query_str.str, - mysql_errno(mysql), mysql_error(mysql)); - } - else - { - /* - Yes, it was possible to create this query as a view - */ - view_created= 1; - query= (char*)"SELECT * FROM mysqltest_tmp_v"; - query_len = strlen(query); - - /* - Collect warnings from create of the view that should otherwise - have been produced when the SELECT was executed - */ - append_warnings(&ds_warnings, cur_con->util_mysql); - } - - dynstr_free(&query_str); - - } - - if (sp_protocol_enabled && - complete_query && - match_re(&sp_re, query)) - { - /* - Create the query as a stored procedure - Drop first since sp can exist from a failed mysqltest run - */ - DYNAMIC_STRING query_str; - init_dynamic_string(&query_str, - "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;", - query_len+64, 256); - util_query(mysql, query_str.str); - dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n"); - dynstr_append_mem(&query_str, query, query_len); - if (util_query(mysql, query_str.str)) - { - /* - Failed to create the stored procedure for this query, - this is not fatal just run the query the normal way - */ - DBUG_PRINT("sp_create_error", - ("Failed to create sp '%s': %d: %s", query_str.str, - mysql_errno(mysql), mysql_error(mysql))); - - /* Log error to create sp */ - verbose_msg("Failed to create sp '%s' %d: %s", query_str.str, - mysql_errno(mysql), mysql_error(mysql)); - - } - else - { - sp_created= 1; - - query= (char*)"CALL mysqltest_tmp_sp()"; - query_len = strlen(query); - } - dynstr_free(&query_str); - } - - if (display_result_sorted) - { - /* - Collect the query output in a separate string - that can be sorted before it's added to the - global result string - */ - init_dynamic_string(&ds_sorted, "", 1024, 1024); - save_ds= ds; /* Remember original ds */ - ds= &ds_sorted; - } - - /* - Find out how to run this query - - Always run with normal C API if it's not a 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 && - complete_query && - match_re(&ps_re, query)) - run_query_stmt(mysql, command, query, query_len, ds, &ds_warnings); - else - run_query_normal(cn, command, flags, query, query_len, - ds, &ds_warnings); - - if (display_result_sorted) - { - /* Sort the result set and append it to result */ - dynstr_append_sorted(save_ds, &ds_sorted); - ds= save_ds; - dynstr_free(&ds_sorted); - } - - if (sp_created) - { - if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp ")) - die("Failed to drop sp: %d: %s", mysql_errno(mysql), mysql_error(mysql)); - } - - if (view_created) - { - if (util_query(mysql, "DROP VIEW mysqltest_tmp_v ")) - die("Failed to drop view: %d: %s", - mysql_errno(mysql), mysql_error(mysql)); - } - - if (command->require_file[0]) - { - /* A result file was specified for _this_ query - and the output should be checked against an already - existing file which has been specified using --require or --result - */ - check_require(ds, command->require_file); - } - - dynstr_free(&ds_warnings); - if (ds == &ds_result) - dynstr_free(&ds_result); - if (command->type == Q_EVAL) - dynstr_free(&eval_query); - DBUG_VOID_RETURN; -} - -/****************************************************************************/ -/* - Functions to detect different SQL statements -*/ - -char *re_eprint(int err) -{ - static char epbuf[100]; - size_t len= my_regerror(REG_ITOA|err, (my_regex_t *)NULL, - epbuf, sizeof(epbuf)); - assert(len <= sizeof(epbuf)); - return(epbuf); -} - -void init_re_comp(my_regex_t *re, const char* str) -{ - int err= my_regcomp(re, str, (REG_EXTENDED | REG_ICASE | REG_NOSUB), - &my_charset_latin1); - if (err) - { - char erbuf[100]; - int len= my_regerror(err, re, erbuf, sizeof(erbuf)); - die("error %s, %d/%d `%s'\n", - re_eprint(err), len, (int)sizeof(erbuf), erbuf); - } -} - -void init_re(void) -{ - /* - Filter for queries that can be run using the - MySQL Prepared Statements C API - */ - const char *ps_re_str = - "^(" - "[[:space:]]*REPLACE[[:space:]]|" - "[[: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:]])"; - - /* - Filter for queries that can be run using the - Stored procedures - */ - const char *sp_re_str =ps_re_str; - - /* - Filter for queries that can be run as views - */ - const char *view_re_str = - "^(" - "[[:space:]]*SELECT[[:space:]])"; - - init_re_comp(&ps_re, ps_re_str); - init_re_comp(&sp_re, sp_re_str); - init_re_comp(&view_re, view_re_str); -} - - -int match_re(my_regex_t *re, char *str) -{ - int err= my_regexec(re, str, (size_t)0, NULL, 0); - - if (err == 0) - return 1; - else if (err == REG_NOMATCH) - return 0; - - { - char erbuf[100]; - int len= my_regerror(err, re, erbuf, sizeof(erbuf)); - die("error %s, %d/%d `%s'\n", - re_eprint(err), len, (int)sizeof(erbuf), erbuf); - } - return 0; -} - -void free_re(void) -{ - my_regfree(&ps_re); - my_regfree(&sp_re); - my_regfree(&view_re); - my_regex_end(); -} - -/****************************************************************************/ - -void get_command_type(struct st_command* command) -{ - char save; - uint type; - DBUG_ENTER("get_command_type"); - - if (*command->query == '}') - { - command->type = Q_END_BLOCK; - DBUG_VOID_RETURN; - } - - save= command->query[command->first_word_len]; - command->query[command->first_word_len]= 0; - type= find_type(command->query, &command_typelib, 1+2); - command->query[command->first_word_len]= save; - if (type > 0) - { - command->type=(enum enum_commands) type; /* Found command */ - - /* - Look for case where "query" was explicitly specified to - force command being sent to server - */ - if (type == Q_QUERY) - { - /* Skip the "query" part */ - command->query= command->first_argument; - } - } - else - { - /* No mysqltest command matched */ - - if (command->type != Q_COMMENT_WITH_COMMAND) - { - /* A query that will sent to mysqld */ - command->type= Q_QUERY; - } - else - { - /* -- comment that didn't contain a mysqltest command */ - command->type= Q_COMMENT; - warning_msg("Suspicious command '--%s' detected, was this intentional? "\ - "Use # instead of -- to avoid this warning", - command->query); - - if (command->first_word_len && - strcmp(command->query + command->first_word_len - 1, delimiter) == 0) - { - /* - Detect comment with command using extra delimiter - Ex --disable_query_log; - ^ Extra delimiter causing the command - to be skipped - */ - save= command->query[command->first_word_len-1]; - command->query[command->first_word_len-1]= 0; - if (find_type(command->query, &command_typelib, 1+2) > 0) - die("Extra delimiter \";\" found"); - command->query[command->first_word_len-1]= save; - - } - } - } - - /* Set expected error on command */ - memcpy(&command->expected_errors, &saved_expected_errors, - sizeof(saved_expected_errors)); - DBUG_PRINT("info", ("There are %d expected errors", - command->expected_errors.count)); - command->abort_on_error= (command->expected_errors.count == 0 && - abort_on_error); - - DBUG_VOID_RETURN; -} - - - -/* - Record how many milliseconds it took to execute the test file - up until the current line and save it in the dynamic string ds_progress. - - The ds_progress will be dumped to .progress when - test run completes - -*/ - -void mark_progress(struct st_command* command __attribute__((unused)), - int line) -{ - char buf[32], *end; - ulonglong timer= timer_now(); - if (!progress_start) - progress_start= timer; - timer-= progress_start; - - /* Milliseconds since start */ - end= longlong2str(timer, buf, 10); - dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); - dynstr_append_mem(&ds_progress, "\t", 1); - - /* Parser line number */ - end= int10_to_str(line, buf, 10); - dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); - dynstr_append_mem(&ds_progress, "\t", 1); - - /* Filename */ - dynstr_append(&ds_progress, cur_file->file_name); - dynstr_append_mem(&ds_progress, ":", 1); - - /* Line in file */ - end= int10_to_str(cur_file->lineno, buf, 10); - dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); - - - dynstr_append_mem(&ds_progress, "\n", 1); - -} - -#ifdef HAVE_STACKTRACE - -static void dump_backtrace(void) -{ - struct st_connection *conn= cur_con; - - my_safe_print_str("read_command_buf", read_command_buf, - sizeof(read_command_buf)); - if (conn) - { - my_safe_print_str("conn->name", conn->name, conn->name_len); -#ifdef EMBEDDED_LIBRARY - my_safe_print_str("conn->cur_query", conn->cur_query, conn->cur_query_len); -#endif - } - fputs("Attempting backtrace...\n", stderr); - my_print_stacktrace(NULL, my_thread_stack_size); -} - -#else - -static void dump_backtrace(void) -{ - fputs("Backtrace not available.\n", stderr); -} - -#endif - -static sig_handler signal_handler(int sig) -{ - fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n", sig); - dump_backtrace(); -} - -#ifdef __WIN__ - -LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) -{ - __try - { - my_set_exception_pointers(exp); - signal_handler(exp->ExceptionRecord->ExceptionCode); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - fputs("Got exception in exception handler!\n", stderr); - } - - return EXCEPTION_CONTINUE_SEARCH; -} - - -static void init_signal_handling(void) -{ - UINT mode; - - /* Set output destination of messages to the standard error stream. */ - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - - /* Do not not display the a error message box. */ - mode= SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX; - SetErrorMode(mode); - - SetUnhandledExceptionFilter(exception_filter); -} - -#else /* __WIN__ */ - -static void init_signal_handling(void) -{ - struct sigaction sa; - DBUG_ENTER("init_signal_handling"); - -#ifdef HAVE_STACKTRACE - my_init_stacktrace(); -#endif - - sa.sa_flags = SA_RESETHAND | SA_NODEFER; - sigemptyset(&sa.sa_mask); - sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); - - sa.sa_handler= signal_handler; - - sigaction(SIGSEGV, &sa, NULL); - sigaction(SIGABRT, &sa, NULL); -#ifdef SIGBUS - sigaction(SIGBUS, &sa, NULL); -#endif - sigaction(SIGILL, &sa, NULL); - sigaction(SIGFPE, &sa, NULL); -} - -#endif /* !__WIN__ */ - -int main(int argc, char **argv) -{ - struct st_command *command; - my_bool q_send_flag= 0, abort_flag= 0; - uint command_executed= 0, last_command_executed= 0; - char save_file[FN_REFLEN]; - MY_STAT res_info; - MY_INIT(argv[0]); - - save_file[0]= 0; - TMPDIR[0]= 0; - - init_signal_handling(); - - /* Init expected errors */ - memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); - - /* Init connections */ - memset(connections, 0, sizeof(connections)); - connections_end= connections + - (sizeof(connections)/sizeof(struct st_connection)) - 1; - next_con= connections + 1; - -#ifdef EMBEDDED_LIBRARY - /* set appropriate stack for the 'query' threads */ - (void) pthread_attr_init(&cn_thd_attrib); - pthread_attr_setstacksize(&cn_thd_attrib, DEFAULT_THREAD_STACK); -#endif /*EMBEDDED_LIBRARY*/ - - /* Init file stack */ - memset(file_stack, 0, sizeof(file_stack)); - file_stack_end= - file_stack + (sizeof(file_stack)/sizeof(struct st_test_file)) - 1; - cur_file= file_stack; - - /* Init block stack */ - memset(block_stack, 0, sizeof(block_stack)); - block_stack_end= - block_stack + (sizeof(block_stack)/sizeof(struct st_block)) - 1; - cur_block= block_stack; - cur_block->ok= TRUE; /* Outer block should always be executed */ - cur_block->cmd= cmd_none; - - my_init_dynamic_array(&q_lines, sizeof(struct st_command*), 1024, 1024); - - if (hash_init(&var_hash, charset_info, - 1024, 0, 0, get_var_key, var_free, MYF(0))) - die("Variable hash initialization failed"); - - var_set_string("$MYSQL_SERVER_VERSION", MYSQL_SERVER_VERSION); - - memset(&master_pos, 0, sizeof(master_pos)); - - parser.current_line= parser.read_lines= 0; - memset(&var_reg, 0, sizeof(var_reg)); - - init_builtin_echo(); -#ifdef __WIN__ -#ifndef USE_CYGWIN - is_windows= 1; -#endif - init_tmp_sh_file(); - init_win_path_patterns(); -#endif - - init_dynamic_string(&ds_res, "", 65536, 65536); - init_dynamic_string(&ds_progress, "", 0, 2048); - init_dynamic_string(&ds_warning_messages, "", 0, 2048); - parse_args(argc, argv); - - var_set_int("$PS_PROTOCOL", ps_protocol); - var_set_int("$SP_PROTOCOL", sp_protocol); - var_set_int("$VIEW_PROTOCOL", view_protocol); - var_set_int("$CURSOR_PROTOCOL", cursor_protocol); - - DBUG_PRINT("info",("result_file: '%s'", - result_file_name ? result_file_name : "")); - if (mysql_server_init(embedded_server_arg_count, - embedded_server_args, - (char**) embedded_server_groups)) - die("Can't initialize MySQL server"); - server_initialized= 1; - if (cur_file == file_stack && cur_file->file == 0) - { - cur_file->file= stdin; - cur_file->file_name= my_strdup("", MYF(MY_WME)); - cur_file->lineno= 1; - } - init_re(); - ps_protocol_enabled= ps_protocol; - sp_protocol_enabled= sp_protocol; - view_protocol_enabled= view_protocol; - cursor_protocol_enabled= cursor_protocol; - /* Cursor protcol implies ps protocol */ - if (cursor_protocol_enabled) - ps_protocol_enabled= 1; - - cur_con= connections; - 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, - charset_info->csname); - if (opt_charsets_dir) - mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_DIR, - opt_charsets_dir); - -#ifdef HAVE_OPENSSL - - if (opt_use_ssl) - { - mysql_ssl_set(&cur_con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, - opt_ssl_capath, opt_ssl_cipher); -#if MYSQL_VERSION_ID >= 50000 - /* Turn on ssl_verify_server_cert only if host is "localhost" */ - opt_ssl_verify_server_cert= opt_host && !strcmp(opt_host, "localhost"); - mysql_options(&cur_con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, - &opt_ssl_verify_server_cert); -#endif - } -#endif - - if (!(cur_con->name = my_strdup("default", MYF(MY_WME)))) - die("Out of memory"); - - safe_connect(&cur_con->mysql, cur_con->name, opt_host, opt_user, opt_pass, - opt_db, opt_port, unix_sock); - - /* Use all time until exit if no explicit 'start_timer' */ - timer_start= timer_now(); - - /* - Initialize $mysql_errno with -1, so we can - - distinguish it from valid values ( >= 0 ) and - - detect if there was never a command sent to the server - */ - var_set_errno(-1); - - /* Update $mysql_get_server_version to that of current connection */ - var_set_mysql_get_server_version(&cur_con->mysql); - - if (opt_include) - { - open_file(opt_include); - } - - while (!read_command(&command) && !abort_flag) - { - int current_line_inc = 1, processed = 0; - if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND) - get_command_type(command); - - if (parsing_disabled && - command->type != Q_ENABLE_PARSING && - command->type != Q_DISABLE_PARSING) - { - command->type= Q_COMMENT; - scan_command_for_warnings(command); - } - - if (cur_block->ok) - { - command->last_argument= command->first_argument; - processed = 1; - switch (command->type) { - case Q_CONNECT: - do_connect(command); - break; - case Q_CONNECTION: select_connection(command); break; - case Q_DISCONNECT: - case Q_DIRTY_CLOSE: - do_close_connection(command); break; - case Q_RPL_PROBE: do_rpl_probe(command); break; - case Q_ENABLE_RPL_PARSE: do_enable_rpl_parse(command); break; - case Q_DISABLE_RPL_PARSE: do_disable_rpl_parse(command); break; - case Q_ENABLE_QUERY_LOG: disable_query_log=0; break; - case Q_DISABLE_QUERY_LOG: disable_query_log=1; break; - case Q_ENABLE_ABORT_ON_ERROR: abort_on_error=1; break; - case Q_DISABLE_ABORT_ON_ERROR: abort_on_error=0; break; - case Q_ENABLE_RESULT_LOG: disable_result_log=0; break; - 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_INFO: disable_info=0; break; - case Q_DISABLE_INFO: disable_info=1; break; - case Q_ENABLE_METADATA: display_metadata=1; break; - case Q_DISABLE_METADATA: display_metadata=0; break; - case Q_SOURCE: do_source(command); break; - case Q_SLEEP: do_sleep(command, 0); break; - case Q_REAL_SLEEP: do_sleep(command, 1); break; - case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(command); break; - case Q_INC: do_modify_var(command, DO_INC); break; - case Q_DEC: do_modify_var(command, DO_DEC); break; - case Q_ECHO: do_echo(command); command_executed++; break; - case Q_SYSTEM: do_system(command); break; - case Q_REMOVE_FILE: do_remove_file(command); break; - case Q_MKDIR: do_mkdir(command); break; - case Q_RMDIR: do_rmdir(command); break; - case Q_FILE_EXIST: do_file_exist(command); break; - case Q_WRITE_FILE: do_write_file(command); break; - case Q_APPEND_FILE: do_append_file(command); break; - case Q_DIFF_FILES: do_diff_files(command); break; - case Q_SEND_QUIT: do_send_quit(command); break; - case Q_CHANGE_USER: do_change_user(command); break; - case Q_CAT_FILE: do_cat_file(command); break; - case Q_COPY_FILE: do_copy_file(command); break; - case Q_CHMOD_FILE: do_chmod_file(command); break; - case Q_PERL: do_perl(command); break; - case Q_DELIMITER: - do_delimiter(command); - break; - case Q_DISPLAY_VERTICAL_RESULTS: - display_result_vertically= TRUE; - break; - case Q_DISPLAY_HORIZONTAL_RESULTS: - display_result_vertically= FALSE; - break; - case Q_SORTED_RESULT: - /* - Turn on sorting of result set, will be reset after next - command - */ - display_result_sorted= TRUE; - break; - case Q_LET: do_let(command); break; - case Q_EVAL_RESULT: - die("'eval_result' command is deprecated"); - case Q_EVAL: - case Q_QUERY_VERTICAL: - case Q_QUERY_HORIZONTAL: - if (command->query == command->query_buf) - { - /* Skip the first part of command, i.e query_xxx */ - command->query= command->first_argument; - command->first_word_len= 0; - } - /* fall through */ - case Q_QUERY: - case Q_REAP: - { - my_bool old_display_result_vertically= display_result_vertically; - /* Default is full query, both reap and send */ - int flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG; - - if (q_send_flag) - { - /* Last command was an empty 'send' */ - flags= QUERY_SEND_FLAG; - q_send_flag= 0; - } - else if (command->type == Q_REAP) - { - flags= QUERY_REAP_FLAG; - } - - /* Check for special property for this query */ - display_result_vertically|= (command->type == Q_QUERY_VERTICAL); - - if (save_file[0]) - { - strmake(command->require_file, save_file, sizeof(save_file) - 1); - save_file[0]= 0; - } - run_query(cur_con, command, flags); - command_executed++; - command->last_argument= command->end; - - /* Restore settings */ - display_result_vertically= old_display_result_vertically; - - break; - } - case Q_SEND: - if (!*command->first_argument) - { - /* - This is a send without arguments, it indicates that _next_ query - should be send only - */ - q_send_flag= 1; - break; - } - - /* Remove "send" if this is first iteration */ - if (command->query == command->query_buf) - command->query= command->first_argument; - - /* - run_query() can execute a query partially, depending on the flags. - QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send - the query and read the result some time later when reap instruction - is given on this connection. - */ - run_query(cur_con, command, QUERY_SEND_FLAG); - command_executed++; - command->last_argument= command->end; - break; - case Q_REQUIRE: - do_get_file_name(command, save_file, sizeof(save_file)); - break; - case Q_ERROR: - do_get_errcodes(command); - break; - case Q_REPLACE: - do_get_replace(command); - break; - case Q_REPLACE_REGEX: - do_get_replace_regex(command); - break; - case Q_REPLACE_COLUMN: - do_get_replace_column(command); - break; - case Q_SAVE_MASTER_POS: do_save_master_pos(); break; - case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break; - case Q_SYNC_SLAVE_WITH_MASTER: - { - do_save_master_pos(); - if (*command->first_argument) - select_connection(command); - else - select_connection_name("slave"); - do_sync_with_master2(0); - break; - } - case Q_COMMENT: /* Ignore row */ - command->last_argument= command->end; - break; - case Q_PING: - handle_command_error(command, mysql_ping(&cur_con->mysql)); - break; - case Q_SEND_SHUTDOWN: - handle_command_error(command, - mysql_shutdown(&cur_con->mysql, - SHUTDOWN_DEFAULT)); - break; - case Q_SHUTDOWN_SERVER: - do_shutdown_server(command); - break; - case Q_EXEC: - do_exec(command); - command_executed++; - 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(); - break; - case Q_CHARACTER_SET: - do_set_charset(command); - break; - case Q_DISABLE_PS_PROTOCOL: - ps_protocol_enabled= 0; - /* Close any open statements */ - close_statements(); - break; - case Q_ENABLE_PS_PROTOCOL: - ps_protocol_enabled= ps_protocol; - break; - case Q_DISABLE_RECONNECT: - set_reconnect(&cur_con->mysql, 0); - break; - case Q_ENABLE_RECONNECT: - set_reconnect(&cur_con->mysql, 1); - /* Close any open statements - no reconnect, need new prepare */ - close_statements(); - break; - case Q_DISABLE_PARSING: - if (parsing_disabled == 0) - parsing_disabled= 1; - else - die("Parsing is already disabled"); - break; - case Q_ENABLE_PARSING: - /* - Ensure we don't get parsing_disabled < 0 as this would accidentally - disable code we don't want to have disabled - */ - if (parsing_disabled == 1) - parsing_disabled= 0; - else - die("Parsing is already enabled"); - break; - case Q_DIE: - /* Abort test with error code and error message */ - die("%s", command->first_argument); - break; - case Q_EXIT: - /* Stop processing any more commands */ - abort_flag= 1; - break; - case Q_SKIP: - abort_not_supported_test("%s", command->first_argument); - break; - - case Q_RESULT: - die("result, deprecated command"); - break; - - default: - processed= 0; - break; - } - } - - if (!processed) - { - current_line_inc= 0; - switch (command->type) { - case Q_WHILE: do_block(cmd_while, command); break; - case Q_IF: do_block(cmd_if, command); break; - case Q_END_BLOCK: do_done(command); break; - default: current_line_inc = 1; break; - } - } - else - check_eol_junk(command->last_argument); - - if (command->type != Q_ERROR && - command->type != Q_COMMENT) - { - /* - As soon as any non "error" command or comment has been executed, - the array with expected errors should be cleared - */ - memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); - } - - if (command_executed != last_command_executed) - { - /* - As soon as any command has been executed, - the replace structures should be cleared - */ - free_all_replace(); - - /* Also reset "sorted_result" */ - display_result_sorted= FALSE; - } - last_command_executed= command_executed; - - parser.current_line += current_line_inc; - if ( opt_mark_progress ) - mark_progress(command, parser.current_line); - } - - start_lineno= 0; - - if (parsing_disabled) - die("Test ended with parsing disabled"); - - /* - The whole test has been executed _sucessfully_. - Time to compare result or save it to record file. - The entire output from test is now kept in ds_res. - */ - if (ds_res.length) - { - if (result_file_name) - { - /* A result file has been specified */ - - if (record) - { - /* Recording - dump the output from test to result file */ - str_to_file(result_file_name, ds_res.str, ds_res.length); - } - else - { - /* Check that the output from test is equal to result file - - detect missing result file - - detect zero size result file - */ - check_result(&ds_res); - } - } - else - { - /* No result_file_name specified to compare with, print to stdout */ - printf("%s", ds_res.str); - } - } - else - { - die("The test didn't produce any output"); - } - - if (!command_executed && - result_file_name && my_stat(result_file_name, &res_info, 0)) - { - /* - my_stat() successful on result file. Check if we have not run a - single query, but we do have a result file that contains data. - Note that we don't care, if my_stat() fails. For example, for a - non-existing or non-readable file, we assume it's fine to have - no query output from the test file, e.g. regarded as no error. - */ - die("No queries executed but result file found!"); - } - - if ( opt_mark_progress && result_file_name ) - dump_progress(); - - /* Dump warning messages */ - if (result_file_name && ds_warning_messages.length) - dump_warning_messages(); - - timer_output(); - /* Yes, if we got this far the test has suceeded! Sakila smiles */ - cleanup_and_exit(0); - return 0; /* Keep compiler happy too */ -} - - -/* - A primitive timer that give results in milliseconds if the - --timer-file= 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. -*/ - -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)); - /* Timer has been written to the file, don't use it anymore */ - timer_file= 0; - } -} - - -ulonglong timer_now(void) -{ - return my_micro_time() / 1000; -} - - -/* - Get arguments for replace_columns. The syntax is: - replace-column column_number to_string [column_number to_string ...] - Where each argument may be quoted with ' or " - A argument may also be a variable, in which case the value of the - variable is replaced. -*/ - -void do_get_replace_column(struct st_command *command) -{ - char *from= command->first_argument; - char *buff, *start; - DBUG_ENTER("get_replace_columns"); - - free_replace_column(); - if (!*from) - die("Missing argument in %s", command->query); - - /* Allocate a buffer for results */ - start= buff= my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); - while (*from) - { - char *to; - uint column_number; - - to= get_string(&buff, &from, command); - if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS) - die("Wrong column number to replace_column in '%s'", command->query); - if (!*from) - die("Wrong number of arguments to replace_column in '%s'", command->query); - to= get_string(&buff, &from, command); - my_free(replace_column[column_number-1], MY_ALLOW_ZERO_PTR); - replace_column[column_number-1]= my_strdup(to, MYF(MY_WME | MY_FAE)); - set_if_bigger(max_replace_column, column_number); - } - my_free(start, MYF(0)); - command->last_argument= command->end; -} - - -void free_replace_column() -{ - uint i; - for (i=0 ; i < max_replace_column ; i++) - { - if (replace_column[i]) - { - my_free(replace_column[i], 0); - replace_column[i]= 0; - } - } - max_replace_column= 0; -} - - -/****************************************************************************/ -/* - Replace functions -*/ - -/* Definitions for replace result */ - -typedef struct st_pointer_array { /* when using array-strings */ - TYPELIB typelib; /* Pointer to strings */ - uchar *str; /* Strings is here */ - int7 *flag; /* Flag about each var. */ - uint array_allocs,max_count,length,max_length; -} POINTER_ARRAY; - -struct st_replace; -struct st_replace *init_replace(char * *from, char * *to, uint count, - char * word_end_chars); -int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name); -void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, - const char *from, int len); -void free_pointer_array(POINTER_ARRAY *pa); - -struct st_replace *glob_replace; - -/* - Get arguments for replace. The syntax is: - replace from to [from to ...] - Where each argument may be quoted with ' or " - A argument may also be a variable, in which case the value of the - variable is replaced. -*/ - -void do_get_replace(struct st_command *command) -{ - uint i; - char *from= command->first_argument; - char *buff, *start; - char word_end_chars[256], *pos; - POINTER_ARRAY to_array, from_array; - DBUG_ENTER("get_replace"); - - free_replace(); - - bzero((char*) &to_array,sizeof(to_array)); - bzero((char*) &from_array,sizeof(from_array)); - if (!*from) - die("Missing argument in %s", command->query); - start= buff= my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); - while (*from) - { - char *to= buff; - to= get_string(&buff, &from, command); - if (!*from) - die("Wrong number of arguments to replace_result in '%s'", - command->query); -#ifdef __WIN__ - fix_win_paths(to, from - to); -#endif - insert_pointer_name(&from_array,to); - to= get_string(&buff, &from, command); - insert_pointer_name(&to_array,to); - } - for (i= 1,pos= word_end_chars ; i < 256 ; i++) - if (my_isspace(charset_info,i)) - *pos++= i; - *pos=0; /* End pointer */ - if (!(glob_replace= init_replace((char**) from_array.typelib.type_names, - (char**) to_array.typelib.type_names, - (uint) from_array.typelib.count, - word_end_chars))) - die("Can't initialize replace from '%s'", command->query); - free_pointer_array(&from_array); - free_pointer_array(&to_array); - my_free(start, MYF(0)); - command->last_argument= command->end; - DBUG_VOID_RETURN; -} - - -void free_replace() -{ - DBUG_ENTER("free_replace"); - if (glob_replace) - { - my_free(glob_replace,MYF(0)); - glob_replace=0; - } - DBUG_VOID_RETURN; -} - - -typedef struct st_replace { - my_bool found; - struct st_replace *next[256]; -} REPLACE; - -typedef struct st_replace_found { - my_bool found; - char *replace_string; - uint to_offset; - int from_offset; -} REPLACE_STRING; - - -void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, - const char *str, - int len __attribute__((unused))) -{ - reg1 REPLACE *rep_pos; - reg2 REPLACE_STRING *rep_str; - const char *start, *from; - DBUG_ENTER("replace_strings_append"); - - start= from= str; - rep_pos=rep+1; - for (;;) - { - /* Loop through states */ - DBUG_PRINT("info", ("Looping through states")); - while (!rep_pos->found) - rep_pos= rep_pos->next[(uchar) *from++]; - - /* Does this state contain a string to be replaced */ - if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string) - { - /* No match found */ - dynstr_append_mem(ds, start, from - start - 1); - DBUG_PRINT("exit", ("Found no more string to replace, appended: %s", start)); - DBUG_VOID_RETURN; - } - - /* Found a string that needs to be replaced */ - DBUG_PRINT("info", ("found: %d, to_offset: %d, from_offset: %d, string: %s", - rep_str->found, rep_str->to_offset, - rep_str->from_offset, rep_str->replace_string)); - - /* Append part of original string before replace string */ - dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start); - - /* Append replace string */ - dynstr_append_mem(ds, rep_str->replace_string, - strlen(rep_str->replace_string)); - - if (!*(from-=rep_str->from_offset) && rep_pos->found != 2) - { - /* End of from string */ - DBUG_PRINT("exit", ("Found end of from string")); - DBUG_VOID_RETURN; - } - DBUG_ASSERT(from <= str+len); - start= from; - rep_pos=rep; - } -} - - -/* - Regex replace functions -*/ - - -/* Stores regex substitutions */ - -struct st_regex -{ - char* pattern; /* Pattern to be replaced */ - char* replace; /* String or expression to replace the pattern with */ - int icase; /* true if the match is case insensitive */ -}; - -struct st_replace_regex -{ - DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */ - - /* - Temporary storage areas for substitutions. To reduce unnessary copying - and memory freeing/allocation, we pre-allocate two buffers, and alternate - their use, one for input/one for output, the roles changing on the next - st_regex substition. At the end of substitutions buf points to the - one containing the final result. - */ - char* buf; - char* even_buf; - char* odd_buf; - int even_buf_len; - int odd_buf_len; -}; - -struct st_replace_regex *glob_replace_regex= 0; - -int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, - char *string, int icase); - - - -/* - Finds the next (non-escaped) '/' in the expression. - (If the character '/' is needed, it can be escaped using '\'.) -*/ - -#define PARSE_REGEX_ARG \ - while (p < expr_end) \ - { \ - char c= *p; \ - if (c == '/') \ - { \ - if (last_c == '\\') \ - { \ - buf_p[-1]= '/'; \ - } \ - else \ - { \ - *buf_p++ = 0; \ - break; \ - } \ - } \ - else \ - *buf_p++ = c; \ - \ - last_c= c; \ - p++; \ - } \ - \ -/* - Initializes the regular substitution expression to be used in the - result output of test. - - Returns: st_replace_regex struct with pairs of substitutions -*/ - -struct st_replace_regex* init_replace_regex(char* expr) -{ - struct st_replace_regex* res; - char* buf,*expr_end; - char* p; - char* buf_p; - uint expr_len= strlen(expr); - char last_c = 0; - struct st_regex reg; - - /* my_malloc() will die on fail with MY_FAE */ - res=(struct st_replace_regex*)my_malloc( - sizeof(*res)+expr_len ,MYF(MY_FAE+MY_WME)); - my_init_dynamic_array(&res->regex_arr,sizeof(struct st_regex),128,128); - - buf= (char*)res + sizeof(*res); - expr_end= expr + expr_len; - p= expr; - buf_p= buf; - - /* for each regexp substitution statement */ - while (p < expr_end) - { - bzero(®,sizeof(reg)); - /* find the start of the statement */ - while (p < expr_end) - { - if (*p == '/') - break; - p++; - } - - if (p == expr_end || ++p == expr_end) - { - if (res->regex_arr.elements) - break; - else - goto err; - } - /* we found the start */ - reg.pattern= buf_p; - - /* Find first argument -- pattern string to be removed */ - PARSE_REGEX_ARG - - if (p == expr_end || ++p == expr_end) - goto err; - - /* buf_p now points to the replacement pattern terminated with \0 */ - reg.replace= buf_p; - - /* Find second argument -- replace string to replace pattern */ - PARSE_REGEX_ARG - - if (p == expr_end) - goto err; - - /* skip the ending '/' in the statement */ - p++; - - /* Check if we should do matching case insensitive */ - if (p < expr_end && *p == 'i') - reg.icase= 1; - - /* done parsing the statement, now place it in regex_arr */ - if (insert_dynamic(&res->regex_arr,(uchar*) ®)) - die("Out of memory"); - } - res->odd_buf_len= res->even_buf_len= 8192; - res->even_buf= (char*)my_malloc(res->even_buf_len,MYF(MY_WME+MY_FAE)); - res->odd_buf= (char*)my_malloc(res->odd_buf_len,MYF(MY_WME+MY_FAE)); - res->buf= res->even_buf; - - return res; - -err: - my_free(res,0); - die("Error parsing replace_regex \"%s\"", expr); - return 0; -} - -/* - Execute all substitutions on val. - - Returns: true if substituition was made, false otherwise - Side-effect: Sets r->buf to be the buffer with all substitutions done. - - IN: - struct st_replace_regex* r - char* val - Out: - struct st_replace_regex* r - r->buf points at the resulting buffer - r->even_buf and r->odd_buf might have been reallocated - r->even_buf_len and r->odd_buf_len might have been changed - - TODO: at some point figure out if there is a way to do everything - in one pass -*/ - -int multi_reg_replace(struct st_replace_regex* r,char* val) -{ - uint i; - char* in_buf, *out_buf; - int* buf_len_p; - - in_buf= val; - out_buf= r->even_buf; - buf_len_p= &r->even_buf_len; - r->buf= 0; - - /* For each substitution, do the replace */ - for (i= 0; i < r->regex_arr.elements; i++) - { - struct st_regex re; - char* save_out_buf= out_buf; - - get_dynamic(&r->regex_arr,(uchar*)&re,i); - - if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace, - in_buf, re.icase)) - { - /* if the buffer has been reallocated, make adjustements */ - if (save_out_buf != out_buf) - { - if (save_out_buf == r->even_buf) - r->even_buf= out_buf; - else - r->odd_buf= out_buf; - } - - r->buf= out_buf; - if (in_buf == val) - in_buf= r->odd_buf; - - swap_variables(char*,in_buf,out_buf); - - buf_len_p= (out_buf == r->even_buf) ? &r->even_buf_len : - &r->odd_buf_len; - } - } - - return (r->buf == 0); -} - -/* - Parse the regular expression to be used in all result files - from now on. - - The syntax is --replace_regex /from/to/i /from/to/i ... - i means case-insensitive match. If omitted, the match is - case-sensitive - -*/ -void do_get_replace_regex(struct st_command *command) -{ - char *expr= command->first_argument; - free_replace_regex(); - if (!(glob_replace_regex=init_replace_regex(expr))) - die("Could not init replace_regex"); - command->last_argument= command->end; -} - -void free_replace_regex() -{ - if (glob_replace_regex) - { - delete_dynamic(&glob_replace_regex->regex_arr); - my_free(glob_replace_regex->even_buf,MYF(MY_ALLOW_ZERO_PTR)); - my_free(glob_replace_regex->odd_buf,MYF(MY_ALLOW_ZERO_PTR)); - my_free(glob_replace_regex,MYF(0)); - glob_replace_regex=0; - } -} - - - -/* - auxiluary macro used by reg_replace - makes sure the result buffer has sufficient length -*/ -#define SECURE_REG_BUF if (buf_len < need_buf_len) \ - { \ - int off= res_p - buf; \ - buf= (char*)my_realloc(buf,need_buf_len,MYF(MY_WME+MY_FAE)); \ - res_p= buf + off; \ - buf_len= need_buf_len; \ - } \ - \ -/* - Performs a regex substitution - - IN: - - buf_p - result buffer pointer. Will change if reallocated - buf_len_p - result buffer length. Will change if the buffer is reallocated - pattern - regexp pattern to match - replace - replacement expression - string - the string to perform substituions in - icase - flag, if set to 1 the match is case insensitive -*/ -int reg_replace(char** buf_p, int* buf_len_p, char *pattern, - char *replace, char *string, int icase) -{ - my_regex_t r; - my_regmatch_t *subs; - char *replace_end; - char *buf= *buf_p; - int len; - int buf_len, need_buf_len; - int cflags= REG_EXTENDED; - int err_code; - char *res_p,*str_p,*str_end; - - buf_len= *buf_len_p; - len= strlen(string); - str_end= string + len; - - /* start with a buffer of a reasonable size that hopefully will not - need to be reallocated - */ - need_buf_len= len * 2 + 1; - res_p= buf; - - SECURE_REG_BUF - - if (icase) - cflags|= REG_ICASE; - - if ((err_code= my_regcomp(&r,pattern,cflags,&my_charset_latin1))) - { - check_regerr(&r,err_code); - return 1; - } - - subs= (my_regmatch_t*)my_malloc(sizeof(my_regmatch_t) * (r.re_nsub+1), - MYF(MY_WME+MY_FAE)); - - *res_p= 0; - str_p= string; - replace_end= replace + strlen(replace); - - /* for each pattern match instance perform a replacement */ - while (!err_code) - { - /* find the match */ - err_code= my_regexec(&r,str_p, r.re_nsub+1, subs, - (str_p == string) ? REG_NOTBOL : 0); - - /* if regular expression error (eg. bad syntax, or out of memory) */ - if (err_code && err_code != REG_NOMATCH) - { - check_regerr(&r,err_code); - my_regfree(&r); - return 1; - } - - /* if match found */ - if (!err_code) - { - char* expr_p= replace; - int c; - - /* - we need at least what we have so far in the buffer + the part - before this match - */ - need_buf_len= (res_p - buf) + (int) subs[0].rm_so; - - /* on this pass, calculate the memory for the result buffer */ - while (expr_p < replace_end) - { - int back_ref_num= -1; - c= *expr_p; - - if (c == '\\' && expr_p + 1 < replace_end) - { - back_ref_num= (int) (expr_p[1] - '0'); - } - - /* found a valid back_ref (eg. \1)*/ - if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) - { - regoff_t start_off, end_off; - if ((start_off=subs[back_ref_num].rm_so) > -1 && - (end_off=subs[back_ref_num].rm_eo) > -1) - { - need_buf_len += (int) (end_off - start_off); - } - expr_p += 2; - } - else - { - expr_p++; - need_buf_len++; - } - } - need_buf_len++; - /* - now that we know the size of the buffer, - make sure it is big enough - */ - SECURE_REG_BUF - - /* copy the pre-match part */ - if (subs[0].rm_so) - { - memcpy(res_p, str_p, (size_t) subs[0].rm_so); - res_p+= subs[0].rm_so; - } - - expr_p= replace; - - /* copy the match and expand back_refs */ - while (expr_p < replace_end) - { - int back_ref_num= -1; - c= *expr_p; - - if (c == '\\' && expr_p + 1 < replace_end) - { - back_ref_num= expr_p[1] - '0'; - } - - if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) - { - regoff_t start_off, end_off; - if ((start_off=subs[back_ref_num].rm_so) > -1 && - (end_off=subs[back_ref_num].rm_eo) > -1) - { - int block_len= (int) (end_off - start_off); - memcpy(res_p,str_p + start_off, block_len); - res_p += block_len; - } - expr_p += 2; - } - else - { - *res_p++ = *expr_p++; - } - } - - /* handle the post-match part */ - if (subs[0].rm_so == subs[0].rm_eo) - { - if (str_p + subs[0].rm_so >= str_end) - break; - str_p += subs[0].rm_eo ; - *res_p++ = *str_p++; - } - else - { - str_p += subs[0].rm_eo; - } - } - else /* no match this time, just copy the string as is */ - { - int left_in_str= str_end-str_p; - need_buf_len= (res_p-buf) + left_in_str; - SECURE_REG_BUF - memcpy(res_p,str_p,left_in_str); - res_p += left_in_str; - str_p= str_end; - } - } - my_free(subs, MYF(0)); - my_regfree(&r); - *res_p= 0; - *buf_p= buf; - *buf_len_p= buf_len; - return 0; -} - - -#ifndef WORD_BIT -#define WORD_BIT (8*sizeof(uint)) -#endif - -#define SET_MALLOC_HUNC 64 -#define LAST_CHAR_CODE 259 - -typedef struct st_rep_set { - uint *bits; /* Pointer to used sets */ - short next[LAST_CHAR_CODE]; /* Pointer to next sets */ - uint found_len; /* Best match to date */ - int found_offset; - uint table_offset; - uint size_of_bits; /* For convinience */ -} REP_SET; - -typedef struct st_rep_sets { - uint count; /* Number of sets */ - uint extra; /* Extra sets in buffer */ - uint invisible; /* Sets not chown */ - uint size_of_bits; - REP_SET *set,*set_buffer; - uint *bit_buffer; -} REP_SETS; - -typedef struct st_found_set { - uint table_offset; - int found_offset; -} FOUND_SET; - -typedef struct st_follow { - int chr; - uint table_offset; - uint len; -} FOLLOWS; - - -int init_sets(REP_SETS *sets,uint states); -REP_SET *make_new_set(REP_SETS *sets); -void make_sets_invisible(REP_SETS *sets); -void free_last_set(REP_SETS *sets); -void free_sets(REP_SETS *sets); -void internal_set_bit(REP_SET *set, uint bit); -void internal_clear_bit(REP_SET *set, uint bit); -void or_bits(REP_SET *to,REP_SET *from); -void copy_bits(REP_SET *to,REP_SET *from); -int cmp_bits(REP_SET *set1,REP_SET *set2); -int get_next_bit(REP_SET *set,uint lastpos); -int find_set(REP_SETS *sets,REP_SET *find); -int find_found(FOUND_SET *found_set,uint table_offset, - int found_offset); -uint start_at_word(char * pos); -uint end_of_word(char * pos); - -static uint found_sets=0; - - -uint replace_len(char * str) -{ - uint len=0; - while (*str) - { - str++; - len++; - } - return len; -} - -/* Init a replace structure for further calls */ - -REPLACE *init_replace(char * *from, char * *to,uint count, - char * word_end_chars) -{ - static const int SPACE_CHAR= 256; - static const int END_OF_LINE= 258; - - uint i,j,states,set_nr,len,result_len,max_length,found_end,bits_set,bit_nr; - int used_sets,chr,default_state; - char used_chars[LAST_CHAR_CODE],is_word_end[256]; - char * pos, *to_pos, **to_array; - REP_SETS sets; - REP_SET *set,*start_states,*word_states,*new_set; - FOLLOWS *follow,*follow_ptr; - REPLACE *replace; - FOUND_SET *found_set; - REPLACE_STRING *rep_str; - DBUG_ENTER("init_replace"); - - /* Count number of states */ - for (i=result_len=max_length=0 , states=2 ; i < count ; i++) - { - len=replace_len(from[i]); - if (!len) - { - errno=EINVAL; - DBUG_RETURN(0); - } - states+=len+1; - result_len+=(uint) strlen(to[i])+1; - if (len > max_length) - max_length=len; - } - bzero((char*) is_word_end,sizeof(is_word_end)); - for (i=0 ; word_end_chars[i] ; i++) - is_word_end[(uchar) word_end_chars[i]]=1; - - if (init_sets(&sets,states)) - DBUG_RETURN(0); - found_sets=0; - if (!(found_set= (FOUND_SET*) my_malloc(sizeof(FOUND_SET)*max_length*count, - MYF(MY_WME)))) - { - free_sets(&sets); - DBUG_RETURN(0); - } - VOID(make_new_set(&sets)); /* Set starting set */ - make_sets_invisible(&sets); /* Hide previus sets */ - used_sets=-1; - word_states=make_new_set(&sets); /* Start of new word */ - start_states=make_new_set(&sets); /* This is first state */ - if (!(follow=(FOLLOWS*) my_malloc((states+2)*sizeof(FOLLOWS),MYF(MY_WME)))) - { - free_sets(&sets); - my_free(found_set,MYF(0)); - DBUG_RETURN(0); - } - - /* Init follow_ptr[] */ - for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++) - { - if (from[i][0] == '\\' && from[i][1] == '^') - { - internal_set_bit(start_states,states+1); - if (!from[i][2]) - { - start_states->table_offset=i; - start_states->found_offset=1; - } - } - else if (from[i][0] == '\\' && from[i][1] == '$') - { - internal_set_bit(start_states,states); - internal_set_bit(word_states,states); - if (!from[i][2] && start_states->table_offset == (uint) ~0) - { - start_states->table_offset=i; - start_states->found_offset=0; - } - } - else - { - internal_set_bit(word_states,states); - if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2])) - internal_set_bit(start_states,states+1); - else - internal_set_bit(start_states,states); - } - for (pos=from[i], len=0; *pos ; pos++) - { - follow_ptr->chr= (uchar) *pos; - follow_ptr->table_offset=i; - follow_ptr->len= ++len; - follow_ptr++; - } - follow_ptr->chr=0; - follow_ptr->table_offset=i; - follow_ptr->len=len; - follow_ptr++; - states+=(uint) len+1; - } - - - for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++) - { - set=sets.set+set_nr; - default_state= 0; /* Start from beginning */ - - /* If end of found-string not found or start-set with current set */ - - for (i= (uint) ~0; (i=get_next_bit(set,i)) ;) - { - if (!follow[i].chr) - { - if (! default_state) - default_state= find_found(found_set,set->table_offset, - set->found_offset+1); - } - } - copy_bits(sets.set+used_sets,set); /* Save set for changes */ - if (!default_state) - or_bits(sets.set+used_sets,sets.set); /* Can restart from start */ - - /* Find all chars that follows current sets */ - bzero((char*) used_chars,sizeof(used_chars)); - for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;) - { - used_chars[follow[i].chr]=1; - if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr && - follow[i].len > 1) || follow[i].chr == END_OF_LINE) - used_chars[0]=1; - } - - /* Mark word_chars used if \b is in state */ - if (used_chars[SPACE_CHAR]) - for (pos= word_end_chars ; *pos ; pos++) - used_chars[(int) (uchar) *pos] = 1; - - /* Handle other used characters */ - for (chr= 0 ; chr < 256 ; chr++) - { - if (! used_chars[chr]) - set->next[chr]= chr ? default_state : -1; - else - { - new_set=make_new_set(&sets); - set=sets.set+set_nr; /* if realloc */ - new_set->table_offset=set->table_offset; - new_set->found_len=set->found_len; - new_set->found_offset=set->found_offset+1; - found_end=0; - - for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; ) - { - if (!follow[i].chr || follow[i].chr == chr || - (follow[i].chr == SPACE_CHAR && - (is_word_end[chr] || - (!chr && follow[i].len > 1 && ! follow[i+1].chr))) || - (follow[i].chr == END_OF_LINE && ! chr)) - { - if ((! chr || (follow[i].chr && !follow[i+1].chr)) && - follow[i].len > found_end) - found_end=follow[i].len; - if (chr && follow[i].chr) - internal_set_bit(new_set,i+1); /* To next set */ - else - internal_set_bit(new_set,i); - } - } - if (found_end) - { - new_set->found_len=0; /* Set for testing if first */ - bits_set=0; - for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;) - { - if ((follow[i].chr == SPACE_CHAR || - follow[i].chr == END_OF_LINE) && ! chr) - bit_nr=i+1; - else - bit_nr=i; - if (follow[bit_nr-1].len < found_end || - (new_set->found_len && - (chr == 0 || !follow[bit_nr].chr))) - internal_clear_bit(new_set,i); - else - { - if (chr == 0 || !follow[bit_nr].chr) - { /* best match */ - new_set->table_offset=follow[bit_nr].table_offset; - if (chr || (follow[i].chr == SPACE_CHAR || - follow[i].chr == END_OF_LINE)) - new_set->found_offset=found_end; /* New match */ - new_set->found_len=found_end; - } - bits_set++; - } - } - if (bits_set == 1) - { - set->next[chr] = find_found(found_set, - new_set->table_offset, - new_set->found_offset); - free_last_set(&sets); - } - else - set->next[chr] = find_set(&sets,new_set); - } - else - set->next[chr] = find_set(&sets,new_set); - } - } - } - - /* Alloc replace structure for the replace-state-machine */ - - if ((replace=(REPLACE*) my_malloc(sizeof(REPLACE)*(sets.count)+ - sizeof(REPLACE_STRING)*(found_sets+1)+ - sizeof(char *)*count+result_len, - MYF(MY_WME | MY_ZEROFILL)))) - { - rep_str=(REPLACE_STRING*) (replace+sets.count); - to_array= (char **) (rep_str+found_sets+1); - to_pos=(char *) (to_array+count); - for (i=0 ; i < count ; i++) - { - to_array[i]=to_pos; - to_pos=strmov(to_pos,to[i])+1; - } - rep_str[0].found=1; - rep_str[0].replace_string=0; - for (i=1 ; i <= found_sets ; i++) - { - pos=from[found_set[i-1].table_offset]; - rep_str[i].found= !bcmp((const uchar*) pos, - (const uchar*) "\\^", 3) ? 2 : 1; - rep_str[i].replace_string=to_array[found_set[i-1].table_offset]; - rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos); - rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+ - end_of_word(pos); - } - for (i=0 ; i < sets.count ; i++) - { - for (j=0 ; j < 256 ; j++) - if (sets.set[i].next[j] >= 0) - replace[i].next[j]=replace+sets.set[i].next[j]; - else - replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1)); - } - } - my_free(follow,MYF(0)); - free_sets(&sets); - my_free(found_set,MYF(0)); - DBUG_PRINT("exit",("Replace table has %d states",sets.count)); - DBUG_RETURN(replace); -} - - -int init_sets(REP_SETS *sets,uint states) -{ - bzero((char*) sets,sizeof(*sets)); - sets->size_of_bits=((states+7)/8); - if (!(sets->set_buffer=(REP_SET*) my_malloc(sizeof(REP_SET)*SET_MALLOC_HUNC, - MYF(MY_WME)))) - return 1; - if (!(sets->bit_buffer=(uint*) my_malloc(sizeof(uint)*sets->size_of_bits* - SET_MALLOC_HUNC,MYF(MY_WME)))) - { - my_free(sets->set,MYF(0)); - return 1; - } - return 0; -} - -/* Make help sets invisible for nicer codeing */ - -void make_sets_invisible(REP_SETS *sets) -{ - sets->invisible=sets->count; - sets->set+=sets->count; - sets->count=0; -} - -REP_SET *make_new_set(REP_SETS *sets) -{ - uint i,count,*bit_buffer; - REP_SET *set; - if (sets->extra) - { - sets->extra--; - set=sets->set+ sets->count++; - bzero((char*) set->bits,sizeof(uint)*sets->size_of_bits); - bzero((char*) &set->next[0],sizeof(set->next[0])*LAST_CHAR_CODE); - set->found_offset=0; - set->found_len=0; - set->table_offset= (uint) ~0; - set->size_of_bits=sets->size_of_bits; - return set; - } - count=sets->count+sets->invisible+SET_MALLOC_HUNC; - if (!(set=(REP_SET*) my_realloc((uchar*) sets->set_buffer, - sizeof(REP_SET)*count, - MYF(MY_WME)))) - return 0; - sets->set_buffer=set; - sets->set=set+sets->invisible; - if (!(bit_buffer=(uint*) my_realloc((uchar*) sets->bit_buffer, - (sizeof(uint)*sets->size_of_bits)*count, - MYF(MY_WME)))) - return 0; - sets->bit_buffer=bit_buffer; - for (i=0 ; i < count ; i++) - { - sets->set_buffer[i].bits=bit_buffer; - bit_buffer+=sets->size_of_bits; - } - sets->extra=SET_MALLOC_HUNC; - return make_new_set(sets); -} - -void free_last_set(REP_SETS *sets) -{ - sets->count--; - sets->extra++; - return; -} - -void free_sets(REP_SETS *sets) -{ - my_free(sets->set_buffer,MYF(0)); - my_free(sets->bit_buffer,MYF(0)); - return; -} - -void internal_set_bit(REP_SET *set, uint bit) -{ - set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT); - return; -} - -void internal_clear_bit(REP_SET *set, uint bit) -{ - set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT)); - return; -} - - -void or_bits(REP_SET *to,REP_SET *from) -{ - reg1 uint i; - for (i=0 ; i < to->size_of_bits ; i++) - to->bits[i]|=from->bits[i]; - return; -} - -void copy_bits(REP_SET *to,REP_SET *from) -{ - memcpy((uchar*) to->bits,(uchar*) from->bits, - (size_t) (sizeof(uint) * to->size_of_bits)); -} - -int cmp_bits(REP_SET *set1,REP_SET *set2) -{ - return bcmp((uchar*) set1->bits,(uchar*) set2->bits, - sizeof(uint) * set1->size_of_bits); -} - - -/* Get next set bit from set. */ - -int get_next_bit(REP_SET *set,uint lastpos) -{ - uint pos,*start,*end,bits; - - start=set->bits+ ((lastpos+1) / WORD_BIT); - end=set->bits + set->size_of_bits; - bits=start[0] & ~((1 << ((lastpos+1) % WORD_BIT)) -1); - - while (! bits && ++start < end) - bits=start[0]; - if (!bits) - return 0; - pos=(uint) (start-set->bits)*WORD_BIT; - while (! (bits & 1)) - { - bits>>=1; - pos++; - } - return pos; -} - -/* find if there is a same set in sets. If there is, use it and - free given set, else put in given set in sets and return its - position */ - -int find_set(REP_SETS *sets,REP_SET *find) -{ - uint i; - for (i=0 ; i < sets->count-1 ; i++) - { - if (!cmp_bits(sets->set+i,find)) - { - free_last_set(sets); - return i; - } - } - return i; /* return new postion */ -} - -/* find if there is a found_set with same table_offset & found_offset - If there is return offset to it, else add new offset and return pos. - Pos returned is -offset-2 in found_set_structure because it is - saved in set->next and set->next[] >= 0 points to next set and - set->next[] == -1 is reserved for end without replaces. -*/ - -int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) -{ - int i; - for (i=0 ; (uint) i < found_sets ; i++) - if (found_set[i].table_offset == table_offset && - found_set[i].found_offset == found_offset) - return -i-2; - found_set[i].table_offset=table_offset; - found_set[i].found_offset=found_offset; - found_sets++; - return -i-2; /* return new postion */ -} - -/* Return 1 if regexp starts with \b or ends with \b*/ - -uint start_at_word(char * pos) -{ - return (((!bcmp((const uchar*) pos, (const uchar*) "\\b",2) && pos[2]) || - !bcmp((const uchar*) pos, (const uchar*) "\\^", 2)) ? 1 : 0); -} - -uint end_of_word(char * pos) -{ - char * end=strend(pos); - return ((end > pos+2 && !bcmp((const uchar*) end-2, - (const uchar*) "\\b", 2)) || - (end >= pos+2 && !bcmp((const uchar*) end-2, - (const uchar*) "\\$",2))) ? 1 : 0; -} - -/**************************************************************************** - * Handle replacement of strings - ****************************************************************************/ - -#define PC_MALLOC 256 /* Bytes for pointers */ -#define PS_MALLOC 512 /* Bytes for data */ - -int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name) -{ - uint i,length,old_count; - uchar *new_pos; - const char **new_array; - DBUG_ENTER("insert_pointer_name"); - - if (! pa->typelib.count) - { - if (!(pa->typelib.type_names=(const char **) - my_malloc(((PC_MALLOC-MALLOC_OVERHEAD)/ - (sizeof(char *)+sizeof(*pa->flag))* - (sizeof(char *)+sizeof(*pa->flag))),MYF(MY_WME)))) - DBUG_RETURN(-1); - if (!(pa->str= (uchar*) my_malloc((uint) (PS_MALLOC-MALLOC_OVERHEAD), - MYF(MY_WME)))) - { - my_free((char*) pa->typelib.type_names,MYF(0)); - DBUG_RETURN (-1); - } - pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(uchar*)+ - sizeof(*pa->flag)); - pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); - pa->length=0; - pa->max_length=PS_MALLOC-MALLOC_OVERHEAD; - pa->array_allocs=1; - } - length=(uint) strlen(name)+1; - if (pa->length+length >= pa->max_length) - { - if (!(new_pos= (uchar*) my_realloc((uchar*) pa->str, - (uint) (pa->max_length+PS_MALLOC), - MYF(MY_WME)))) - DBUG_RETURN(1); - if (new_pos != pa->str) - { - my_ptrdiff_t diff=PTR_BYTE_DIFF(new_pos,pa->str); - for (i=0 ; i < pa->typelib.count ; i++) - pa->typelib.type_names[i]= ADD_TO_PTR(pa->typelib.type_names[i],diff, - char*); - pa->str=new_pos; - } - pa->max_length+=PS_MALLOC; - } - if (pa->typelib.count >= pa->max_count-1) - { - int len; - pa->array_allocs++; - len=(PC_MALLOC*pa->array_allocs - MALLOC_OVERHEAD); - if (!(new_array=(const char **) my_realloc((uchar*) pa->typelib.type_names, - (uint) len/ - (sizeof(uchar*)+sizeof(*pa->flag))* - (sizeof(uchar*)+sizeof(*pa->flag)), - MYF(MY_WME)))) - DBUG_RETURN(1); - pa->typelib.type_names=new_array; - old_count=pa->max_count; - pa->max_count=len/(sizeof(uchar*) + sizeof(*pa->flag)); - pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); - memcpy((uchar*) pa->flag,(char *) (pa->typelib.type_names+old_count), - old_count*sizeof(*pa->flag)); - } - pa->flag[pa->typelib.count]=0; /* Reset flag */ - pa->typelib.type_names[pa->typelib.count++]= (char*) pa->str+pa->length; - pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ - VOID(strmov((char*) pa->str+pa->length,name)); - pa->length+=length; - DBUG_RETURN(0); -} /* insert_pointer_name */ - - -/* free pointer array */ - -void free_pointer_array(POINTER_ARRAY *pa) -{ - if (pa->typelib.count) - { - pa->typelib.count=0; - my_free((char*) pa->typelib.type_names,MYF(0)); - pa->typelib.type_names=0; - my_free(pa->str,MYF(0)); - } -} /* free_pointer_array */ - - -/* Functions that uses replace and replace_regex */ - -/* Append the string to ds, with optional replace */ -void replace_dynstr_append_mem(DYNAMIC_STRING *ds, - const char *val, int len) -{ -#ifdef __WIN__ - fix_win_paths(val, len); -#endif - - if (glob_replace_regex) - { - /* Regex replace */ - if (!multi_reg_replace(glob_replace_regex, (char*)val)) - { - val= glob_replace_regex->buf; - len= strlen(val); - } - } - - if (glob_replace) - { - /* Normal replace */ - replace_strings_append(glob_replace, ds, val, len); - } - else - dynstr_append_mem(ds, val, len); -} - - -/* Append zero-terminated string to ds, with optional replace */ -void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) -{ - replace_dynstr_append_mem(ds, val, strlen(val)); -} - -/* Append uint to ds, with optional replace */ -void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) -{ - char buff[22]; /* This should be enough for any int */ - char *end= longlong10_to_str(val, buff, 10); - replace_dynstr_append_mem(ds, buff, end - buff); -} - - - -/* - Build a list of pointer to each line in ds_input, sort - the list and use the sorted list to append the strings - sorted to the output ds - - SYNOPSIS - dynstr_append_sorted - ds - string where the sorted output will be appended - ds_input - string to be sorted - -*/ - -static int comp_lines(const char **a, const char **b) -{ - return (strcmp(*a,*b)); -} - -void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING *ds_input) -{ - unsigned i; - char *start= ds_input->str; - DYNAMIC_ARRAY lines; - DBUG_ENTER("dynstr_append_sorted"); - - if (!*start) - DBUG_VOID_RETURN; /* No input */ - - my_init_dynamic_array(&lines, sizeof(const char*), 32, 32); - - /* First line is result header, skip past it */ - while (*start && *start != '\n') - start++; - start++; /* Skip past \n */ - dynstr_append_mem(ds, ds_input->str, start - ds_input->str); - - /* Insert line(s) in array */ - while (*start) - { - char* line_end= (char*)start; - - /* Find end of line */ - while (*line_end && *line_end != '\n') - line_end++; - *line_end= 0; - - /* Insert pointer to the line in array */ - if (insert_dynamic(&lines, (uchar*) &start)) - die("Out of memory inserting lines to sort"); - - start= line_end+1; - } - - /* Sort array */ - qsort(lines.buffer, lines.elements, - sizeof(char**), (qsort_cmp)comp_lines); - - /* Create new result */ - for (i= 0; i < lines.elements ; i++) - { - const char **line= dynamic_element(&lines, i, const char**); - dynstr_append(ds, *line); - dynstr_append(ds, "\n"); - } - - delete_dynamic(&lines); - DBUG_VOID_RETURN; -} diff --git a/client/mysqltest.cc b/client/mysqltest.cc new file mode 100644 index 00000000000..b110cc88514 --- /dev/null +++ b/client/mysqltest.cc @@ -0,0 +1,9101 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + mysqltest + + Tool used for executing a .test file + + See the "MySQL Test framework manual" for more information + http://dev.mysql.com/doc/mysqltest/en/index.html + + Please keep the test framework tools identical in all versions! + + Written by: + Sasha Pachev + Matt Wagner + Monty + Jani + Holyfoot +*/ + +#define MTEST_VERSION "3.3" + +#include "client_priv.h" +#include +#include +#include +#include +#include +#include +#include +#include "my_regex.h" /* Our own version of regex */ +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef __WIN__ +#include +#endif +#include +#include + +#ifdef __WIN__ +#include +#define SIGNAL_FMT "exception 0x%x" +#else +#define SIGNAL_FMT "signal %d" +#endif + +/* Use cygwin for --exec and --system before 5.0 */ +#if MYSQL_VERSION_ID < 50000 +#define USE_CYGWIN +#endif + +#define MAX_VAR_NAME_LENGTH 256 +#define MAX_COLUMNS 256 +#define MAX_EMBEDDED_SERVER_ARGS 64 +#define MAX_DELIMITER_LENGTH 16 + +/* Flags controlling send and reap */ +#define QUERY_SEND_FLAG 1 +#define QUERY_REAP_FLAG 2 + +enum { + OPT_SKIP_SAFEMALLOC=OPT_MAX_CLIENT_OPTION, + OPT_PS_PROTOCOL, OPT_SP_PROTOCOL, OPT_CURSOR_PROTOCOL, OPT_VIEW_PROTOCOL, + OPT_MAX_CONNECT_RETRIES, OPT_MARK_PROGRESS, OPT_LOG_DIR, OPT_TAIL_LINES +}; + +static int record= 0, opt_sleep= -1; +static char *opt_db= 0, *opt_pass= 0; +const char *opt_user= 0, *opt_host= 0, *unix_sock= 0, *opt_basedir= "./"; +const char *opt_logdir= ""; +const char *opt_include= 0, *opt_charsets_dir; +static int opt_port= 0; +static int opt_max_connect_retries; +static my_bool opt_compress= 0, silent= 0, verbose= 0; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static my_bool tty_password= 0; +static my_bool opt_mark_progress= 0; +static my_bool ps_protocol= 0, ps_protocol_enabled= 0; +static my_bool sp_protocol= 0, sp_protocol_enabled= 0; +static my_bool view_protocol= 0, view_protocol_enabled= 0; +static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; +static my_bool parsing_disabled= 0; +static my_bool display_result_vertically= FALSE, + display_metadata= FALSE, display_result_sorted= FALSE; +static my_bool disable_query_log= 0, disable_result_log= 0; +static my_bool disable_warnings= 0; +static my_bool disable_info= 1; +static my_bool abort_on_error= 1; +static my_bool server_initialized= 0; +static my_bool is_windows= 0; +static char **default_argv; +static const char *load_default_groups[]= { "mysqltest", "client", 0 }; +static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer; + +static uint start_lineno= 0; /* Start line of current command */ +static uint my_end_arg= 0; + +/* Number of lines of the result to include in failure report */ +static uint opt_tail_lines= 0; + +static char delimiter[MAX_DELIMITER_LENGTH]= ";"; +static uint delimiter_length= 1; + +static char TMPDIR[FN_REFLEN]; + +/* Block stack */ +enum block_cmd { + cmd_none, + cmd_if, + cmd_while +}; + +struct st_block +{ + int line; /* Start line of block */ + my_bool ok; /* Should block be executed */ + enum block_cmd cmd; /* Command owning the block */ +}; + +static struct st_block block_stack[32]; +static struct st_block *cur_block, *block_stack_end; + +/* Open file stack */ +struct st_test_file +{ + FILE* file; + const char *file_name; + uint lineno; /* Current line in file */ +}; + +static struct st_test_file file_stack[16]; +static struct st_test_file* cur_file; +static struct st_test_file* file_stack_end; + + +static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ + +static const char *embedded_server_groups[]= +{ + "server", + "embedded", + "mysqltest_SERVER", + NullS +}; + +static int embedded_server_arg_count=0; +static char *embedded_server_args[MAX_EMBEDDED_SERVER_ARGS]; + +/* + Timer related variables + See the timer_output() definition for details +*/ +static char *timer_file = NULL; +static ulonglong timer_start; +static void timer_output(void); +static ulonglong timer_now(void); + + +/* Precompiled re's */ +static my_regex_t ps_re; /* the query can be run using PS protocol */ +static my_regex_t sp_re; /* the query can be run as a SP */ +static my_regex_t view_re; /* the query can be run as a view*/ + +static void init_re(void); +static int match_re(my_regex_t *, char *); +static void free_re(void); + +DYNAMIC_ARRAY q_lines; + +#include "sslopt-vars.h" + +struct Parser +{ + int read_lines,current_line; +} parser; + +struct MasterPos +{ + char file[FN_REFLEN]; + ulong pos; +} master_pos; + +/* if set, all results are concated and compared against this file */ +const char *result_file_name= 0; + +typedef struct +{ + char *name; + int name_len; + char *str_val; + int str_val_len; + int int_val; + int alloced_len; + int int_dirty; /* do not update string if int is updated until first read */ + int alloced; + char *env_s; +} VAR; + +/*Perl/shell-like variable registers */ +VAR var_reg[10]; + +HASH var_hash; + +struct st_connection +{ + MYSQL mysql; + /* Used when creating views and sp, to avoid implicit commit */ + MYSQL* util_mysql; + char *name; + size_t name_len; + MYSQL_STMT* stmt; + +#ifdef EMBEDDED_LIBRARY + const char *cur_query; + int cur_query_len; + pthread_mutex_t mutex; + pthread_cond_t cond; + int query_done; +#endif /*EMBEDDED_LIBRARY*/ +}; +struct st_connection connections[128]; +struct st_connection* cur_con= NULL, *next_con, *connections_end; + +/* + List of commands in mysqltest + Must match the "command_names" array + Add new commands before Q_UNKNOWN! +*/ +enum enum_commands { + Q_CONNECTION=1, Q_QUERY, + Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, + Q_INC, Q_DEC, + Q_SOURCE, Q_DISCONNECT, + Q_LET, Q_ECHO, + Q_WHILE, Q_END_BLOCK, + Q_SYSTEM, Q_RESULT, + Q_REQUIRE, Q_SAVE_MASTER_POS, + Q_SYNC_WITH_MASTER, + Q_SYNC_SLAVE_WITH_MASTER, + Q_ERROR, + Q_SEND, Q_REAP, + Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN, + Q_PING, Q_EVAL, + Q_RPL_PROBE, Q_ENABLE_RPL_PARSE, + Q_DISABLE_RPL_PARSE, Q_EVAL_RESULT, + Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG, + Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG, + Q_WAIT_FOR_SLAVE_TO_STOP, + Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS, + Q_ENABLE_INFO, Q_DISABLE_INFO, + Q_ENABLE_METADATA, Q_DISABLE_METADATA, + Q_EXEC, Q_DELIMITER, + Q_DISABLE_ABORT_ON_ERROR, Q_ENABLE_ABORT_ON_ERROR, + Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, + Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_SORTED_RESULT, + Q_START_TIMER, Q_END_TIMER, + Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, + Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, + Q_IF, + Q_DISABLE_PARSING, Q_ENABLE_PARSING, + Q_REPLACE_REGEX, Q_REMOVE_FILE, Q_FILE_EXIST, + Q_WRITE_FILE, Q_COPY_FILE, Q_PERL, Q_DIE, Q_EXIT, Q_SKIP, + Q_CHMOD_FILE, Q_APPEND_FILE, Q_CAT_FILE, Q_DIFF_FILES, + Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, + Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, + + Q_UNKNOWN, /* Unknown command. */ + Q_COMMENT, /* Comments, ignored. */ + Q_COMMENT_WITH_COMMAND +}; + + +const char *command_names[]= +{ + "connection", + "query", + "connect", + "sleep", + "real_sleep", + "inc", + "dec", + "source", + "disconnect", + "let", + "echo", + "while", + "end", + "system", + "result", + "require", + "save_master_pos", + "sync_with_master", + "sync_slave_with_master", + "error", + "send", + "reap", + "dirty_close", + "replace_result", + "replace_column", + "ping", + "eval", + "rpl_probe", + "enable_rpl_parse", + "disable_rpl_parse", + "eval_result", + /* Enable/disable that the _query_ is logged to result file */ + "enable_query_log", + "disable_query_log", + /* Enable/disable that the _result_ from a query is logged to result file */ + "enable_result_log", + "disable_result_log", + "wait_for_slave_to_stop", + "enable_warnings", + "disable_warnings", + "enable_info", + "disable_info", + "enable_metadata", + "disable_metadata", + "exec", + "delimiter", + "disable_abort_on_error", + "enable_abort_on_error", + "vertical_results", + "horizontal_results", + "query_vertical", + "query_horizontal", + "sorted_result", + "start_timer", + "end_timer", + "character_set", + "disable_ps_protocol", + "enable_ps_protocol", + "disable_reconnect", + "enable_reconnect", + "if", + "disable_parsing", + "enable_parsing", + "replace_regex", + "remove_file", + "file_exists", + "write_file", + "copy_file", + "perl", + "die", + + /* Don't execute any more commands, compare result */ + "exit", + "skip", + "chmod", + "append_file", + "cat_file", + "diff_files", + "send_quit", + "change_user", + "mkdir", + "rmdir", + "send_shutdown", + "shutdown_server", + + 0 +}; + + +/* + The list of error codes to --error are stored in an internal array of + structs. This struct can hold numeric SQL error codes, error names 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 returns 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 +}; + +struct st_match_err +{ + enum match_err_type type; + union + { + uint errnum; + char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ + } code; +}; + +struct st_expected_errors +{ + struct st_match_err err[10]; + uint count; +}; +static struct st_expected_errors saved_expected_errors; + +struct st_command +{ + char *query, *query_buf,*first_argument,*last_argument,*end; + int first_word_len, query_len; + my_bool abort_on_error; + struct st_expected_errors expected_errors; + char require_file[FN_REFLEN]; + enum enum_commands type; +}; + +TYPELIB command_typelib= {array_elements(command_names),"", + command_names, 0}; + +DYNAMIC_STRING ds_res; + +char builtin_echo[FN_REFLEN]; + +void die(const char *fmt, ...) + ATTRIBUTE_FORMAT(printf, 1, 2); +void abort_not_supported_test(const char *fmt, ...) + ATTRIBUTE_FORMAT(printf, 1, 2); +void verbose_msg(const char *fmt, ...) + ATTRIBUTE_FORMAT(printf, 1, 2); +void log_msg(const char *fmt, ...) + ATTRIBUTE_FORMAT(printf, 1, 2); + +VAR* var_from_env(const char *, const char *); +VAR* var_init(VAR* v, const char *name, int name_len, const char *val, + int val_len); +void var_free(void* v); +VAR* var_get(const char *var_name, const char** var_name_end, + my_bool raw, my_bool ignore_not_existing); +void eval_expr(VAR* v, const char *p, const char** p_end); +my_bool match_delimiter(int c, const char *delim, uint length); +void dump_result_to_reject_file(char *buf, int size); +void dump_warning_messages(); + +void do_eval(DYNAMIC_STRING *query_eval, const char *query, + const char *query_end, my_bool pass_through_escape_chars); +void str_to_file(const char *fname, char *str, int size); +void str_to_file2(const char *fname, char *str, int size, my_bool append); + +void fix_win_paths(const char *val, int len); + +#ifdef __WIN__ +void free_tmp_sh_file(); +void free_win_path_patterns(); +#endif + + +/* For replace_column */ +static char *replace_column[MAX_COLUMNS]; +static uint max_replace_column= 0; +void do_get_replace_column(struct st_command*); +void free_replace_column(); + +/* For replace */ +void do_get_replace(struct st_command *command); +void free_replace(); + +/* For replace_regex */ +void do_get_replace_regex(struct st_command *command); +void free_replace_regex(); + + +void free_all_replace(){ + free_replace(); + free_replace_regex(); + free_replace_column(); +} + + +class LogFile { + FILE* m_file; + char m_file_name[FN_REFLEN]; + uint m_bytes_written; +public: + LogFile() : m_file(NULL), m_bytes_written(0) { + bzero(m_file_name, sizeof(m_file_name)); + } + + ~LogFile() { + close(); + } + + const char* file_name() const { return m_file_name; } + uint bytes_written() const { return m_bytes_written; } + + void open(const char* dir, const char* name, const char* ext) + { + DBUG_ENTER("LogFile::open"); + DBUG_PRINT("enter", ("dir: %s, name: %s", + name, dir)); + if (!name) + { + m_file= stdout; + DBUG_VOID_RETURN; + } + + fn_format(m_file_name, name, dir, ext, + *dir ? MY_REPLACE_DIR | MY_REPLACE_EXT : + MY_REPLACE_EXT); + + DBUG_PRINT("info", ("file_name: %s", m_file_name)); + + if ((m_file= fopen(m_file_name, "w+")) == NULL) + die("Failed to open log file %s, errno: %d", m_file_name, errno); + + DBUG_VOID_RETURN; + } + + void close() + { + if (m_file && m_file != stdout) + fclose(m_file); + m_file= NULL; + } + + void flush() + { + if (m_file && m_file != stdout) + fflush(m_file); + } + + void write(DYNAMIC_STRING* ds) + { + DBUG_ENTER("LogFile::write"); + DBUG_ASSERT(m_file); + + if (ds->length == 0) + DBUG_VOID_RETURN; + DBUG_ASSERT(ds->str); + + if (fwrite(ds->str, 1, ds->length, m_file) != ds->length) + die("Failed to write %d bytes to '%s', errno: %d", + ds->length, m_file_name, errno); + m_bytes_written+= ds->length; + DBUG_VOID_RETURN; + } + + void show_tail(uint lines) { + DBUG_ENTER("LogFile::show_tail"); + + if (!m_file || m_file == stdout) + DBUG_VOID_RETURN; + + if (lines == 0) + DBUG_VOID_RETURN; + lines++; + + int show_offset= 0; + char buf[256]; + size_t bytes; + bool found_bof= false; + + /* Search backward in file until "lines" newline has been found */ + while (lines && !found_bof) + { + show_offset-= sizeof(buf); + while(fseek(m_file, show_offset, SEEK_END) != 0 && show_offset < 0) + { + found_bof= true; + // Seeking before start of file + show_offset++; + } + + if ((bytes= fread(buf, 1, sizeof(buf), m_file)) <= 0) + { + fprintf(stderr, "Failed to read from '%s', errno: %d\n", + m_file_name, errno); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", ("Read %d bytes from file, buf: %s", bytes, buf)); + + char* show_from= buf + bytes; + while(show_from > buf && lines > 0 ) + { + show_from--; + if (*show_from == '\n') + lines--; + } + if (show_from != buf) + { + // The last new line was found in this buf, adjust offset + show_offset+= (show_from - buf) + 1; + DBUG_PRINT("info", ("adjusted offset to %d", show_offset)); + } + DBUG_PRINT("info", ("show_offset: %d", show_offset)); + } + + fprintf(stderr, "\nThe result from queries just before the failure was:\n"); + + DBUG_PRINT("info", ("show_offset: %d", show_offset)); + if (!lines) + { + fprintf(stderr, "< snip >\n"); + + if (fseek(m_file, show_offset, SEEK_END) != 0) + { + fprintf(stderr, "Failed to seek to position %d in '%s', errno: %d", + show_offset, m_file_name, errno); + DBUG_VOID_RETURN; + } + + } + else { + DBUG_PRINT("info", ("Showing the whole file")); + if (fseek(m_file, 0L, SEEK_SET) != 0) + { + fprintf(stderr, "Failed to seek to pos 0 in '%s', errno: %d", + m_file_name, errno); + DBUG_VOID_RETURN; + } + } + + while ((bytes= fread(buf, 1, sizeof(buf), m_file)) > 0) + fwrite(buf, 1, bytes, stderr); + + if (!lines) + { + fprintf(stderr, + "\nMore results from queries before failure can be found in %s\n", + m_file_name); + } + fflush(stderr); + + DBUG_VOID_RETURN; + } +}; + +LogFile log_file; +LogFile progress_file; + + +/* Disable functions that only exist in MySQL 4.0 */ +#if MYSQL_VERSION_ID < 40000 +void mysql_enable_rpl_parse(MYSQL* mysql __attribute__((unused))) {} +void mysql_disable_rpl_parse(MYSQL* mysql __attribute__((unused))) {} +int mysql_rpl_parse_enabled(MYSQL* mysql __attribute__((unused))) { return 1; } +my_bool mysql_rpl_probe(MYSQL *mysql __attribute__((unused))) { return 1; } +#endif +void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, + int len); +void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); +void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val); +void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING* ds_input); + +void handle_error(struct st_command*, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds); +void handle_no_error(struct st_command*); + +#ifdef EMBEDDED_LIBRARY + +/* attributes of the query thread */ +pthread_attr_t cn_thd_attrib; + +/* + send_one_query executes query in separate thread, which is + necessary in embedded library to run 'send' in proper way. + This implementation doesn't handle errors returned + by mysql_send_query. It's technically possible, though + I don't see where it is needed. +*/ +pthread_handler_t send_one_query(void *arg) +{ + struct st_connection *cn= (struct st_connection*)arg; + + mysql_thread_init(); + VOID(mysql_send_query(&cn->mysql, cn->cur_query, cn->cur_query_len)); + + mysql_thread_end(); + pthread_mutex_lock(&cn->mutex); + cn->query_done= 1; + VOID(pthread_cond_signal(&cn->cond)); + pthread_mutex_unlock(&cn->mutex); + pthread_exit(0); + return 0; +} + +static int do_send_query(struct st_connection *cn, const char *q, int q_len, + int flags) +{ + pthread_t tid; + + if (flags & QUERY_REAP_FLAG) + return mysql_send_query(&cn->mysql, q, q_len); + + if (pthread_mutex_init(&cn->mutex, NULL) || + pthread_cond_init(&cn->cond, NULL)) + die("Error in the thread library"); + + cn->cur_query= q; + cn->cur_query_len= q_len; + cn->query_done= 0; + if (pthread_create(&tid, &cn_thd_attrib, send_one_query, (void*)cn)) + die("Cannot start new thread for query"); + + return 0; +} + +static void wait_query_thread_end(struct st_connection *con) +{ + if (!con->query_done) + { + pthread_mutex_lock(&con->mutex); + while (!con->query_done) + pthread_cond_wait(&con->cond, &con->mutex); + pthread_mutex_unlock(&con->mutex); + } +} + +#else /*EMBEDDED_LIBRARY*/ + +#define do_send_query(cn,q,q_len,flags) mysql_send_query(&cn->mysql, q, q_len) + +#endif /*EMBEDDED_LIBRARY*/ + +void do_eval(DYNAMIC_STRING *query_eval, const char *query, + const char *query_end, my_bool pass_through_escape_chars) +{ + const char *p; + register char c, next_c; + register int escaped = 0; + VAR *v; + DBUG_ENTER("do_eval"); + + for (p= query; (c= *p) && p < query_end; ++p) + { + switch(c) { + case '$': + if (escaped) + { + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + } + else + { + if (!(v= var_get(p, &p, 0, 0))) + die("Bad variable in eval"); + dynstr_append_mem(query_eval, v->str_val, v->str_val_len); + } + break; + case '\\': + next_c= *(p+1); + if (escaped) + { + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + } + else if (next_c == '\\' || next_c == '$' || next_c == '"') + { + /* Set escaped only if next char is \, " or $ */ + escaped= 1; + + if (pass_through_escape_chars) + { + /* The escape char should be added to the output string. */ + dynstr_append_mem(query_eval, p, 1); + } + } + else + dynstr_append_mem(query_eval, p, 1); + break; + default: + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + break; + } + } +#ifdef __WIN__ + fix_win_paths(query_eval->str, query_eval->length); +#endif + DBUG_VOID_RETURN; +} + + +/* + Run query and dump the result to stderr in vertical format + + NOTE! This function should be safe to call when an error + has occured and thus any further errors will be ignored(although logged) + + SYNOPSIS + show_query + mysql - connection to use + query - query to run + +*/ + +static void show_query(MYSQL* mysql, const char* query) +{ + MYSQL_RES* res; + DBUG_ENTER("show_query"); + + if (!mysql) + DBUG_VOID_RETURN; + + if (mysql_query(mysql, query)) + { + log_msg("Error running query '%s': %d %s", + query, mysql_errno(mysql), mysql_error(mysql)); + DBUG_VOID_RETURN; + } + + if ((res= mysql_store_result(mysql)) == NULL) + { + /* No result set returned */ + DBUG_VOID_RETURN; + } + + { + MYSQL_ROW row; + unsigned int i; + unsigned int row_num= 0; + unsigned int num_fields= mysql_num_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); + + fprintf(stderr, "=== %s ===\n", query); + while ((row= mysql_fetch_row(res))) + { + unsigned long *lengths= mysql_fetch_lengths(res); + row_num++; + + fprintf(stderr, "---- %d. ----\n", row_num); + for(i= 0; i < num_fields; i++) + { + fprintf(stderr, "%s\t%.*s\n", + fields[i].name, + (int)lengths[i], row[i] ? row[i] : "NULL"); + } + } + for (i= 0; i < strlen(query)+8; i++) + fprintf(stderr, "="); + fprintf(stderr, "\n\n"); + } + mysql_free_result(res); + + DBUG_VOID_RETURN; +} + + +/* + Show any warnings just before the error. Since the last error + is added to the warning stack, only print @@warning_count-1 warnings. + + NOTE! This function should be safe to call when an error + has occured and this any further errors will be ignored(although logged) + + SYNOPSIS + show_warnings_before_error + mysql - connection to use + +*/ + +static void show_warnings_before_error(MYSQL* mysql) +{ + MYSQL_RES* res; + const char* query= "SHOW WARNINGS"; + DBUG_ENTER("show_warnings_before_error"); + + if (!mysql) + DBUG_VOID_RETURN; + + if (mysql_query(mysql, query)) + { + log_msg("Error running query '%s': %d %s", + query, mysql_errno(mysql), mysql_error(mysql)); + DBUG_VOID_RETURN; + } + + if ((res= mysql_store_result(mysql)) == NULL) + { + /* No result set returned */ + DBUG_VOID_RETURN; + } + + if (mysql_num_rows(res) <= 1) + { + /* Don't display the last row, it's "last error" */ + } + else + { + MYSQL_ROW row; + unsigned int row_num= 0; + unsigned int num_fields= mysql_num_fields(res); + + fprintf(stderr, "\nWarnings from just before the error:\n"); + while ((row= mysql_fetch_row(res))) + { + unsigned int i; + unsigned long *lengths= mysql_fetch_lengths(res); + + if (++row_num >= mysql_num_rows(res)) + { + /* Don't display the last row, it's "last error" */ + break; + } + + for(i= 0; i < num_fields; i++) + { + fprintf(stderr, "%.*s ", (int)lengths[i], + row[i] ? row[i] : "NULL"); + } + fprintf(stderr, "\n"); + } + } + mysql_free_result(res); + + DBUG_VOID_RETURN; +} + + +enum arg_type +{ + ARG_STRING, + ARG_REST +}; + +struct command_arg { + const char *argname; /* Name of argument */ + enum arg_type type; /* Type of argument */ + my_bool required; /* Argument required */ + DYNAMIC_STRING *ds; /* Storage for argument */ + const char *description; /* Description of the argument */ +}; + + +void check_command_args(struct st_command *command, + const char *arguments, + const struct command_arg *args, + int num_args, const char delimiter_arg) +{ + int i; + const char *ptr= arguments; + const char *start; + DBUG_ENTER("check_command_args"); + DBUG_PRINT("enter", ("num_args: %d", num_args)); + + for (i= 0; i < num_args; i++) + { + const struct command_arg *arg= &args[i]; + + switch (arg->type) { + /* A string */ + case ARG_STRING: + /* Skip leading spaces */ + while (*ptr && *ptr == ' ') + ptr++; + start= ptr; + /* Find end of arg, terminated by "delimiter_arg" */ + while (*ptr && *ptr != delimiter_arg) + ptr++; + if (ptr > start) + { + init_dynamic_string(arg->ds, 0, ptr-start, 32); + do_eval(arg->ds, start, ptr, FALSE); + } + else + { + /* Empty string */ + init_dynamic_string(arg->ds, "", 0, 0); + } + command->last_argument= (char*)ptr; + + /* Step past the delimiter */ + if (*ptr && *ptr == delimiter_arg) + ptr++; + DBUG_PRINT("info", ("val: %s", arg->ds->str)); + break; + + /* Rest of line */ + case ARG_REST: + start= ptr; + init_dynamic_string(arg->ds, 0, command->query_len, 256); + do_eval(arg->ds, start, command->end, FALSE); + command->last_argument= command->end; + DBUG_PRINT("info", ("val: %s", arg->ds->str)); + break; + + default: + DBUG_ASSERT("Unknown argument type"); + break; + } + + /* Check required arg */ + if (arg->ds->length == 0 && arg->required) + die("Missing required argument '%s' to command '%.*s'", arg->argname, + command->first_word_len, command->query); + + } + /* Check for too many arguments passed */ + ptr= command->last_argument; + while(ptr <= command->end) + { + if (*ptr && *ptr != ' ') + die("Extra argument '%s' passed to '%.*s'", + ptr, command->first_word_len, command->query); + ptr++; + } + DBUG_VOID_RETURN; +} + + +void handle_command_error(struct st_command *command, uint error) +{ + DBUG_ENTER("handle_command_error"); + DBUG_PRINT("enter", ("error: %d", error)); + if (error != 0) + { + uint i; + + if (command->abort_on_error) + die("command \"%.*s\" failed with error %d", + command->first_word_len, command->query, error); + for (i= 0; i < command->expected_errors.count; i++) + { + DBUG_PRINT("info", ("expected error: %d", + command->expected_errors.err[i].code.errnum)); + if ((command->expected_errors.err[i].type == ERR_ERRNO) && + (command->expected_errors.err[i].code.errnum == error)) + { + DBUG_PRINT("info", ("command \"%.*s\" failed with expected error: %d", + command->first_word_len, command->query, error)); + DBUG_VOID_RETURN; + } + } + die("command \"%.*s\" failed with wrong error: %d", + command->first_word_len, command->query, error); + } + else if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + die("command \"%.*s\" succeeded - should have failed with errno %d...", + command->first_word_len, command->query, + command->expected_errors.err[0].code.errnum); + } + DBUG_VOID_RETURN; +} + + +void close_connections() +{ + DBUG_ENTER("close_connections"); + for (--next_con; next_con >= connections; --next_con) + { + if (next_con->stmt) + mysql_stmt_close(next_con->stmt); + next_con->stmt= 0; + mysql_close(&next_con->mysql); + if (next_con->util_mysql) + mysql_close(next_con->util_mysql); + my_free(next_con->name, MYF(MY_ALLOW_ZERO_PTR)); + } + DBUG_VOID_RETURN; +} + + +void close_statements() +{ + struct st_connection *con; + DBUG_ENTER("close_statements"); + for (con= connections; con < next_con; con++) + { + if (con->stmt) + mysql_stmt_close(con->stmt); + con->stmt= 0; + } + DBUG_VOID_RETURN; +} + + +void close_files() +{ + DBUG_ENTER("close_files"); + for (; cur_file >= file_stack; cur_file--) + { + if (cur_file->file && cur_file->file != stdin) + { + DBUG_PRINT("info", ("closing file: %s", cur_file->file_name)); + my_fclose(cur_file->file, MYF(0)); + } + my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); + cur_file->file_name= 0; + } + DBUG_VOID_RETURN; +} + + +void free_used_memory() +{ + uint i; + DBUG_ENTER("free_used_memory"); + + close_connections(); + close_files(); + hash_free(&var_hash); + + for (i= 0 ; i < q_lines.elements ; i++) + { + struct st_command **q= dynamic_element(&q_lines, i, struct st_command**); + my_free((*q)->query_buf,MYF(MY_ALLOW_ZERO_PTR)); + my_free((*q),MYF(0)); + } + for (i= 0; i < 10; i++) + { + if (var_reg[i].alloced_len) + my_free(var_reg[i].str_val, MYF(MY_WME)); + } + while (embedded_server_arg_count > 1) + my_free(embedded_server_args[--embedded_server_arg_count],MYF(0)); + delete_dynamic(&q_lines); + dynstr_free(&ds_res); + free_all_replace(); + my_free(opt_pass,MYF(MY_ALLOW_ZERO_PTR)); + free_defaults(default_argv); + free_re(); +#ifdef __WIN__ + free_tmp_sh_file(); + free_win_path_patterns(); +#endif + + /* Only call mysql_server_end if mysql_server_init has been called */ + if (server_initialized) + mysql_server_end(); + + /* Don't use DBUG after mysql_server_end() */ + return; +} + + +static void cleanup_and_exit(int exit_code) +{ + free_used_memory(); + my_end(my_end_arg); + + if (!silent) { + switch (exit_code) { + case 1: + printf("not ok\n"); + break; + case 0: + printf("ok\n"); + break; + case 62: + printf("skipped\n"); + break; + default: + printf("unknown exit code: %d\n", exit_code); + DBUG_ASSERT(0); + } + } + + exit(exit_code); +} + +void die(const char *fmt, ...) +{ + static int dying= 0; + va_list args; + DBUG_ENTER("die"); + DBUG_PRINT("enter", ("start_lineno: %d", start_lineno)); + + /* + Protect against dying twice + first time 'die' is called, try to write log files + second time, just exit + */ + if (dying) + cleanup_and_exit(1); + dying= 1; + + /* Print the error message */ + fprintf(stderr, "mysqltest: "); + if (cur_file && cur_file != file_stack) + fprintf(stderr, "In included file \"%s\": ", + cur_file->file_name); + if (start_lineno > 0) + fprintf(stderr, "At line %u: ", start_lineno); + if (fmt) + { + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + } + else + fprintf(stderr, "unknown error"); + fprintf(stderr, "\n"); + fflush(stderr); + + log_file.show_tail(opt_tail_lines); + + /* + Help debugging by displaying any warnings that might have + been produced prior to the error + */ + if (cur_con) + show_warnings_before_error(&cur_con->mysql); + + cleanup_and_exit(1); +} + + +void abort_not_supported_test(const char *fmt, ...) +{ + va_list args; + struct st_test_file* err_file= cur_file; + DBUG_ENTER("abort_not_supported_test"); + + /* Print include filestack */ + fprintf(stderr, "The test '%s' is not supported by this installation\n", + file_stack->file_name); + fprintf(stderr, "Detected in file %s at line %d\n", + err_file->file_name, err_file->lineno); + while (err_file != file_stack) + { + err_file--; + fprintf(stderr, "included from %s at line %d\n", + err_file->file_name, err_file->lineno); + } + + /* Print error message */ + va_start(args, fmt); + if (fmt) + { + fprintf(stderr, "reason: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + va_end(args); + + cleanup_and_exit(62); +} + + +void abort_not_in_this_version() +{ + die("Not available in this version of mysqltest"); +} + + +void verbose_msg(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("verbose_msg"); + if (!verbose) + DBUG_VOID_RETURN; + + va_start(args, fmt); + fprintf(stderr, "mysqltest: "); + if (cur_file && cur_file != file_stack) + fprintf(stderr, "In included file \"%s\": ", + cur_file->file_name); + if (start_lineno != 0) + fprintf(stderr, "At line %u: ", start_lineno); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + + DBUG_VOID_RETURN; +} + + +void log_msg(const char *fmt, ...) +{ + va_list args; + char buff[1024]; + size_t len; + DBUG_ENTER("log_msg"); + + va_start(args, fmt); + len= my_vsnprintf(buff, sizeof(buff)-1, fmt, args); + va_end(args); + + dynstr_append_mem(&ds_res, buff, len); + dynstr_append(&ds_res, "\n"); + + DBUG_VOID_RETURN; +} + + +/* + Read a file and append it to ds + + SYNOPSIS + cat_file + ds - pointer to dynamic string where to add the files content + filename - name of the file to read + +*/ + +void cat_file(DYNAMIC_STRING* ds, const char* filename) +{ + int fd; + uint len; + char buff[512]; + + if ((fd= my_open(filename, O_RDONLY, MYF(0))) < 0) + die("Failed to open file '%s'", filename); + while((len= my_read(fd, (uchar*)&buff, + sizeof(buff), MYF(0))) > 0) + { + char *p= buff, *start= buff; + while (p < buff+len) + { + /* Convert cr/lf to lf */ + if (*p == '\r' && *(p+1) && *(p+1)== '\n') + { + /* Add fake newline instead of cr and output the line */ + *p= '\n'; + p++; /* Step past the "fake" newline */ + dynstr_append_mem(ds, start, p-start); + p++; /* Step past the "fake" newline */ + start= p; + } + else + p++; + } + /* Output any chars that migh be left */ + dynstr_append_mem(ds, start, p-start); + } + my_close(fd, MYF(0)); +} + + +/* + Run the specified command with popen + + SYNOPSIS + run_command + cmd - command to execute(should be properly quoted + ds_res- pointer to dynamic string where to store the result + +*/ + +static int run_command(char* cmd, + DYNAMIC_STRING *ds_res) +{ + char buf[512]= {0}; + FILE *res_file; + int error; + + if (!(res_file= popen(cmd, "r"))) + die("popen(\"%s\", \"r\") failed", cmd); + + while (fgets(buf, sizeof(buf), res_file)) + { + DBUG_PRINT("info", ("buf: %s", buf)); + if(ds_res) + { + /* Save the output of this command in the supplied string */ + dynstr_append(ds_res, buf); + } + else + { + /* Print it directly on screen */ + fprintf(stdout, "%s", buf); + } + } + + error= pclose(res_file); + return WEXITSTATUS(error); +} + + +/* + Run the specified tool with variable number of arguments + + SYNOPSIS + run_tool + tool_path - the name of the tool to run + ds_res - pointer to dynamic string where to store the result + ... - variable number of arguments that will be properly + quoted and appended after the tool's name + +*/ + +static int run_tool(const char *tool_path, DYNAMIC_STRING *ds_res, ...) +{ + int ret; + const char* arg; + va_list args; + DYNAMIC_STRING ds_cmdline; + + DBUG_ENTER("run_tool"); + DBUG_PRINT("enter", ("tool_path: %s", tool_path)); + + if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN)) + die("Out of memory"); + + dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS); + dynstr_append(&ds_cmdline, " "); + + va_start(args, ds_res); + + while ((arg= va_arg(args, char *))) + { + /* Options should be os quoted */ + if (strncmp(arg, "--", 2) == 0) + dynstr_append_os_quoted(&ds_cmdline, arg, NullS); + else + dynstr_append(&ds_cmdline, arg); + dynstr_append(&ds_cmdline, " "); + } + + va_end(args); + +#ifdef __WIN__ + dynstr_append(&ds_cmdline, "\""); +#endif + + DBUG_PRINT("info", ("Running: %s", ds_cmdline.str)); + ret= run_command(ds_cmdline.str, ds_res); + DBUG_PRINT("exit", ("ret: %d", ret)); + dynstr_free(&ds_cmdline); + DBUG_RETURN(ret); +} + + +/* + Show the diff of two files using the systems builtin diff + command. If no such diff command exist, just dump the content + of the two files and inform about how to get "diff" + + SYNOPSIS + show_diff + ds - pointer to dynamic string where to add the diff(may be NULL) + filename1 - name of first file + filename2 - name of second file + +*/ + +void show_diff(DYNAMIC_STRING* ds, + const char* filename1, const char* filename2) +{ + + const char* diff_failed= 0; + DYNAMIC_STRING ds_tmp; + + if (init_dynamic_string(&ds_tmp, "", 256, 256)) + die("Out of memory"); + + /* First try with unified diff */ + if (run_tool("diff", + &ds_tmp, /* Get output from diff in ds_tmp */ + "-u", + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + dynstr_set(&ds_tmp, ""); + + /* Fallback to context diff with "diff -c" */ + if (run_tool("diff", + &ds_tmp, /* Get output from diff in ds_tmp */ + "-c", + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + dynstr_set(&ds_tmp, ""); + + /* Fallback to plain "diff" */ + if (run_tool("diff", + &ds_tmp, /* Get output from diff in ds_tmp */ + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + dynstr_set(&ds_tmp, ""); + + diff_failed= "Could not execute 'diff -u', 'diff -c' or 'diff'"; + } + } + } + + if (diff_failed) + { + /* + Fallback to dump both files to result file and inform + about installing "diff" + */ + dynstr_append(&ds_tmp, "\n"); + dynstr_append(&ds_tmp, diff_failed); + dynstr_append(&ds_tmp, +"\n" +"The two files differ but it was not possible to execute 'diff' in\n" +"order to show only the difference. Instead the whole content of the\n" +"two files was shown for you to diff manually.\n\n" +"To get a better report you should install 'diff' on your system, which you\n" +"for example can get from http://www.gnu.org/software/diffutils/diffutils.html\n" +#ifdef __WIN__ +"or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n" +#endif +"\n"); + + dynstr_append(&ds_tmp, " --- "); + dynstr_append(&ds_tmp, filename1); + dynstr_append(&ds_tmp, " >>>\n"); + cat_file(&ds_tmp, filename1); + dynstr_append(&ds_tmp, "<<<\n --- "); + dynstr_append(&ds_tmp, filename1); + dynstr_append(&ds_tmp, " >>>\n"); + cat_file(&ds_tmp, filename2); + dynstr_append(&ds_tmp, "<<<<\n"); + } + + if (ds) + { + /* Add the diff to output */ + dynstr_append_mem(ds, ds_tmp.str, ds_tmp.length); + } + else + { + /* Print diff directly to stdout */ + fprintf(stderr, "%s\n", ds_tmp.str); + } + + dynstr_free(&ds_tmp); + +} + + +enum compare_files_result_enum { + RESULT_OK= 0, + RESULT_CONTENT_MISMATCH= 1, + RESULT_LENGTH_MISMATCH= 2 +}; + +/* + Compare two files, given a fd to the first file and + name of the second file + + SYNOPSIS + compare_files2 + fd - Open file descriptor of the first file + filename2 - Name of second file + + RETURN VALUES + According to the values in "compare_files_result_enum" + +*/ + +int compare_files2(File fd, const char* filename2) +{ + int error= RESULT_OK; + File fd2; + uint len, len2; + char buff[512], buff2[512]; + + if ((fd2= my_open(filename2, O_RDONLY, MYF(0))) < 0) + { + my_close(fd, MYF(0)); + die("Failed to open second file: '%s'", filename2); + } + while((len= my_read(fd, (uchar*)&buff, + sizeof(buff), MYF(0))) > 0) + { + if ((len2= my_read(fd2, (uchar*)&buff2, + sizeof(buff2), MYF(0))) < len) + { + /* File 2 was smaller */ + error= RESULT_LENGTH_MISMATCH; + break; + } + if (len2 > len) + { + /* File 1 was smaller */ + error= RESULT_LENGTH_MISMATCH; + break; + } + if ((memcmp(buff, buff2, len))) + { + /* Content of this part differed */ + error= RESULT_CONTENT_MISMATCH; + break; + } + } + if (!error && my_read(fd2, (uchar*)&buff2, + sizeof(buff2), MYF(0)) > 0) + { + /* File 1 was smaller */ + error= RESULT_LENGTH_MISMATCH; + } + + my_close(fd2, MYF(0)); + + return error; +} + + +/* + Compare two files, given their filenames + + SYNOPSIS + compare_files + filename1 - Name of first file + filename2 - Name of second file + + RETURN VALUES + See 'compare_files2' + +*/ + +int compare_files(const char* filename1, const char* filename2) +{ + File fd; + int error; + + if ((fd= my_open(filename1, O_RDONLY, MYF(0))) < 0) + die("Failed to open first file: '%s'", filename1); + + error= compare_files2(fd, filename2); + + my_close(fd, MYF(0)); + + return error; +} + + +/* + Compare content of the string in ds to content of file fname + + SYNOPSIS + dyn_string_cmp + ds - Dynamic string containing the string o be compared + fname - Name of file to compare with + + RETURN VALUES + See 'compare_files2' +*/ + +int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) +{ + int error; + File fd; + char temp_file_path[FN_REFLEN]; + + DBUG_ENTER("dyn_string_cmp"); + DBUG_PRINT("enter", ("fname: %s", fname)); + + if ((fd= create_temp_file(temp_file_path, TMPDIR, + "tmp", O_CREAT | O_SHARE | O_RDWR, + MYF(MY_WME))) < 0) + die("Failed to create temporary file for ds"); + + /* Write ds to temporary file and set file pos to beginning*/ + if (my_write(fd, (uchar *) ds->str, ds->length, + MYF(MY_FNABP | MY_WME)) || + my_seek(fd, 0, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) + { + my_close(fd, MYF(0)); + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(0)); + die("Failed to write file '%s'", temp_file_path); + } + + error= compare_files2(fd, fname); + + my_close(fd, MYF(0)); + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(0)); + + DBUG_RETURN(error); +} + + +/* + Check the content of log against result file + + SYNOPSIS + check_result + + RETURN VALUES + error - the function will not return + +*/ + +void check_result() +{ + const char* mess= "Result content mismatch\n"; + + DBUG_ENTER("check_result"); + DBUG_ASSERT(result_file_name); + DBUG_PRINT("enter", ("result_file_name: %s", result_file_name)); + + switch (compare_files(log_file.file_name(), result_file_name)) { + case RESULT_OK: + break; /* ok */ + case RESULT_LENGTH_MISMATCH: + mess= "Result length mismatch\n"; + /* Fallthrough */ + case RESULT_CONTENT_MISMATCH: + { + /* + Result mismatched, dump results to .reject file + and then show the diff + */ + char reject_file[FN_REFLEN]; + size_t reject_length; + dirname_part(reject_file, result_file_name, &reject_length); + + if (access(reject_file, W_OK) == 0) + { + /* Result file directory is writable, save reject file there */ + fn_format(reject_file, result_file_name, NULL, + ".reject", MY_REPLACE_EXT); + } + else + { + /* Put reject file in opt_logdir */ + fn_format(reject_file, result_file_name, opt_logdir, + ".reject", MY_REPLACE_DIR | MY_REPLACE_EXT); + } + + if (my_copy(log_file.file_name(), reject_file, MYF(0)) != 0) + die("Failed to copy '%s' to '%s', errno: %d", + log_file.file_name(), reject_file, errno); + + show_diff(NULL, result_file_name, reject_file); + die(mess); + break; + } + default: /* impossible */ + die("Unknown error code from dyn_string_cmp()"); + } + + DBUG_VOID_RETURN; +} + + +/* + Check the content of ds against a require file + If match fails, abort the test with special error code + indicating that test is not supported + + SYNOPSIS + check_require + ds - content to be checked + fname - name of file to check against + + RETURN VALUES + error - the function will not return + +*/ + +void check_require(DYNAMIC_STRING* ds, const char *fname) +{ + DBUG_ENTER("check_require"); + + if (dyn_string_cmp(ds, fname)) + { + char reason[FN_REFLEN]; + fn_format(reason, fname, "", "", MY_REPLACE_EXT | MY_REPLACE_DIR); + abort_not_supported_test("Test requires: '%s'", reason); + } + DBUG_VOID_RETURN; +} + + +/* + Remove surrounding chars from string + + Return 1 if first character is found but not last +*/ +static int strip_surrounding(char* str, char c1, char c2) +{ + char* ptr= str; + + /* Check if the first non space character is c1 */ + while(*ptr && my_isspace(charset_info, *ptr)) + ptr++; + if (*ptr == c1) + { + /* Replace it with a space */ + *ptr= ' '; + + /* Last non space charecter should be c2 */ + ptr= strend(str)-1; + while(*ptr && my_isspace(charset_info, *ptr)) + ptr--; + if (*ptr == c2) + { + /* Replace it with \0 */ + *ptr= 0; + } + else + { + /* Mismatch detected */ + return 1; + } + } + return 0; +} + + +static void strip_parentheses(struct st_command *command) +{ + if (strip_surrounding(command->first_argument, '(', ')')) + die("%.*s - argument list started with '%c' must be ended with '%c'", + command->first_word_len, command->query, '(', ')'); +} + + +static uchar *get_var_key(const uchar* var, size_t *len, + my_bool __attribute__((unused)) t) +{ + register char* key; + key = ((VAR*)var)->name; + *len = ((VAR*)var)->name_len; + return (uchar*)key; +} + + +VAR *var_init(VAR *v, const char *name, int name_len, const char *val, + int val_len) +{ + int val_alloc_len; + VAR *tmp_var; + if (!name_len && name) + name_len = strlen(name); + if (!val_len && val) + val_len = strlen(val) ; + val_alloc_len = val_len + 16; /* room to grow */ + if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(sizeof(*tmp_var) + + name_len+1, MYF(MY_WME)))) + die("Out of memory"); + + tmp_var->name = (name) ? (char*) tmp_var + sizeof(*tmp_var) : 0; + tmp_var->alloced = (v == 0); + + if (!(tmp_var->str_val = (char*)my_malloc(val_alloc_len+1, MYF(MY_WME)))) + die("Out of memory"); + + memcpy(tmp_var->name, name, name_len); + if (val) + { + memcpy(tmp_var->str_val, val, val_len); + tmp_var->str_val[val_len]= 0; + } + tmp_var->name_len = name_len; + tmp_var->str_val_len = val_len; + tmp_var->alloced_len = val_alloc_len; + tmp_var->int_val = (val) ? atoi(val) : 0; + tmp_var->int_dirty = 0; + tmp_var->env_s = 0; + return tmp_var; +} + + +void var_free(void *v) +{ + my_free(((VAR*) v)->str_val, MYF(MY_WME)); + if (((VAR*)v)->alloced) + my_free(v, MYF(MY_WME)); +} + + +VAR* var_from_env(const char *name, const char *def_val) +{ + const char *tmp; + VAR *v; + if (!(tmp = getenv(name))) + tmp = def_val; + + v = var_init(0, name, strlen(name), tmp, strlen(tmp)); + my_hash_insert(&var_hash, (uchar*)v); + return v; +} + + +VAR* var_get(const char *var_name, const char **var_name_end, my_bool raw, + my_bool ignore_not_existing) +{ + int digit; + VAR *v; + DBUG_ENTER("var_get"); + DBUG_PRINT("enter", ("var_name: %s",var_name)); + + if (*var_name != '$') + goto err; + digit = *++var_name - '0'; + if (digit < 0 || digit >= 10) + { + const char *save_var_name = var_name, *end; + uint length; + end = (var_name_end) ? *var_name_end : 0; + while (my_isvar(charset_info,*var_name) && var_name != end) + var_name++; + if (var_name == save_var_name) + { + if (ignore_not_existing) + DBUG_RETURN(0); + die("Empty variable"); + } + length= (uint) (var_name - save_var_name); + if (length >= MAX_VAR_NAME_LENGTH) + die("Too long variable name: %s", save_var_name); + + if (!(v = (VAR*) hash_search(&var_hash, (const uchar*) save_var_name, + length))) + { + char buff[MAX_VAR_NAME_LENGTH+1]; + strmake(buff, save_var_name, length); + v= var_from_env(buff, ""); + } + var_name--; /* Point at last character */ + } + else + v = var_reg + digit; + + if (!raw && v->int_dirty) + { + sprintf(v->str_val, "%d", v->int_val); + v->int_dirty = 0; + v->str_val_len = strlen(v->str_val); + } + if (var_name_end) + *var_name_end = var_name ; + DBUG_RETURN(v); +err: + if (var_name_end) + *var_name_end = 0; + die("Unsupported variable name: %s", var_name); + DBUG_RETURN(0); +} + + +VAR *var_obtain(const char *name, int len) +{ + VAR* v; + if ((v = (VAR*)hash_search(&var_hash, (const uchar *) name, len))) + return v; + v = var_init(0, name, len, "", 0); + my_hash_insert(&var_hash, (uchar*)v); + return v; +} + + +/* + - if variable starts with a $ it is regarded as a local test varable + - if not it is treated as a environment variable, and the corresponding + environment variable will be updated +*/ + +void var_set(const char *var_name, const char *var_name_end, + const char *var_val, const char *var_val_end) +{ + int digit, env_var= 0; + VAR *v; + DBUG_ENTER("var_set"); + DBUG_PRINT("enter", ("var_name: '%.*s' = '%.*s' (length: %d)", + (int) (var_name_end - var_name), var_name, + (int) (var_val_end - var_val), var_val, + (int) (var_val_end - var_val))); + + if (*var_name != '$') + env_var= 1; + else + var_name++; + + digit= *var_name - '0'; + if (!(digit < 10 && digit >= 0)) + { + v= var_obtain(var_name, (uint) (var_name_end - var_name)); + } + else + v= var_reg + digit; + + eval_expr(v, var_val, (const char**) &var_val_end); + + if (env_var) + { + char buf[1024], *old_env_s= v->env_s; + if (v->int_dirty) + { + sprintf(v->str_val, "%d", v->int_val); + v->int_dirty= 0; + v->str_val_len= strlen(v->str_val); + } + my_snprintf(buf, sizeof(buf), "%.*s=%.*s", + v->name_len, v->name, + v->str_val_len, v->str_val); + if (!(v->env_s= my_strdup(buf, MYF(MY_WME)))) + die("Out of memory"); + putenv(v->env_s); + my_free(old_env_s, MYF(MY_ALLOW_ZERO_PTR)); + } + DBUG_VOID_RETURN; +} + + +void var_set_string(const char* name, const char* value) +{ + var_set(name, name + strlen(name), value, value + strlen(value)); +} + + +void var_set_int(const char* name, int value) +{ + char buf[21]; + my_snprintf(buf, sizeof(buf), "%d", value); + var_set_string(name, buf); +} + + +/* + Store an integer (typically the returncode of the last SQL) + statement in the mysqltest builtin variable $mysql_errno +*/ + +void var_set_errno(int sql_errno) +{ + var_set_int("$mysql_errno", sql_errno); +} + + +/* + Update $mysql_get_server_version variable with version + of the currently connected server +*/ + +void var_set_mysql_get_server_version(MYSQL* mysql) +{ + var_set_int("$mysql_get_server_version", mysql_get_server_version(mysql)); +} + + +/* + Set variable from the result of a query + + SYNOPSIS + var_query_set() + var variable to set from query + query start of query string to execute + query_end end of the query string to execute + + + DESCRIPTION + let @ = `` + + Execute the query and assign the first row of result to var as + a tab separated strings + + Also assign each column of the result set to + variable "$_" + Thus the tab separated output can be read from $ and + and each individual column can be read as $_ + +*/ + +void var_query_set(VAR *var, const char *query, const char** query_end) +{ + char *end = (char*)((query_end && *query_end) ? + *query_end : query + strlen(query)); + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL* mysql = &cur_con->mysql; + DYNAMIC_STRING ds_query; + DBUG_ENTER("var_query_set"); + LINT_INIT(res); + + while (end > query && *end != '`') + --end; + if (query == end) + die("Syntax error in query, missing '`'"); + ++query; + + /* Eval the query, thus replacing all environment variables */ + init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); + do_eval(&ds_query, query, end, FALSE); + + if (mysql_real_query(mysql, ds_query.str, ds_query.length)) + die("Error running query '%s': %d %s", ds_query.str, + mysql_errno(mysql), mysql_error(mysql)); + if (!(res= mysql_store_result(mysql))) + die("Query '%s' didn't return a result set", ds_query.str); + dynstr_free(&ds_query); + + if ((row= mysql_fetch_row(res)) && row[0]) + { + /* + Concatenate all fields in the first row with tab in between + and assign that string to the $variable + */ + DYNAMIC_STRING result; + uint i; + ulong *lengths; + + init_dynamic_string(&result, "", 512, 512); + lengths= mysql_fetch_lengths(res); + for (i= 0; i < mysql_num_fields(res); i++) + { + if (row[i]) + { + /* Add column to tab separated string */ + dynstr_append_mem(&result, row[i], lengths[i]); + } + dynstr_append_mem(&result, "\t", 1); + } + end= result.str + result.length-1; + eval_expr(var, result.str, (const char**) &end); + dynstr_free(&result); + } + else + eval_expr(var, "", 0); + + mysql_free_result(res); + DBUG_VOID_RETURN; +} + + +/* + Set variable from the result of a field in a query + + This function is useful when checking for a certain value + in the output from a query that can't be restricted to only + return some values. A very good example of that is most SHOW + commands. + + SYNOPSIS + var_set_query_get_value() + + DESCRIPTION + let $variable= query_get_value(,,); + + - The query that should be sent to the server + - Name of the column that holds the field be compared + against the expected value + - Number of the row that holds the field to be + compared against the expected value + +*/ + +void var_set_query_get_value(struct st_command *command, VAR *var) +{ + long row_no; + int col_no= -1; + MYSQL_RES* res; + MYSQL* mysql= &cur_con->mysql; + + static DYNAMIC_STRING ds_query; + static DYNAMIC_STRING ds_col; + static DYNAMIC_STRING ds_row; + const struct command_arg query_get_value_args[] = { + {"query", ARG_STRING, TRUE, &ds_query, "Query to run"}, + {"column name", ARG_STRING, TRUE, &ds_col, "Name of column"}, + {"row number", ARG_STRING, TRUE, &ds_row, "Number for row"} + }; + + DBUG_ENTER("var_set_query_get_value"); + LINT_INIT(res); + + strip_parentheses(command); + DBUG_PRINT("info", ("query: %s", command->query)); + check_command_args(command, command->first_argument, query_get_value_args, + sizeof(query_get_value_args)/sizeof(struct command_arg), + ','); + + DBUG_PRINT("info", ("query: %s", ds_query.str)); + DBUG_PRINT("info", ("col: %s", ds_col.str)); + + /* Convert row number to int */ + if (!str2int(ds_row.str, 10, (long) 0, (long) INT_MAX, &row_no)) + die("Invalid row number: '%s'", ds_row.str); + DBUG_PRINT("info", ("row: %s, row_no: %ld", ds_row.str, row_no)); + dynstr_free(&ds_row); + + /* Remove any surrounding "'s from the query - if there is any */ + if (strip_surrounding(ds_query.str, '"', '"')) + die("Mismatched \"'s around query '%s'", ds_query.str); + + /* Run the query */ + if (mysql_real_query(mysql, ds_query.str, ds_query.length)) + die("Error running query '%s': %d %s", ds_query.str, + mysql_errno(mysql), mysql_error(mysql)); + if (!(res= mysql_store_result(mysql))) + die("Query '%s' didn't return a result set", ds_query.str); + + { + /* Find column number from the given column name */ + uint i; + uint num_fields= mysql_num_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); + + for (i= 0; i < num_fields; i++) + { + if (strcmp(fields[i].name, ds_col.str) == 0 && + strlen(fields[i].name) == ds_col.length) + { + col_no= i; + break; + } + } + if (col_no == -1) + { + mysql_free_result(res); + die("Could not find column '%s' in the result of '%s'", + ds_col.str, ds_query.str); + } + DBUG_PRINT("info", ("Found column %d with name '%s'", + i, fields[i].name)); + } + dynstr_free(&ds_col); + + { + /* Get the value */ + MYSQL_ROW row; + long rows= 0; + const char* value= "No such row"; + + while ((row= mysql_fetch_row(res))) + { + if (++rows == row_no) + { + + DBUG_PRINT("info", ("At row %ld, column %d is '%s'", + row_no, col_no, row[col_no])); + /* Found the row to get */ + if (row[col_no]) + value= row[col_no]; + else + value= "NULL"; + + break; + } + } + eval_expr(var, value, 0); + } + dynstr_free(&ds_query); + mysql_free_result(res); + + DBUG_VOID_RETURN; +} + + +void var_copy(VAR *dest, VAR *src) +{ + dest->int_val= src->int_val; + dest->int_dirty= src->int_dirty; + + /* Alloc/realloc data for str_val in dest */ + if (dest->alloced_len < src->alloced_len && + !(dest->str_val= dest->str_val + ? (char*)my_realloc(dest->str_val, src->alloced_len, MYF(MY_WME)) + : (char*)my_malloc(src->alloced_len, MYF(MY_WME)))) + die("Out of memory"); + else + dest->alloced_len= src->alloced_len; + + /* Copy str_val data to dest */ + dest->str_val_len= src->str_val_len; + if (src->str_val_len) + memcpy(dest->str_val, src->str_val, src->str_val_len); +} + + +void eval_expr(VAR *v, const char *p, const char **p_end) +{ + + DBUG_ENTER("eval_expr"); + DBUG_PRINT("enter", ("p: '%s'", p)); + + if (*p == '$') + { + VAR *vp; + if ((vp= var_get(p, p_end, 0, 0))) + var_copy(v, vp); + DBUG_VOID_RETURN; + } + + if (*p == '`') + { + var_query_set(v, p, p_end); + DBUG_VOID_RETURN; + } + + { + /* Check if this is a "let $var= query_get_value()" */ + const char* get_value_str= "query_get_value"; + const size_t len= strlen(get_value_str); + if (strncmp(p, get_value_str, len)==0) + { + struct st_command command; + memset(&command, 0, sizeof(command)); + command.query= (char*)p; + command.first_word_len= len; + command.first_argument= command.query + len; + command.end= (char*)*p_end; + var_set_query_get_value(&command, v); + DBUG_VOID_RETURN; + } + } + + { + int new_val_len = (p_end && *p_end) ? + (int) (*p_end - p) : (int) strlen(p); + if (new_val_len + 1 >= v->alloced_len) + { + static int MIN_VAR_ALLOC= 32; + v->alloced_len = (new_val_len < MIN_VAR_ALLOC - 1) ? + MIN_VAR_ALLOC : new_val_len + 1; + if (!(v->str_val = + v->str_val ? + (char*)my_realloc(v->str_val, v->alloced_len+1, MYF(MY_WME)) : + (char*)my_malloc(v->alloced_len+1, MYF(MY_WME)))) + die("Out of memory"); + } + v->str_val_len = new_val_len; + memcpy(v->str_val, p, new_val_len); + v->str_val[new_val_len] = 0; + v->int_val=atoi(p); + DBUG_PRINT("info", ("atoi on '%s', returns: %d", p, v->int_val)); + v->int_dirty=0; + } + DBUG_VOID_RETURN; +} + + +int open_file(const char *name) +{ + char buff[FN_REFLEN]; + size_t length; + DBUG_ENTER("open_file"); + DBUG_PRINT("enter", ("name: %s", name)); + + /* Extract path from current file and try it as base first */ + if (dirname_part(buff, cur_file->file_name, &length)) + { + strxmov(buff, buff, name, NullS); + if (access(buff, F_OK) == 0){ + DBUG_PRINT("info", ("The file exists")); + name= buff; + } + } + if (!test_if_hard_path(name)) + { + strxmov(buff, opt_basedir, name, NullS); + name=buff; + } + fn_format(buff, name, "", "", MY_UNPACK_FILENAME); + + if (cur_file == file_stack_end) + die("Source directives are nesting too deep"); + cur_file++; + if (!(cur_file->file = my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) + { + cur_file--; + die("Could not open '%s' for reading, errno: %d", buff, errno); + } + cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); + cur_file->lineno=1; + DBUG_RETURN(0); +} + + +/* + Source and execute the given file + + SYNOPSIS + do_source() + query called command + + DESCRIPTION + source + + Open the file and execute it + +*/ + +void do_source(struct st_command *command) +{ + static DYNAMIC_STRING ds_filename; + const struct command_arg source_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to source" } + }; + DBUG_ENTER("do_source"); + + check_command_args(command, command->first_argument, source_args, + sizeof(source_args)/sizeof(struct command_arg), + ' '); + + /* + If this file has already been sourced, don't source it again. + It's already available in the q_lines cache. + */ + if (parser.current_line < (parser.read_lines - 1)) + ; /* Do nothing */ + else + { + DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str)); + open_file(ds_filename.str); + } + + dynstr_free(&ds_filename); + return; +} + + +#if defined __WIN__ + +#ifdef USE_CYGWIN +/* Variables used for temporary sh files used for emulating Unix on Windows */ +char tmp_sh_name[64], tmp_sh_cmd[70]; +#endif + +void init_tmp_sh_file() +{ +#ifdef USE_CYGWIN + /* Format a name for the tmp sh file that is unique for this process */ + my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); + /* Format the command to execute in order to run the script */ + my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); +#endif +} + + +void free_tmp_sh_file() +{ +#ifdef USE_CYGWIN + my_delete(tmp_sh_name, MYF(0)); +#endif +} +#endif + + +FILE* my_popen(DYNAMIC_STRING *ds_cmd, const char *mode) +{ +#if defined __WIN__ && defined USE_CYGWIN + /* Dump the command into a sh script file and execute with popen */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return popen(tmp_sh_cmd, mode); +#else + return popen(ds_cmd->str, mode); +#endif +} + + +static void init_builtin_echo(void) +{ +#ifdef __WIN__ + size_t echo_length; + + /* Look for "echo.exe" in same dir as mysqltest was started from */ + dirname_part(builtin_echo, my_progname, &echo_length); + fn_format(builtin_echo, ".\\echo.exe", + builtin_echo, "", MYF(MY_REPLACE_DIR)); + + /* Make sure echo.exe exists */ + if (access(builtin_echo, F_OK) != 0) + builtin_echo[0]= 0; + return; + +#else + + builtin_echo[0]= 0; + return; + +#endif +} + + +/* + Replace a substring + + SYNOPSIS + replace + ds_str The string to search and perform the replace in + search_str The string to search for + search_len Length of the string to search for + replace_str The string to replace with + replace_len Length of the string to replace with + + RETURN + 0 String replaced + 1 Could not find search_str in str +*/ + +static int replace(DYNAMIC_STRING *ds_str, + const char *search_str, ulong search_len, + const char *replace_str, ulong replace_len) +{ + DYNAMIC_STRING ds_tmp; + const char *start= strstr(ds_str->str, search_str); + if (!start) + return 1; + init_dynamic_string(&ds_tmp, "", + ds_str->length + replace_len, 256); + dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str); + dynstr_append_mem(&ds_tmp, replace_str, replace_len); + dynstr_append(&ds_tmp, start + search_len); + dynstr_set(ds_str, ds_tmp.str); + dynstr_free(&ds_tmp); + return 0; +} + + +/* + Execute given command. + + SYNOPSIS + do_exec() + query called command + + DESCRIPTION + exec + + Execute the text between exec and end of line in a subprocess. + The error code returned from the subprocess is checked against the + expected error array, previously set with the --error command. + It can thus be used to execute a command that shall fail. + + NOTE + Although mysqltest is executed from cygwin shell, the command will be + executed in "cmd.exe". Thus commands like "rm" etc can NOT be used, use + mysqltest commmand(s) like "remove_file" for that +*/ + +void do_exec(struct st_command *command) +{ + int error; + char buf[512]; + FILE *res_file; + char *cmd= command->first_argument; + DYNAMIC_STRING ds_cmd; + DBUG_ENTER("do_exec"); + DBUG_PRINT("enter", ("cmd: '%s'", cmd)); + + /* Skip leading space */ + while (*cmd && my_isspace(charset_info, *cmd)) + cmd++; + if (!*cmd) + die("Missing argument in exec"); + command->last_argument= command->end; + + init_dynamic_string(&ds_cmd, 0, command->query_len+256, 256); + /* Eval the command, thus replacing all environment variables */ + do_eval(&ds_cmd, cmd, command->end, !is_windows); + + /* Check if echo should be replaced with "builtin" echo */ + if (builtin_echo[0] && strncmp(cmd, "echo", 4) == 0) + { + /* Replace echo with our "builtin" echo */ + replace(&ds_cmd, "echo", 4, builtin_echo, strlen(builtin_echo)); + } + +#ifdef __WIN__ +#ifndef USE_CYGWIN + /* Replace /dev/null with NUL */ + while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) + ; + /* Replace "closed stdout" with non existing output fd */ + while(replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0) + ; +#endif +#endif + + DBUG_PRINT("info", ("Executing '%s' as '%s'", + command->first_argument, ds_cmd.str)); + + if (!(res_file= my_popen(&ds_cmd, "r")) && command->abort_on_error) + { + dynstr_free(&ds_cmd); + die("popen(\"%s\", \"r\") failed", command->first_argument); + } + + while (fgets(buf, sizeof(buf), res_file)) + { + if (disable_result_log) + { + buf[strlen(buf)-1]=0; + DBUG_PRINT("exec_result",("%s", buf)); + } + else + { + replace_dynstr_append(&ds_res, buf); + } + } + error= pclose(res_file); + if (error > 0) + { + uint status= WEXITSTATUS(error), i; + my_bool ok= 0; + + if (command->abort_on_error) + { + log_msg("exec of '%s' failed, error: %d, status: %d, errno: %d", + ds_cmd.str, error, status, errno); + dynstr_free(&ds_cmd); + die("command \"%s\" failed", command->first_argument); + } + + DBUG_PRINT("info", + ("error: %d, status: %d", error, status)); + for (i= 0; i < command->expected_errors.count; i++) + { + DBUG_PRINT("info", ("expected error: %d", + command->expected_errors.err[i].code.errnum)); + if ((command->expected_errors.err[i].type == ERR_ERRNO) && + (command->expected_errors.err[i].code.errnum == status)) + { + ok= 1; + DBUG_PRINT("info", ("command \"%s\" failed with expected error: %d", + command->first_argument, status)); + } + } + if (!ok) + { + dynstr_free(&ds_cmd); + die("command \"%s\" failed with wrong error: %d", + command->first_argument, status); + } + } + else if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + log_msg("exec of '%s failed, error: %d, errno: %d", + ds_cmd.str, error, errno); + dynstr_free(&ds_cmd); + die("command \"%s\" succeeded - should have failed with errno %d...", + command->first_argument, command->expected_errors.err[0].code.errnum); + } + + dynstr_free(&ds_cmd); + DBUG_VOID_RETURN; +} + +enum enum_operator +{ + DO_DEC, + DO_INC +}; + + +/* + Decrease or increase the value of a variable + + SYNOPSIS + do_modify_var() + query called command + op operation to perform on the var + + DESCRIPTION + dec $var_name + inc $var_name + +*/ + +int do_modify_var(struct st_command *command, + enum enum_operator op) +{ + const char *p= command->first_argument; + VAR* v; + if (!*p) + die("Missing argument to %.*s", command->first_word_len, command->query); + if (*p != '$') + die("The argument to %.*s must be a variable (start with $)", + command->first_word_len, command->query); + v= var_get(p, &p, 1, 0); + switch (op) { + case DO_DEC: + v->int_val--; + break; + case DO_INC: + v->int_val++; + break; + default: + die("Invalid operator to do_modify_var"); + break; + } + v->int_dirty= 1; + command->last_argument= (char*)++p; + return 0; +} + + +/* + Wrapper for 'system' function + + NOTE + If mysqltest is executed from cygwin shell, the command will be + executed in the "windows command interpreter" cmd.exe and we prepend "sh" + to make it be executed by cygwins "bash". Thus commands like "rm", + "mkdir" as well as shellscripts can executed by "system" in Windows. + +*/ + +int my_system(DYNAMIC_STRING* ds_cmd) +{ +#if defined __WIN__ && defined USE_CYGWIN + /* Dump the command into a sh script file and execute with system */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return system(tmp_sh_cmd); +#else + return system(ds_cmd->str); +#endif +} + + +/* + SYNOPSIS + do_system + command called command + + DESCRIPTION + system + + Eval the query to expand any $variables in the command. + Execute the command with the "system" command. + +*/ + +void do_system(struct st_command *command) +{ + DYNAMIC_STRING ds_cmd; + DBUG_ENTER("do_system"); + + if (strlen(command->first_argument) == 0) + die("Missing arguments to system, nothing to do!"); + + init_dynamic_string(&ds_cmd, 0, command->query_len + 64, 256); + + /* Eval the system command, thus replacing all environment variables */ + do_eval(&ds_cmd, command->first_argument, command->end, !is_windows); + +#ifdef __WIN__ +#ifndef USE_CYGWIN + /* Replace /dev/null with NUL */ + while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) + ; +#endif +#endif + + + DBUG_PRINT("info", ("running system command '%s' as '%s'", + command->first_argument, ds_cmd.str)); + if (my_system(&ds_cmd)) + { + if (command->abort_on_error) + die("system command '%s' failed", command->first_argument); + + /* If ! abort_on_error, log message and continue */ + dynstr_append(&ds_res, "system command '"); + replace_dynstr_append(&ds_res, command->first_argument); + dynstr_append(&ds_res, "' failed\n"); + } + + command->last_argument= command->end; + dynstr_free(&ds_cmd); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_remove_file + command called command + + DESCRIPTION + remove_file + Remove the file +*/ + +void do_remove_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_filename; + const struct command_arg rm_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to delete" } + }; + DBUG_ENTER("do_remove_file"); + + check_command_args(command, command->first_argument, + rm_args, sizeof(rm_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("removing file: %s", ds_filename.str)); + error= my_delete(ds_filename.str, MYF(0)) != 0; + handle_command_error(command, error); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_copy_file + command command handle + + DESCRIPTION + copy_file + Copy to + + NOTE! Will fail if exists +*/ + +void do_copy_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_from_file; + static DYNAMIC_STRING ds_to_file; + const struct command_arg copy_file_args[] = { + { "from_file", ARG_STRING, TRUE, &ds_from_file, "Filename to copy from" }, + { "to_file", ARG_STRING, TRUE, &ds_to_file, "Filename to copy to" } + }; + DBUG_ENTER("do_copy_file"); + + check_command_args(command, command->first_argument, + copy_file_args, + sizeof(copy_file_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str)); + error= (my_copy(ds_from_file.str, ds_to_file.str, + MYF(MY_DONT_OVERWRITE_FILE)) != 0); + handle_command_error(command, error); + dynstr_free(&ds_from_file); + dynstr_free(&ds_to_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_chmod_file + command command handle + + DESCRIPTION + chmod + Change file permission of + +*/ + +void do_chmod_file(struct st_command *command) +{ + long mode= 0; + static DYNAMIC_STRING ds_mode; + static DYNAMIC_STRING ds_file; + const struct command_arg chmod_file_args[] = { + { "mode", ARG_STRING, TRUE, &ds_mode, "Mode of file(octal) ex. 0660"}, + { "filename", ARG_STRING, TRUE, &ds_file, "Filename of file to modify" } + }; + DBUG_ENTER("do_chmod_file"); + + check_command_args(command, command->first_argument, + chmod_file_args, + sizeof(chmod_file_args)/sizeof(struct command_arg), + ' '); + + /* Parse what mode to set */ + if (ds_mode.length != 4 || + str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS) + die("You must write a 4 digit octal number for mode"); + + DBUG_PRINT("info", ("chmod %o %s", (uint)mode, ds_file.str)); + handle_command_error(command, chmod(ds_file.str, mode)); + dynstr_free(&ds_mode); + dynstr_free(&ds_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_file_exists + command called command + + DESCRIPTION + fiile_exist + Check if file exists +*/ + +void do_file_exist(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_filename; + const struct command_arg file_exist_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to check if it exist" } + }; + DBUG_ENTER("do_file_exist"); + + check_command_args(command, command->first_argument, + file_exist_args, + sizeof(file_exist_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str)); + error= (access(ds_filename.str, F_OK) != 0); + handle_command_error(command, error); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_mkdir + command called command + + DESCRIPTION + mkdir + Create the directory +*/ + +void do_mkdir(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_dirname; + const struct command_arg mkdir_args[] = { + {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to create"} + }; + DBUG_ENTER("do_mkdir"); + + check_command_args(command, command->first_argument, + mkdir_args, sizeof(mkdir_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("creating directory: %s", ds_dirname.str)); + error= my_mkdir(ds_dirname.str, 0777, MYF(0)) != 0; + handle_command_error(command, error); + dynstr_free(&ds_dirname); + DBUG_VOID_RETURN; +} + +/* + SYNOPSIS + do_rmdir + command called command + + DESCRIPTION + rmdir + Remove the empty directory +*/ + +void do_rmdir(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_dirname; + const struct command_arg rmdir_args[] = { + {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to remove"} + }; + DBUG_ENTER("do_rmdir"); + + check_command_args(command, command->first_argument, + rmdir_args, sizeof(rmdir_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("removing directory: %s", ds_dirname.str)); + error= rmdir(ds_dirname.str) != 0; + handle_command_error(command, error); + dynstr_free(&ds_dirname); + DBUG_VOID_RETURN; +} + + +/* + Read characters from line buffer or file. This is needed to allow + my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file + + NOTE: + This works as long as one doesn't change files (with 'source file_name') + when there is things pushed into the buffer. This should however not + happen for any tests in the test suite. +*/ + +int my_getc(FILE *file) +{ + if (line_buffer_pos == line_buffer) + return fgetc(file); + return *--line_buffer_pos; +} + + +void my_ungetc(int c) +{ + *line_buffer_pos++= (char) c; +} + + +void read_until_delimiter(DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_delimiter) +{ + char c; + DBUG_ENTER("read_until_delimiter"); + DBUG_PRINT("enter", ("delimiter: %s, length: %u", + ds_delimiter->str, (uint) ds_delimiter->length)); + + if (ds_delimiter->length > MAX_DELIMITER_LENGTH) + die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH); + + /* Read from file until delimiter is found */ + while (1) + { + c= my_getc(cur_file->file); + + if (c == '\n') + { + cur_file->lineno++; + + /* Skip newline from the same line as the command */ + if (start_lineno == (cur_file->lineno - 1)) + continue; + } + else if (start_lineno == cur_file->lineno) + { + /* + No characters except \n are allowed on + the same line as the command + */ + die("Trailing characters found after command"); + } + + if (feof(cur_file->file)) + die("End of file encountered before '%s' delimiter was found", + ds_delimiter->str); + + if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) + { + DBUG_PRINT("exit", ("Found delimiter '%s'", ds_delimiter->str)); + break; + } + dynstr_append_mem(ds, (const char*)&c, 1); + } + DBUG_PRINT("exit", ("ds: %s", ds->str)); + DBUG_VOID_RETURN; +} + + +void do_write_file_command(struct st_command *command, my_bool append) +{ + static DYNAMIC_STRING ds_content; + static DYNAMIC_STRING ds_filename; + static DYNAMIC_STRING ds_delimiter; + const struct command_arg write_file_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to write to" }, + { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } + }; + DBUG_ENTER("do_write_file"); + + check_command_args(command, + command->first_argument, + write_file_args, + sizeof(write_file_args)/sizeof(struct command_arg), + ' '); + + /* If no delimiter was provided, use EOF */ + if (ds_delimiter.length == 0) + dynstr_set(&ds_delimiter, "EOF"); + + if (!append && access(ds_filename.str, F_OK) == 0) + { + /* The file should not be overwritten */ + die("File already exist: '%s'", ds_filename.str); + } + + init_dynamic_string(&ds_content, "", 1024, 1024); + read_until_delimiter(&ds_content, &ds_delimiter); + DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str)); + str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); + dynstr_free(&ds_content); + dynstr_free(&ds_filename); + dynstr_free(&ds_delimiter); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_write_file + command called command + + DESCRIPTION + write_file []; + + <...> + < what to write line n> + EOF + + --write_file ; + + <...> + < what to write line n> + EOF + + Write everything between the "write_file" command and 'delimiter' + to "file_name" + + NOTE! Will fail if exists + + Default is EOF + +*/ + +void do_write_file(struct st_command *command) +{ + do_write_file_command(command, FALSE); +} + + +/* + SYNOPSIS + do_append_file + command called command + + DESCRIPTION + append_file []; + + <...> + < what to write line n> + EOF + + --append_file ; + + <...> + < what to write line n> + EOF + + Append everything between the "append_file" command + and 'delimiter' to "file_name" + + Default is EOF + +*/ + +void do_append_file(struct st_command *command) +{ + do_write_file_command(command, TRUE); +} + + +/* + SYNOPSIS + do_cat_file + command called command + + DESCRIPTION + cat_file ; + + Print the given file to result log + +*/ + +void do_cat_file(struct st_command *command) +{ + static DYNAMIC_STRING ds_filename; + const struct command_arg cat_file_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to read from" } + }; + DBUG_ENTER("do_cat_file"); + + check_command_args(command, + command->first_argument, + cat_file_args, + sizeof(cat_file_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("Reading from, file: %s", ds_filename.str)); + + cat_file(&ds_res, ds_filename.str); + + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_diff_files + command called command + + DESCRIPTION + diff_files ; + + Fails if the two files differ. + +*/ + +void do_diff_files(struct st_command *command) +{ + int error= 0; + static DYNAMIC_STRING ds_filename; + static DYNAMIC_STRING ds_filename2; + const struct command_arg diff_file_args[] = { + { "file1", ARG_STRING, TRUE, &ds_filename, "First file to diff" }, + { "file2", ARG_STRING, TRUE, &ds_filename2, "Second file to diff" } + }; + DBUG_ENTER("do_diff_files"); + + check_command_args(command, + command->first_argument, + diff_file_args, + sizeof(diff_file_args)/sizeof(struct command_arg), + ' '); + + if ((error= compare_files(ds_filename.str, ds_filename2.str))) + { + /* Compare of the two files failed, append them to output + so the failure can be analyzed + */ + show_diff(&ds_res, ds_filename.str, ds_filename2.str); + } + + dynstr_free(&ds_filename); + dynstr_free(&ds_filename2); + handle_command_error(command, error); + DBUG_VOID_RETURN; +} + + +struct st_connection * find_connection_by_name(const char *name) +{ + struct st_connection *con; + for (con= connections; con < next_con; con++) + { + if (!strcmp(con->name, name)) + { + return con; + } + } + return 0; /* Connection not found */ +} + + +/* + SYNOPSIS + do_send_quit + command called command + + DESCRIPTION + Sends a simple quit command to the server for the named connection. + +*/ + +void do_send_quit(struct st_command *command) +{ + char *p= command->first_argument, *name; + struct st_connection *con; + + DBUG_ENTER("do_send_quit"); + DBUG_PRINT("enter",("name: '%s'",p)); + + if (!*p) + die("Missing connection name in send_quit"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + + if (*p) + *p++= 0; + command->last_argument= p; + + if (!(con= find_connection_by_name(name))) + die("connection '%s' not found in connection pool", name); + + simple_command(&con->mysql,COM_QUIT,0,0,1); + + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_change_user + command called command + + DESCRIPTION + change_user [], [], [] + - user to change to + - user password + - default database + + Changes the user and causes the database specified by db to become + the default (current) database for the the current connection. + +*/ + +void do_change_user(struct st_command *command) +{ + MYSQL *mysql = &cur_con->mysql; + /* static keyword to make the NetWare compiler happy. */ + static DYNAMIC_STRING ds_user, ds_passwd, ds_db; + const struct command_arg change_user_args[] = { + { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, + { "password", ARG_STRING, FALSE, &ds_passwd, "Password used when connecting" }, + { "database", ARG_STRING, FALSE, &ds_db, "Database to select after connect" }, + }; + + DBUG_ENTER("do_change_user"); + + check_command_args(command, command->first_argument, + change_user_args, + sizeof(change_user_args)/sizeof(struct command_arg), + ','); + + if (cur_con->stmt) + { + mysql_stmt_close(cur_con->stmt); + cur_con->stmt= NULL; + } + + if (!ds_user.length) + dynstr_set(&ds_user, mysql->user); + + if (!ds_passwd.length) + dynstr_set(&ds_passwd, mysql->passwd); + + if (!ds_db.length) + dynstr_set(&ds_db, mysql->db); + + DBUG_PRINT("info",("connection: '%s' user: '%s' password: '%s' database: '%s'", + cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); + + if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) + die("change user failed: %s", mysql_error(mysql)); + + dynstr_free(&ds_user); + dynstr_free(&ds_passwd); + dynstr_free(&ds_db); + + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_perl + command command handle + + DESCRIPTION + perl []; + + <...> + + EOF + + Execute everything after "perl" until as perl. + Useful for doing more advanced things + but still being able to execute it on all platforms. + + Default is EOF +*/ + +void do_perl(struct st_command *command) +{ + int error; + File fd; + FILE *res_file; + char buf[FN_REFLEN]; + char temp_file_path[FN_REFLEN]; + static DYNAMIC_STRING ds_script; + static DYNAMIC_STRING ds_delimiter; + const struct command_arg perl_args[] = { + { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } + }; + DBUG_ENTER("do_perl"); + + check_command_args(command, + command->first_argument, + perl_args, + sizeof(perl_args)/sizeof(struct command_arg), + ' '); + + /* If no delimiter was provided, use EOF */ + if (ds_delimiter.length == 0) + dynstr_set(&ds_delimiter, "EOF"); + + init_dynamic_string(&ds_script, "", 1024, 1024); + read_until_delimiter(&ds_script, &ds_delimiter); + + DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); + + /* Create temporary file name */ + if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), + "tmp", O_CREAT | O_SHARE | O_RDWR, + MYF(MY_WME))) < 0) + die("Failed to create temporary file for perl command"); + my_close(fd, MYF(0)); + + str_to_file(temp_file_path, ds_script.str, ds_script.length); + + /* Format the "perl " command */ + my_snprintf(buf, sizeof(buf), "perl %s", temp_file_path); + + if (!(res_file= popen(buf, "r")) && command->abort_on_error) + die("popen(\"%s\", \"r\") failed", buf); + + while (fgets(buf, sizeof(buf), res_file)) + { + if (disable_result_log) + { + buf[strlen(buf)-1]=0; + DBUG_PRINT("exec_result",("%s", buf)); + } + else + { + replace_dynstr_append(&ds_res, buf); + } + } + error= pclose(res_file); + + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(0)); + + handle_command_error(command, WEXITSTATUS(error)); + dynstr_free(&ds_script); + dynstr_free(&ds_delimiter); + DBUG_VOID_RETURN; +} + + +/* + Print the content between echo and to result file. + Evaluate all variables in the string before printing, allow + for variable names to be escaped using \ + + SYNOPSIS + do_echo() + command called command + + DESCRIPTION + echo text + Print the text after echo until end of command to result file + + echo $ + Print the content of the variable to result file + + echo Some text $ + Print "Some text" plus the content of the variable to + result file + + echo Some text \$ + Print "Some text" plus $ to result file +*/ + +int do_echo(struct st_command *command) +{ + DYNAMIC_STRING ds_echo; + DBUG_ENTER("do_echo"); + + init_dynamic_string(&ds_echo, "", command->query_len, 256); + do_eval(&ds_echo, command->first_argument, command->end, FALSE); + dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length); + dynstr_append_mem(&ds_res, "\n", 1); + dynstr_free(&ds_echo); + command->last_argument= command->end; + DBUG_RETURN(0); +} + + +void do_wait_for_slave_to_stop(struct st_command *c __attribute__((unused))) +{ + static int SLAVE_POLL_INTERVAL= 300000; + MYSQL* mysql = &cur_con->mysql; + for (;;) + { + MYSQL_RES *res; + MYSQL_ROW row; + int done; + LINT_INIT(res); + + if (mysql_query(mysql,"show status like 'Slave_running'") || + !(res=mysql_store_result(mysql))) + die("Query failed while probing slave for stop: %s", + mysql_error(mysql)); + if (!(row=mysql_fetch_row(res)) || !row[1]) + { + mysql_free_result(res); + die("Strange result from query while probing slave for stop"); + } + done = !strcmp(row[1],"OFF"); + mysql_free_result(res); + if (done) + break; + my_sleep(SLAVE_POLL_INTERVAL); + } + return; +} + + +void do_sync_with_master2(struct st_command *command, long offset) +{ + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL *mysql= &cur_con->mysql; + char query_buf[FN_REFLEN+128]; + int timeout= 300; /* seconds */ + + if (!master_pos.file[0]) + die("Calling 'sync_with_master' without calling 'save_master_pos'"); + + sprintf(query_buf, "select master_pos_wait('%s', %ld, %d)", + master_pos.file, master_pos.pos + offset, timeout); + + if (mysql_query(mysql, query_buf)) + die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), + mysql_error(mysql)); + + if (!(res= mysql_store_result(mysql))) + die("mysql_store_result() returned NULL for '%s'", query_buf); + if (!(row= mysql_fetch_row(res))) + { + mysql_free_result(res); + die("empty result in %s", query_buf); + } + + int result= -99; + const char* result_str= row[0]; + if (result_str) + result= atoi(result_str); + + mysql_free_result(res); + + if (!result_str || result < 0) + { + /* master_pos_wait returned NULL or < 0 */ + show_query(mysql, "SHOW MASTER STATUS"); + show_query(mysql, "SHOW SLAVE STATUS"); + show_query(mysql, "SHOW PROCESSLIST"); + fprintf(stderr, "analyze: sync_with_master\n"); + + if (!result_str) + { + /* + master_pos_wait returned NULL. This indicates that + slave SQL thread is not started, the slave's master + information is not initialized, the arguments are + incorrect, or an error has occured + */ + die("%.*s failed: '%s' returned NULL "\ + "indicating slave SQL thread failure", + command->first_word_len, command->query, query_buf); + + } + + if (result == -1) + die("%.*s failed: '%s' returned -1 "\ + "indicating timeout after %d seconds", + command->first_word_len, command->query, query_buf, timeout); + else + die("%.*s failed: '%s' returned unknown result :%d", + command->first_word_len, command->query, query_buf, result); + } + + return; +} + +void do_sync_with_master(struct st_command *command) +{ + long offset= 0; + char *p= command->first_argument; + const char *offset_start= p; + if (*offset_start) + { + for (; my_isdigit(charset_info, *p); p++) + offset = offset * 10 + *p - '0'; + + if(*p && !my_isspace(charset_info, *p)) + die("Invalid integer argument \"%s\"", offset_start); + command->last_argument= p; + } + do_sync_with_master2(command, offset); + return; +} + + +/* + when ndb binlog is on, this call will wait until last updated epoch + (locally in the mysqld) has been received into the binlog +*/ +int do_save_master_pos() +{ + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL *mysql = &cur_con->mysql; + const char *query; + int rpl_parse; + DBUG_ENTER("do_save_master_pos"); + + rpl_parse = mysql_rpl_parse_enabled(mysql); + mysql_disable_rpl_parse(mysql); + +#ifdef HAVE_NDB_BINLOG + /* + Wait for ndb binlog to be up-to-date with all changes + done on the local mysql server + */ + { + ulong have_ndbcluster; + if (mysql_query(mysql, query= "show variables like 'have_ndbcluster'")) + die("'%s' failed: %d %s", query, + mysql_errno(mysql), mysql_error(mysql)); + if (!(res= mysql_store_result(mysql))) + die("mysql_store_result() returned NULL for '%s'", query); + if (!(row= mysql_fetch_row(res))) + die("Query '%s' returned empty result", query); + + have_ndbcluster= strcmp("YES", row[1]) == 0; + mysql_free_result(res); + + if (have_ndbcluster) + { + ulonglong start_epoch= 0, handled_epoch= 0, + latest_epoch=0, latest_trans_epoch=0, + latest_handled_binlog_epoch= 0, latest_received_binlog_epoch= 0, + latest_applied_binlog_epoch= 0; + int count= 0; + int do_continue= 1; + while (do_continue) + { + const char binlog[]= "binlog"; + const char latest_epoch_str[]= + "latest_epoch="; + const char latest_trans_epoch_str[]= + "latest_trans_epoch="; + const char latest_received_binlog_epoch_str[]= + "latest_received_binlog_epoch"; + const char latest_handled_binlog_epoch_str[]= + "latest_handled_binlog_epoch="; + const char latest_applied_binlog_epoch_str[]= + "latest_applied_binlog_epoch="; + if (count) + sleep(1); + if (mysql_query(mysql, query= "show engine ndb status")) + die("failed in '%s': %d %s", query, + mysql_errno(mysql), mysql_error(mysql)); + if (!(res= mysql_store_result(mysql))) + die("mysql_store_result() returned NULL for '%s'", query); + while ((row= mysql_fetch_row(res))) + { + if (strcmp(row[1], binlog) == 0) + { + const char *status= row[2]; + + /* latest_epoch */ + while (*status && strncmp(status, latest_epoch_str, + sizeof(latest_epoch_str)-1)) + status++; + if (*status) + { + status+= sizeof(latest_epoch_str)-1; + latest_epoch= strtoull(status, (char**) 0, 10); + } + else + die("result does not contain '%s' in '%s'", + latest_epoch_str, query); + /* latest_trans_epoch */ + while (*status && strncmp(status, latest_trans_epoch_str, + sizeof(latest_trans_epoch_str)-1)) + status++; + if (*status) + { + status+= sizeof(latest_trans_epoch_str)-1; + latest_trans_epoch= strtoull(status, (char**) 0, 10); + } + else + die("result does not contain '%s' in '%s'", + latest_trans_epoch_str, query); + /* latest_received_binlog_epoch */ + while (*status && + strncmp(status, latest_received_binlog_epoch_str, + sizeof(latest_received_binlog_epoch_str)-1)) + status++; + if (*status) + { + status+= sizeof(latest_received_binlog_epoch_str)-1; + latest_received_binlog_epoch= strtoull(status, (char**) 0, 10); + } + else + die("result does not contain '%s' in '%s'", + latest_received_binlog_epoch_str, query); + /* latest_handled_binlog */ + while (*status && + strncmp(status, latest_handled_binlog_epoch_str, + sizeof(latest_handled_binlog_epoch_str)-1)) + status++; + if (*status) + { + status+= sizeof(latest_handled_binlog_epoch_str)-1; + latest_handled_binlog_epoch= strtoull(status, (char**) 0, 10); + } + else + die("result does not contain '%s' in '%s'", + latest_handled_binlog_epoch_str, query); + /* latest_applied_binlog_epoch */ + while (*status && + strncmp(status, latest_applied_binlog_epoch_str, + sizeof(latest_applied_binlog_epoch_str)-1)) + status++; + if (*status) + { + status+= sizeof(latest_applied_binlog_epoch_str)-1; + latest_applied_binlog_epoch= strtoull(status, (char**) 0, 10); + } + else + die("result does not contain '%s' in '%s'", + latest_applied_binlog_epoch_str, query); + if (count == 0) + start_epoch= latest_trans_epoch; + break; + } + } + if (!row) + die("result does not contain '%s' in '%s'", + binlog, query); + if (latest_handled_binlog_epoch > handled_epoch) + count= 0; + handled_epoch= latest_handled_binlog_epoch; + count++; + if (latest_handled_binlog_epoch >= start_epoch) + do_continue= 0; + else if (count > 30) + { + break; + } + mysql_free_result(res); + } + } + } +#endif + if (mysql_query(mysql, query= "show master status")) + die("failed in 'show master status': %d %s", + mysql_errno(mysql), mysql_error(mysql)); + + if (!(res = mysql_store_result(mysql))) + die("mysql_store_result() retuned NULL for '%s'", query); + if (!(row = mysql_fetch_row(res))) + die("empty result in show master status"); + strnmov(master_pos.file, row[0], sizeof(master_pos.file)-1); + master_pos.pos = strtoul(row[1], (char**) 0, 10); + mysql_free_result(res); + + if (rpl_parse) + mysql_enable_rpl_parse(mysql); + + DBUG_RETURN(0); +} + + +/* + Assign the variable with + + SYNOPSIS + do_let() + query called command + + DESCRIPTION + let $= + + - is the string string found between the $ and = + - is the content between the = and , it may span + multiple line and contain any characters except + - is a string containing of one or more chars, default is ; + + RETURN VALUES + Program will die if error detected +*/ + +void do_let(struct st_command *command) +{ + char *p= command->first_argument; + char *var_name, *var_name_end; + DYNAMIC_STRING let_rhs_expr; + DBUG_ENTER("do_let"); + + init_dynamic_string(&let_rhs_expr, "", 512, 2048); + + /* Find */ + if (!*p) + die("Missing arguments to let"); + var_name= p; + while (*p && (*p != '=') && !my_isspace(charset_info,*p)) + p++; + var_name_end= p; + if (var_name == var_name_end || + (var_name+1 == var_name_end && *var_name == '$')) + die("Missing variable name in let"); + while (my_isspace(charset_info,*p)) + p++; + if (*p++ != '=') + die("Missing assignment operator in let"); + + /* Find start of */ + while (*p && my_isspace(charset_info,*p)) + p++; + + do_eval(&let_rhs_expr, p, command->end, FALSE); + + command->last_argument= command->end; + /* Assign var_val to var_name */ + var_set(var_name, var_name_end, let_rhs_expr.str, + (let_rhs_expr.str + let_rhs_expr.length)); + dynstr_free(&let_rhs_expr); + DBUG_VOID_RETURN; +} + + +int do_rpl_probe(struct st_command *command __attribute__((unused))) +{ + DBUG_ENTER("do_rpl_probe"); + if (mysql_rpl_probe(&cur_con->mysql)) + die("Failed in mysql_rpl_probe(): '%s'", mysql_error(&cur_con->mysql)); + DBUG_RETURN(0); +} + + +int do_enable_rpl_parse(struct st_command *command __attribute__((unused))) +{ + mysql_enable_rpl_parse(&cur_con->mysql); + return 0; +} + + +int do_disable_rpl_parse(struct st_command *command __attribute__((unused))) +{ + mysql_disable_rpl_parse(&cur_con->mysql); + return 0; +} + + +/* + Sleep the number of specified seconds + + SYNOPSIS + do_sleep() + q called command + real_sleep use the value from opt_sleep as number of seconds to sleep + if real_sleep is false + + DESCRIPTION + sleep + real_sleep + + The difference between the sleep and real_sleep commands is that sleep + uses the delay from the --sleep command-line option if there is one. + (If the --sleep option is not given, the sleep command uses the delay + specified by its argument.) The real_sleep command always uses the + delay specified by its argument. The logic is that sometimes delays are + cpu-dependent, and --sleep can be used to set this delay. real_sleep is + used for cpu-independent delays. +*/ + +int do_sleep(struct st_command *command, my_bool real_sleep) +{ + int error= 0; + char *p= command->first_argument; + char *sleep_start, *sleep_end= command->end; + double sleep_val; + + while (my_isspace(charset_info, *p)) + p++; + if (!*p) + die("Missing argument to %.*s", command->first_word_len, command->query); + sleep_start= p; + /* Check that arg starts with a digit, not handled by my_strtod */ + if (!my_isdigit(charset_info, *sleep_start)) + die("Invalid argument to %.*s \"%s\"", command->first_word_len, + command->query,command->first_argument); + sleep_val= my_strtod(sleep_start, &sleep_end, &error); + if (error) + die("Invalid argument to %.*s \"%s\"", command->first_word_len, + command->query, command->first_argument); + + /* Fixed sleep time selected by --sleep option */ + if (opt_sleep >= 0 && !real_sleep) + sleep_val= opt_sleep; + + DBUG_PRINT("info", ("sleep_val: %f", sleep_val)); + if (sleep_val) + my_sleep((ulong) (sleep_val * 1000000L)); + command->last_argument= sleep_end; + return 0; +} + + +void do_get_file_name(struct st_command *command, + char* dest, uint dest_max_len) +{ + char *p= command->first_argument, *name; + if (!*p) + die("Missing file name argument"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + if (*p) + *p++= 0; + command->last_argument= p; + strmake(dest, name, dest_max_len - 1); +} + + +void do_set_charset(struct st_command *command) +{ + char *charset_name= command->first_argument; + char *p; + + if (!charset_name || !*charset_name) + die("Missing charset name in 'character_set'"); + /* Remove end space */ + p= charset_name; + while (*p && !my_isspace(charset_info,*p)) + p++; + if(*p) + *p++= 0; + command->last_argument= p; + charset_info= get_charset_by_csname(charset_name,MY_CS_PRIMARY,MYF(MY_WME)); + if (!charset_info) + abort_not_supported_test("Test requires charset '%s'", charset_name); +} + + +/* + Run query and return one field in the result set from the + first row and +*/ + +int query_get_string(MYSQL* mysql, const char* query, + int column, DYNAMIC_STRING* ds) +{ + MYSQL_RES *res= NULL; + MYSQL_ROW row; + + if (mysql_query(mysql, query)) + die("'%s' failed: %d %s", query, + mysql_errno(mysql), mysql_error(mysql)); + if ((res= mysql_store_result(mysql)) == NULL) + die("Failed to store result: %d %s", + mysql_errno(mysql), mysql_error(mysql)); + + if ((row= mysql_fetch_row(res)) == NULL) + { + mysql_free_result(res); + ds= 0; + return 1; + } + init_dynamic_string(ds, (row[column] ? row[column] : "NULL"), ~0, 32); + mysql_free_result(res); + return 0; +} + + +static int my_kill(int pid, int sig) +{ +#ifdef __WIN__ + HANDLE proc; + if ((proc= OpenProcess(PROCESS_TERMINATE, FALSE, pid)) == NULL) + return -1; + if (sig == 0) + { + CloseHandle(proc); + return 0; + } + (void)TerminateProcess(proc, 201); + CloseHandle(proc); + return 1; +#else + return kill(pid, sig); +#endif +} + + + +/* + Shutdown the server of current connection and + make sure it goes away within seconds + + NOTE! Currently only works with local server + + SYNOPSIS + do_shutdown_server() + command called command + + DESCRIPTION + shutdown [] + +*/ + +void do_shutdown_server(struct st_command *command) +{ + int timeout=60, pid; + DYNAMIC_STRING ds_pidfile_name; + MYSQL* mysql = &cur_con->mysql; + static DYNAMIC_STRING ds_timeout; + const struct command_arg shutdown_args[] = { + {"timeout", ARG_STRING, FALSE, &ds_timeout, "Timeout before killing server"} + }; + DBUG_ENTER("do_shutdown_server"); + + check_command_args(command, command->first_argument, shutdown_args, + sizeof(shutdown_args)/sizeof(struct command_arg), + ' '); + + if (ds_timeout.length) + { + timeout= atoi(ds_timeout.str); + if (timeout == 0) + die("Illegal argument for timeout: '%s'", ds_timeout.str); + } + dynstr_free(&ds_timeout); + + /* Get the servers pid_file name and use it to read pid */ + if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1, + &ds_pidfile_name)) + die("Failed to get pid_file from server"); + + /* Read the pid from the file */ + { + int fd; + char buff[32]; + + if ((fd= my_open(ds_pidfile_name.str, O_RDONLY, MYF(0))) < 0) + die("Failed to open file '%s'", ds_pidfile_name.str); + dynstr_free(&ds_pidfile_name); + + if (my_read(fd, (uchar*)&buff, + sizeof(buff), MYF(0)) <= 0){ + my_close(fd, MYF(0)); + die("pid file was empty"); + } + my_close(fd, MYF(0)); + + pid= atoi(buff); + if (pid == 0) + die("Pidfile didn't contain a valid number"); + } + DBUG_PRINT("info", ("Got pid %d", pid)); + + /* Tell server to shutdown if timeout > 0*/ + if (timeout && mysql_shutdown(mysql, SHUTDOWN_DEFAULT)) + die("mysql_shutdown failed"); + + /* Check that server dies */ + while(timeout--){ + if (my_kill(0, pid) < 0){ + DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); + break; + } + DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); + my_sleep(1000000L); + } + + /* Kill the server */ + DBUG_PRINT("info", ("Killing server, pid: %d", pid)); + (void)my_kill(9, pid); + + DBUG_VOID_RETURN; + +} + + +#if MYSQL_VERSION_ID >= 50000 +/* List of error names to error codes, available from 5.0 */ +typedef struct +{ + const char *name; + uint code; +} st_error; + +static st_error global_error_names[] = +{ +#include + { 0, 0 } +}; + +uint get_errcode_from_name(char *error_name, char *error_end) +{ + /* SQL error as string */ + st_error *e= global_error_names; + + DBUG_ENTER("get_errcode_from_name"); + DBUG_PRINT("enter", ("error_name: %s", error_name)); + + /* Loop through the array of known error names */ + for (; e->name; e++) + { + /* + If we get a match, we need to check the length of the name we + matched against in case it was longer than what we are checking + (as in ER_WRONG_VALUE vs. ER_WRONG_VALUE_COUNT). + */ + if (!strncmp(error_name, e->name, (int) (error_end - error_name)) && + (uint) strlen(e->name) == (uint) (error_end - error_name)) + { + DBUG_RETURN(e->code); + } + } + if (!e->name) + die("Unknown SQL error name '%s'", error_name); + DBUG_RETURN(0); +} +#else +uint get_errcode_from_name(char *error_name __attribute__((unused)), + char *error_end __attribute__((unused))) +{ + abort_not_in_this_version(); + return 0; /* Never reached */ +} +#endif + + + +void do_get_errcodes(struct st_command *command) +{ + struct st_match_err *to= saved_expected_errors.err; + char *p= command->first_argument; + uint count= 0; + + DBUG_ENTER("do_get_errcodes"); + + if (!*p) + die("Missing argument(s) to 'error'"); + + do + { + char *end; + + /* Skip leading spaces */ + while (*p && *p == ' ') + p++; + + /* Find end */ + end= p; + while (*end && *end != ',' && *end != ' ') + end++; + + if (*p == 'S') + { + char *to_ptr= to->code.sqlstate; + + /* + SQLSTATE string + - Must be SQLSTATE_LENGTH long + - May contain only digits[0-9] and _uppercase_ letters + */ + p++; /* Step past the S */ + if ((end - p) != SQLSTATE_LENGTH) + die("The sqlstate must be exactly %d chars long", SQLSTATE_LENGTH); + + /* Check sqlstate string validity */ + while (*p && p < end) + { + if (my_isdigit(charset_info, *p) || my_isupper(charset_info, *p)) + *to_ptr++= *p++; + else + die("The sqlstate may only consist of digits[0-9] " \ + "and _uppercase_ letters"); + } + + *to_ptr= 0; + to->type= ERR_SQLSTATE; + DBUG_PRINT("info", ("ERR_SQLSTATE: %s", to->code.sqlstate)); + } + else if (*p == 's') + { + die("The sqlstate definition must start with an uppercase S"); + } + else if (*p == 'E') + { + /* Error name string */ + + DBUG_PRINT("info", ("Error name: %s", p)); + to->code.errnum= get_errcode_from_name(p, end); + to->type= ERR_ERRNO; + DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); + } + else if (*p == 'e') + { + die("The error name definition must start with an uppercase E"); + } + else + { + long val; + char *start= p; + /* Check that the string passed to str2int only contain digits */ + while (*p && p != end) + { + if (!my_isdigit(charset_info, *p)) + die("Invalid argument to error: '%s' - "\ + "the errno may only consist of digits[0-9]", + command->first_argument); + p++; + } + + /* Convert the sting to int */ + if (!str2int(start, 10, (long) INT_MIN, (long) INT_MAX, &val)) + die("Invalid argument to error: '%s'", command->first_argument); + + to->code.errnum= (uint) val; + to->type= ERR_ERRNO; + DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); + } + to++; + count++; + + if (count >= (sizeof(saved_expected_errors.err) / + sizeof(struct st_match_err))) + die("Too many errorcodes specified"); + + /* Set pointer to the end of the last error code */ + p= end; + + /* Find next ',' */ + while (*p && *p != ',') + p++; + + if (*p) + p++; /* Step past ',' */ + + } while (*p); + + command->last_argument= p; + to->type= ERR_EMPTY; /* End of data */ + + DBUG_PRINT("info", ("Expected errors: %d", count)); + saved_expected_errors.count= count; + DBUG_VOID_RETURN; +} + + +/* + Get a string; Return ptr to end of string + Strings may be surrounded by " or ' + + If string is a '$variable', return the value of the variable. +*/ + +char *get_string(char **to_ptr, char **from_ptr, + struct st_command *command) +{ + char c, sep; + char *to= *to_ptr, *from= *from_ptr, *start=to; + DBUG_ENTER("get_string"); + + /* Find separator */ + if (*from == '"' || *from == '\'') + sep= *from++; + else + sep=' '; /* Separated with space */ + + for ( ; (c=*from) ; from++) + { + if (c == '\\' && from[1]) + { /* Escaped character */ + /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */ + switch (*++from) { + case 'n': + *to++= '\n'; + break; + case 't': + *to++= '\t'; + break; + case 'r': + *to++ = '\r'; + break; + case 'b': + *to++ = '\b'; + break; + case 'Z': /* ^Z must be escaped on Win32 */ + *to++='\032'; + break; + default: + *to++ = *from; + break; + } + } + else if (c == sep) + { + if (c == ' ' || c != *++from) + break; /* Found end of string */ + *to++=c; /* Copy duplicated separator */ + } + else + *to++=c; + } + if (*from != ' ' && *from) + die("Wrong string argument in %s", command->query); + + while (my_isspace(charset_info,*from)) /* Point to next string */ + from++; + + *to =0; /* End of string marker */ + *to_ptr= to+1; /* Store pointer to end */ + *from_ptr= from; + + /* Check if this was a variable */ + if (*start == '$') + { + const char *end= to; + VAR *var=var_get(start, &end, 0, 1); + if (var && to == (char*) end+1) + { + DBUG_PRINT("info",("var: '%s' -> '%s'", start, var->str_val)); + DBUG_RETURN(var->str_val); /* return found variable value */ + } + } + DBUG_RETURN(start); +} + + +void set_reconnect(MYSQL* mysql, int val) +{ + my_bool reconnect= val; + DBUG_ENTER("set_reconnect"); + DBUG_PRINT("info", ("val: %d", val)); +#if MYSQL_VERSION_ID < 50000 + mysql->reconnect= reconnect; +#else + mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); +#endif + DBUG_VOID_RETURN; +} + + +int select_connection_name(const char *name) +{ + DBUG_ENTER("select_connection_name"); + DBUG_PRINT("enter",("name: '%s'", name)); + + if (!(cur_con= find_connection_by_name(name))) + die("connection '%s' not found in connection pool", name); + + /* Update $mysql_get_server_version to that of current connection */ + var_set_mysql_get_server_version(&cur_con->mysql); + + DBUG_RETURN(0); +} + + +int select_connection(struct st_command *command) +{ + char *name; + char *p= command->first_argument; + DBUG_ENTER("select_connection"); + + if (!*p) + die("Missing connection name in connect"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + if (*p) + *p++= 0; + command->last_argument= p; + DBUG_RETURN(select_connection_name(name)); +} + + +void do_close_connection(struct st_command *command) +{ + char *p= command->first_argument, *name; + struct st_connection *con; + + DBUG_ENTER("close_connection"); + DBUG_PRINT("enter",("name: '%s'",p)); + + if (!*p) + die("Missing connection name in disconnect"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + + if (*p) + *p++= 0; + command->last_argument= p; + + if (!(con= find_connection_by_name(name))) + die("connection '%s' not found in connection pool", name); + + DBUG_PRINT("info", ("Closing connection %s", con->name)); +#ifndef EMBEDDED_LIBRARY + if (command->type == Q_DIRTY_CLOSE) + { + if (con->mysql.net.vio) + { + vio_delete(con->mysql.net.vio); + con->mysql.net.vio = 0; + } + } +#else + /* + As query could be still executed in a separate theread + we need to check if the query's thread was finished and probably wait + (embedded-server specific) + */ + wait_query_thread_end(con); +#endif /*EMBEDDED_LIBRARY*/ + if (con->stmt) + mysql_stmt_close(con->stmt); + con->stmt= 0; + + mysql_close(&con->mysql); + + if (con->util_mysql) + mysql_close(con->util_mysql); + con->util_mysql= 0; + + my_free(con->name, MYF(0)); + + /* + When the connection is closed set name to "-closed_connection-" + to make it possible to reuse the connection name. + */ + if (!(con->name = my_strdup("-closed_connection-", MYF(MY_WME)))) + die("Out of memory"); + + DBUG_VOID_RETURN; +} + + +/* + Connect to a server doing several retries if needed. + + SYNOPSIS + safe_connect() + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + + NOTE + + Sometimes in a test the client starts before + the server - to solve the problem, we try again + after some sleep if connection fails the first + time + + This function will try to connect to the given server + "opt_max_connect_retries" times and sleep "connection_retry_sleep" + seconds between attempts before finally giving up. + This helps in situation when the client starts + before the server (which happens sometimes). + It will only ignore connection errors during these retries. + +*/ + +void safe_connect(MYSQL* mysql, const char *name, const char *host, + const char *user, const char *pass, const char *db, + int port, const char *sock) +{ + int failed_attempts= 0; + static ulong connection_retry_sleep= 100000; /* Microseconds */ + + DBUG_ENTER("safe_connect"); + while(!mysql_real_connect(mysql, host,user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) + { + /* + Connect failed + + Only allow retry if this was an error indicating the server + could not be contacted. Error code differs depending + on protocol/connection type + */ + + if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR || + mysql_errno(mysql) == CR_CONNECTION_ERROR) && + failed_attempts < opt_max_connect_retries) + { + verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts, + opt_max_connect_retries, mysql_errno(mysql), + mysql_error(mysql)); + my_sleep(connection_retry_sleep); + } + else + { + if (failed_attempts > 0) + die("Could not open connection '%s' after %d attempts: %d %s", name, + failed_attempts, mysql_errno(mysql), mysql_error(mysql)); + else + die("Could not open connection '%s': %d %s", name, + mysql_errno(mysql), mysql_error(mysql)); + } + failed_attempts++; + } + DBUG_VOID_RETURN; +} + + +/* + Connect to a server and handle connection errors in case they occur. + + SYNOPSIS + connect_n_handle_errors() + q - context of connect "query" (command) + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + + 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 VALUES + 1 - Connected + 0 - Not connected + +*/ + +int connect_n_handle_errors(struct st_command *command, + MYSQL* con, const char* host, + const char* user, const char* pass, + const char* db, int port, const char* sock) +{ + DYNAMIC_STRING *ds; + + ds= &ds_res; + + /* Only log if an error is expected */ + if (!command->abort_on_error && + !disable_query_log) + { + /* + Log the connect to result log + */ + 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); + replace_dynstr_append_uint(ds, port); + 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)) + { + var_set_errno(mysql_errno(con)); + handle_error(command, mysql_errno(con), mysql_error(con), + mysql_sqlstate(con), ds); + return 0; /* Not connected */ + } + + var_set_errno(0); + handle_no_error(command); + return 1; /* Connected */ +} + + +/* + Open a new connection to MySQL Server with the parameters + specified. Make the new connection the current connection. + + SYNOPSIS + do_connect() + q called command + + DESCRIPTION + connect(,,,[,[,[,[]]]]); + connect ,,,[,[,[,[]]]]; + + - name of the new connection + - hostname of server + - user to connect as + - password used when connecting + - initial db when connected + - server port + - server socket + - options to use for the connection + * SSL - use SSL if available + * COMPRESS - use compression if available + +*/ + +void do_connect(struct st_command *command) +{ + int con_port= opt_port; + char *con_options; + my_bool con_ssl= 0, con_compress= 0; + struct st_connection* con_slot; + + static DYNAMIC_STRING ds_connection_name; + static DYNAMIC_STRING ds_host; + static DYNAMIC_STRING ds_user; + static DYNAMIC_STRING ds_password; + static DYNAMIC_STRING ds_database; + static DYNAMIC_STRING ds_port; + static DYNAMIC_STRING ds_sock; + static DYNAMIC_STRING ds_options; + const struct command_arg connect_args[] = { + { "connection name", ARG_STRING, TRUE, &ds_connection_name, "Name of the connection" }, + { "host", ARG_STRING, TRUE, &ds_host, "Host to connect to" }, + { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, + { "passsword", ARG_STRING, FALSE, &ds_password, "Password used when connecting" }, + { "database", ARG_STRING, FALSE, &ds_database, "Database to select after connect" }, + { "port", ARG_STRING, FALSE, &ds_port, "Port to connect to" }, + { "socket", ARG_STRING, FALSE, &ds_sock, "Socket to connect with" }, + { "options", ARG_STRING, FALSE, &ds_options, "Options to use while connecting" } + }; + + DBUG_ENTER("do_connect"); + DBUG_PRINT("enter",("connect: %s", command->first_argument)); + + strip_parentheses(command); + check_command_args(command, command->first_argument, connect_args, + sizeof(connect_args)/sizeof(struct command_arg), + ','); + + /* Port */ + if (ds_port.length) + { + con_port= atoi(ds_port.str); + if (con_port == 0) + die("Illegal argument for port: '%s'", ds_port.str); + } + + /* Sock */ + if (ds_sock.length) + { + /* + If the socket is specified just as a name without path + append tmpdir in front + */ + if (*ds_sock.str != FN_LIBCHAR) + { + char buff[FN_REFLEN]; + fn_format(buff, ds_sock.str, TMPDIR, "", 0); + dynstr_set(&ds_sock, buff); + } + } + else + { + /* No socket specified, use default */ + dynstr_set(&ds_sock, unix_sock); + } + DBUG_PRINT("info", ("socket: %s", ds_sock.str)); + + + /* Options */ + con_options= ds_options.str; + while (*con_options) + { + char* end; + /* Step past any spaces in beginning of option*/ + while (*con_options && my_isspace(charset_info, *con_options)) + con_options++; + /* Find end of this option */ + end= con_options; + while (*end && !my_isspace(charset_info, *end)) + end++; + if (!strncmp(con_options, "SSL", 3)) + con_ssl= 1; + else if (!strncmp(con_options, "COMPRESS", 8)) + con_compress= 1; + else + die("Illegal option to connect: %.*s", + (int) (end - con_options), con_options); + /* Process next option */ + con_options= end; + } + + if (find_connection_by_name(ds_connection_name.str)) + die("Connection %s already exists", ds_connection_name.str); + + if (next_con != connections_end) + con_slot= next_con; + else + { + if (!(con_slot= find_connection_by_name("-closed_connection-"))) + die("Connection limit exhausted, you can have max %d connections", + (int) (sizeof(connections)/sizeof(struct st_connection))); + } + +#ifdef EMBEDDED_LIBRARY + con_slot->query_done= 1; +#endif + if (!mysql_init(&con_slot->mysql)) + die("Failed on mysql_init()"); + if (opt_compress || con_compress) + mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS); + mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, 0); + mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, + charset_info->csname); + if (opt_charsets_dir) + mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_DIR, + opt_charsets_dir); + +#ifdef HAVE_OPENSSL + if (opt_use_ssl || con_ssl) + { + mysql_ssl_set(&con_slot->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); +#if MYSQL_VERSION_ID >= 50000 + /* Turn on ssl_verify_server_cert only if host is "localhost" */ + opt_ssl_verify_server_cert= !strcmp(ds_host.str, "localhost"); + mysql_options(&con_slot->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); +#endif + } +#endif + + /* Use default db name */ + if (ds_database.length == 0) + dynstr_set(&ds_database, opt_db); + + /* Special database to allow one to connect without a database name */ + if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*")) + dynstr_set(&ds_database, ""); + + if (connect_n_handle_errors(command, &con_slot->mysql, + ds_host.str,ds_user.str, + ds_password.str, ds_database.str, + con_port, ds_sock.str)) + { + DBUG_PRINT("info", ("Inserting connection %s in connection pool", + ds_connection_name.str)); + if (!(con_slot->name= my_strdup(ds_connection_name.str, MYF(MY_WME)))) + die("Out of memory"); + con_slot->name_len= strlen(con_slot->name); + cur_con= con_slot; + + if (con_slot == next_con) + next_con++; /* if we used the next_con slot, advance the pointer */ + } + + /* Update $mysql_get_server_version to that of current connection */ + var_set_mysql_get_server_version(&cur_con->mysql); + + dynstr_free(&ds_connection_name); + dynstr_free(&ds_host); + dynstr_free(&ds_user); + dynstr_free(&ds_password); + dynstr_free(&ds_database); + dynstr_free(&ds_port); + dynstr_free(&ds_sock); + dynstr_free(&ds_options); + DBUG_VOID_RETURN; +} + + +int do_done(struct st_command *command) +{ + /* Check if empty block stack */ + if (cur_block == block_stack) + { + if (*command->query != '}') + die("Stray 'end' command - end of block before beginning"); + die("Stray '}' - end of block before beginning"); + } + + /* Test if inner block has been executed */ + if (cur_block->ok && cur_block->cmd == cmd_while) + { + /* Pop block from stack, re-execute outer block */ + cur_block--; + parser.current_line = cur_block->line; + } + else + { + /* Pop block from stack, goto next line */ + cur_block--; + parser.current_line++; + } + return 0; +} + + +/* + Process start of a "if" or "while" statement + + SYNOPSIS + do_block() + cmd Type of block + q called command + + DESCRIPTION + if ([!]) + { + + } + + while ([!]) + { + + } + + Evaluates the and if it evaluates to + greater than zero executes the following code block. + A '!' can be used before the to indicate it should + be executed if it evaluates to zero. + +*/ + +void do_block(enum block_cmd cmd, struct st_command* command) +{ + char *p= command->first_argument; + const char *expr_start, *expr_end; + VAR v; + const char *cmd_name= (cmd == cmd_while ? "while" : "if"); + my_bool not_expr= FALSE; + DBUG_ENTER("do_block"); + DBUG_PRINT("enter", ("%s", cmd_name)); + + /* Check stack overflow */ + if (cur_block == block_stack_end) + die("Nesting too deeply"); + + /* Set way to find outer block again, increase line counter */ + cur_block->line= parser.current_line++; + + /* If this block is ignored */ + if (!cur_block->ok) + { + /* Inner block should be ignored too */ + cur_block++; + cur_block->cmd= cmd; + cur_block->ok= FALSE; + DBUG_VOID_RETURN; + } + + /* Parse and evaluate test expression */ + expr_start= strchr(p, '('); + if (!expr_start++) + die("missing '(' in %s", cmd_name); + + /* Check for ! */ + if (*expr_start == '!') + { + not_expr= TRUE; + expr_start++; /* Step past the '!' */ + } + /* Find ending ')' */ + expr_end= strrchr(expr_start, ')'); + if (!expr_end) + die("missing ')' in %s", cmd_name); + p= (char*)expr_end+1; + + while (*p && my_isspace(charset_info, *p)) + p++; + if (*p && *p != '{') + die("Missing '{' after %s. Found \"%s\"", cmd_name, p); + + var_init(&v,0,0,0,0); + eval_expr(&v, expr_start, &expr_end); + + /* Define inner block */ + cur_block++; + cur_block->cmd= cmd; + cur_block->ok= (v.int_val ? TRUE : FALSE); + + if (not_expr) + cur_block->ok = !cur_block->ok; + + DBUG_PRINT("info", ("OK: %d", cur_block->ok)); + + var_free(&v); + DBUG_VOID_RETURN; +} + + +void do_delimiter(struct st_command* command) +{ + char* p= command->first_argument; + DBUG_ENTER("do_delimiter"); + DBUG_PRINT("enter", ("first_argument: %s", command->first_argument)); + + while (*p && my_isspace(charset_info, *p)) + p++; + + if (!(*p)) + die("Can't set empty delimiter"); + + strmake(delimiter, p, sizeof(delimiter) - 1); + delimiter_length= strlen(delimiter); + + DBUG_PRINT("exit", ("delimiter: %s", delimiter)); + command->last_argument= p + delimiter_length; + DBUG_VOID_RETURN; +} + + +my_bool match_delimiter(int c, const char *delim, uint length) +{ + uint i; + char tmp[MAX_DELIMITER_LENGTH]; + + if (c != *delim) + return 0; + + for (i= 1; i < length && + (c= my_getc(cur_file->file)) == *(delim + i); + i++) + tmp[i]= c; + + if (i == length) + return 1; /* Found delimiter */ + + /* didn't find delimiter, push back things that we read */ + my_ungetc(c); + while (i > 1) + my_ungetc(tmp[--i]); + return 0; +} + + +my_bool end_of_query(int c) +{ + return match_delimiter(c, delimiter, delimiter_length); +} + + +/* + Read one "line" from the file + + SYNOPSIS + read_line + buf buffer for the read line + size size of the buffer i.e max size to read + + DESCRIPTION + This function actually reads several lines and adds them to the + buffer buf. It continues to read until it finds what it believes + is a complete query. + + Normally that means it will read lines until it reaches the + "delimiter" that marks end of query. Default delimiter is ';' + The function should be smart enough not to detect delimiter's + found inside strings surrounded with '"' and '\'' escaped strings. + + If the first line in a query starts with '#' or '-' this line is treated + as a comment. A comment is always terminated when end of line '\n' is + reached. + +*/ + +int read_line(char *buf, int size) +{ + char c, last_quote; + char *p= buf, *buf_end= buf + size - 1; + int skip_char= 0; + enum {R_NORMAL, R_Q, R_SLASH_IN_Q, + R_COMMENT, R_LINE_START} state= R_LINE_START; + DBUG_ENTER("read_line"); + LINT_INIT(last_quote); + + start_lineno= cur_file->lineno; + DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno)); + for (; p < buf_end ;) + { + skip_char= 0; + c= my_getc(cur_file->file); + if (feof(cur_file->file)) + { + found_eof: + if (cur_file->file != stdin) + { + my_fclose(cur_file->file, MYF(0)); + cur_file->file= 0; + } + my_free((uchar*) cur_file->file_name, MYF(MY_ALLOW_ZERO_PTR)); + cur_file->file_name= 0; + if (cur_file == file_stack) + { + /* We're back at the first file, check if + all { have matching } + */ + if (cur_block != block_stack) + die("Missing end of block"); + + *p= 0; + DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno)); + DBUG_RETURN(1); + } + cur_file--; + start_lineno= cur_file->lineno; + continue; + } + + if (c == '\n') + { + /* Line counting is independent of state */ + cur_file->lineno++; + + /* Convert cr/lf to lf */ + if (p != buf && *(p-1) == '\r') + p--; + } + + switch(state) { + case R_NORMAL: + if (end_of_query(c)) + { + *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", + delimiter, cur_file->lineno)); + DBUG_RETURN(0); + } + else if ((c == '{' && + (!my_strnncoll_simple(charset_info, (const uchar*) "while", 5, + (uchar*) buf, min(5, p - buf), 0) || + !my_strnncoll_simple(charset_info, (const uchar*) "if", 2, + (uchar*) buf, min(2, p - buf), 0)))) + { + /* Only if and while commands can be terminated by { */ + *p++= c; + *p= 0; + DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '\'' || c == '"' || c == '`') + { + last_quote= c; + state= R_Q; + } + break; + + case R_COMMENT: + if (c == '\n') + { + /* Comments are terminated by newline */ + *p= 0; + DBUG_PRINT("exit", ("Found newline in comment at line: %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + break; + + case R_LINE_START: + if (c == '#' || c == '-') + { + /* A # or - in the first position of the line - this is a comment */ + state = R_COMMENT; + } + else if (my_isspace(charset_info, c)) + { + /* Skip all space at begining of line */ + if (c == '\n') + { + /* Query hasn't started yet */ + start_lineno= cur_file->lineno; + DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d", + start_lineno)); + } + skip_char= 1; + } + else if (end_of_query(c)) + { + *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", + delimiter, cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '}') + { + /* A "}" need to be by itself in the begining of a line to terminate */ + *p++= c; + *p= 0; + DBUG_PRINT("exit", ("Found '}' in begining of a line at line: %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '\'' || c == '"' || c == '`') + { + last_quote= c; + state= R_Q; + } + else + state= R_NORMAL; + break; + + case R_Q: + if (c == last_quote) + state= R_NORMAL; + else if (c == '\\') + state= R_SLASH_IN_Q; + break; + + case R_SLASH_IN_Q: + state= R_Q; + break; + + } + + if (!skip_char) + { + /* 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->file)) + goto found_eof; + c= my_getc(cur_file->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; + } + } + die("The input buffer is too small for this query.x\n" \ + "check your query or increase MAX_QUERY and recompile"); + DBUG_RETURN(0); +} + + +/* + Convert the read query to result format version 1 + + That is: After newline, all spaces need to be skipped + unless the previous char was a quote + + This is due to an old bug that has now been fixed, but the + version 1 output format is preserved by using this function + +*/ + +void convert_to_format_v1(char* query) +{ + int last_c_was_quote= 0; + char *p= query, *to= query; + char *end= strend(query); + char last_c; + + while (p <= end) + { + if (*p == '\n' && !last_c_was_quote) + { + *to++ = *p++; /* Save the newline */ + + /* Skip any spaces on next line */ + while (*p && my_isspace(charset_info, *p)) + p++; + + last_c_was_quote= 0; + } + else if (*p == '\'' || *p == '"' || *p == '`') + { + last_c= *p; + *to++ = *p++; + + /* Copy anything until the next quote of same type */ + while (*p && *p != last_c) + *to++ = *p++; + + *to++ = *p++; + + last_c_was_quote= 1; + } + else + { + *to++ = *p++; + last_c_was_quote= 0; + } + } +} + + +/* + Check for unexpected "junk" after the end of query + This is normally caused by missing delimiters or when + switching between different delimiters +*/ + +void check_eol_junk_line(const char *line) +{ + const char *p= line; + DBUG_ENTER("check_eol_junk_line"); + DBUG_PRINT("enter", ("line: %s", line)); + + /* Check for extra delimiter */ + if (*p && !strncmp(p, delimiter, delimiter_length)) + die("Extra delimiter \"%s\" found", delimiter); + + /* Allow trailing # comment */ + if (*p && *p != '#') + { + if (*p == '\n') + die("Missing delimiter"); + die("End of line junk detected: \"%s\"", p); + } + DBUG_VOID_RETURN; +} + +void check_eol_junk(const char *eol) +{ + const char *p= eol; + DBUG_ENTER("check_eol_junk"); + DBUG_PRINT("enter", ("eol: %s", eol)); + + /* Skip past all spacing chars and comments */ + while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) + { + /* Skip past comments started with # and ended with newline */ + if (*p && *p == '#') + { + p++; + while (*p && *p != '\n') + p++; + } + + /* Check this line */ + if (*p && *p == '\n') + check_eol_junk_line(p); + + if (*p) + p++; + } + + check_eol_junk_line(p); + + DBUG_VOID_RETURN; +} + + +bool is_delimiter(const char* p) +{ + uint match= 0; + char* delim= delimiter; + while (*p && *p == *delim++) + { + match++; + p++; + } + + return (match == delimiter_length); +} + + +/* + Create a command from a set of lines + + SYNOPSIS + read_command() + command_ptr pointer where to return the new query + + DESCRIPTION + Converts lines returned by read_line into a command, this involves + parsing the first word in the read line to find the command type. + + A -- comment may contain a valid query as the first word after the + comment start. Thus it's always checked to see if that is the case. + The advantage with this approach is to be able to execute commands + terminated by new line '\n' regardless how many "delimiter" it contain. +*/ + +#define MAX_QUERY (256*1024*2) /* 256K -- a test in sp-big is >128K */ +static char read_command_buf[MAX_QUERY]; + +int read_command(struct st_command** command_ptr) +{ + char *p= read_command_buf; + struct st_command* command; + DBUG_ENTER("read_command"); + + if (parser.current_line < parser.read_lines) + { + get_dynamic(&q_lines, (uchar*) command_ptr, parser.current_line) ; + DBUG_RETURN(0); + } + if (!(*command_ptr= command= + (struct st_command*) my_malloc(sizeof(*command), + MYF(MY_WME|MY_ZEROFILL))) || + insert_dynamic(&q_lines, (uchar*) &command)) + die(NullS); + command->type= Q_UNKNOWN; + + read_command_buf[0]= 0; + if (read_line(read_command_buf, sizeof(read_command_buf))) + { + check_eol_junk(read_command_buf); + DBUG_RETURN(1); + } + + convert_to_format_v1(read_command_buf); + + DBUG_PRINT("info", ("query: %s", read_command_buf)); + if (*p == '#') + { + command->type= Q_COMMENT; + } + else if (p[0] == '-' && p[1] == '-') + { + command->type= Q_COMMENT_WITH_COMMAND; + p+= 2; /* Skip past -- */ + } + + /* Skip leading spaces */ + while (*p && my_isspace(charset_info, *p)) + p++; + + if (!(command->query_buf= command->query= my_strdup(p, MYF(MY_WME)))) + die("Out of memory"); + + /* + Calculate first word length(the command), terminated + by 'space' , '(' or 'delimiter' */ + p= command->query; + while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) + p++; + command->first_word_len= (uint) (p - command->query); + DBUG_PRINT("info", ("first_word: %.*s", + command->first_word_len, command->query)); + + /* Skip spaces between command and first argument */ + while (*p && my_isspace(charset_info, *p)) + p++; + command->first_argument= p; + + command->end= strend(command->query); + command->query_len= (command->end - command->query); + parser.read_lines++; + DBUG_RETURN(0); +} + + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"basedir", 'b', "Basedir for tests.", (uchar**) &opt_basedir, + (uchar**) &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory where character sets are.", (uchar**) &opt_charsets_dir, + (uchar**) &opt_charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use the compressed server/client protocol.", + (uchar**) &opt_compress, (uchar**) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"cursor-protocol", OPT_CURSOR_PROTOCOL, "Use cursors for prepared statements.", + (uchar**) &cursor_protocol, (uchar**) &cursor_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'D', "Database to use.", (uchar**) &opt_db, (uchar**) &opt_db, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit", + 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + (uchar**) &debug_check_flag, (uchar**) &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + (uchar**) &debug_info_flag, (uchar**) &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", (uchar**) &opt_host, (uchar**) &opt_host, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"include", 'i', "Include SQL before each test case.", (uchar**) &opt_include, + (uchar**) &opt_include, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"logdir", OPT_LOG_DIR, "Directory for log files", (uchar**) &opt_logdir, + (uchar**) &opt_logdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"mark-progress", OPT_MARK_PROGRESS, + "Write linenumber and elapsed time to .progress ", + (uchar**) &opt_mark_progress, (uchar**) &opt_mark_progress, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"max-connect-retries", OPT_MAX_CONNECT_RETRIES, + "Max number of connection attempts when connecting to server", + (uchar**) &opt_max_connect_retries, (uchar**) &opt_max_connect_retries, 0, + GET_INT, REQUIRED_ARG, 500, 1, 10000, 0, 0, 0}, + {"password", 'p', "Password to use when connecting to server.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"port", 'P', "Port number to use for connection or 0 for default to, in " + "order of preference, my.cnf, $MYSQL_TCP_PORT, " +#if MYSQL_PORT_DEFAULT == 0 + "/etc/services, " +#endif + "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", + (uchar**) &opt_port, + (uchar**) &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ps-protocol", OPT_PS_PROTOCOL, "Use prepared statements protocol for communication", + (uchar**) &ps_protocol, (uchar**) &ps_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"quiet", 's', "Suppress all normal output.", (uchar**) &silent, + (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"record", 'r', "Record output of test_file into result file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"result-file", 'R', "Read/Store result from/in this file.", + (uchar**) &result_file_name, (uchar**) &result_file_name, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"server-arg", 'A', "Send option value to embedded server as a parameter.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"server-file", 'F', "Read embedded server arguments from file.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Suppress all normal output. Synonym for --quiet.", + (uchar**) &silent, (uchar**) &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"skip-safemalloc", OPT_SKIP_SAFEMALLOC, + "Don't use the memory allocation checking.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"sleep", 'T', "Sleep always this many seconds on sleep commands.", + (uchar**) &opt_sleep, (uchar**) &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, -1, 0, + 0, 0, 0}, + {"socket", 'S', "Socket file to use for connection.", + (uchar**) &unix_sock, (uchar**) &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"sp-protocol", OPT_SP_PROTOCOL, "Use stored procedures for select", + (uchar**) &sp_protocol, (uchar**) &sp_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"tail-lines", OPT_TAIL_LINES, + "Number of lines of the resul to include in a failure report", + (uchar**) &opt_tail_lines, (uchar**) &opt_tail_lines, 0, + GET_INT, REQUIRED_ARG, 0, 0, 10000, 0, 0, 0}, +#include "sslopt-longopts.h" + {"test-file", 'x', "Read test from/in this file (default stdin).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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.", (uchar**) &opt_user, (uchar**) &opt_user, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Write more.", (uchar**) &verbose, (uchar**) &verbose, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select", + (uchar**) &view_protocol, (uchar**) &view_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +#include + +void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname,MTEST_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + +void usage() +{ + print_version(); + printf("MySQL AB, by Sasha, Matt, Monty & Jani\n"); + printf("This software comes with ABSOLUTELY NO WARRANTY\n\n"); + printf("Runs a test against the mysql server and compares output with a results file.\n\n"); + printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname); + my_print_help(my_long_options); + printf(" --no-defaults Don't read default options from any options file.\n"); + my_print_variables(my_long_options); +} + +#include + + +/* + Read arguments for embedded server and put them into + embedded_server_args[] +*/ + +void read_embedded_server_arguments(const char *name) +{ + char argument[1024],buff[FN_REFLEN], *str=0; + FILE *file; + + if (!test_if_hard_path(name)) + { + strxmov(buff, opt_basedir, name, NullS); + name=buff; + } + fn_format(buff, name, "", "", MY_UNPACK_FILENAME); + + if (!embedded_server_arg_count) + { + embedded_server_arg_count=1; + embedded_server_args[0]= (char*) ""; /* Progname */ + } + if (!(file=my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(MY_WME)))) + die("Failed to open file '%s'", buff); + + while (embedded_server_arg_count < MAX_EMBEDDED_SERVER_ARGS && + (str=fgets(argument,sizeof(argument), file))) + { + *(strend(str)-1)=0; /* Remove end newline */ + if (!(embedded_server_args[embedded_server_arg_count]= + (char*) my_strdup(str,MYF(MY_WME)))) + { + my_fclose(file,MYF(0)); + die("Out of memory"); + + } + embedded_server_arg_count++; + } + my_fclose(file,MYF(0)); + if (str) + die("Too many arguments in option file: %s",name); + + return; +} + + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case '#': +#ifndef DBUG_OFF + DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace"); + debug_check_flag= 1; +#endif + break; + case 'r': + record = 1; + break; + case 'x': + { + char buff[FN_REFLEN]; + if (!test_if_hard_path(argument)) + { + strxmov(buff, opt_basedir, argument, NullS); + argument= buff; + } + fn_format(buff, argument, "", "", MY_UNPACK_FILENAME); + DBUG_ASSERT(cur_file == file_stack && cur_file->file == 0); + if (!(cur_file->file= + my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(0)))) + die("Could not open '%s' for reading, errno: %d", buff, errno); + cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); + cur_file->lineno= 1; + 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, "", "", MY_UNPACK_FILENAME); + timer_file= buff; + unlink(timer_file); /* Ignore error, may not exist */ + break; + } + case 'p': + if (argument) + { + my_free(opt_pass, MYF(MY_ALLOW_ZERO_PTR)); + opt_pass= my_strdup(argument, MYF(MY_FAE)); + while (*argument) *argument++= 'x'; /* Destroy argument */ + tty_password= 0; + } + else + tty_password= 1; + break; +#include + case 't': + strnmov(TMPDIR, argument, sizeof(TMPDIR)); + break; + case 'A': + if (!embedded_server_arg_count) + { + embedded_server_arg_count=1; + embedded_server_args[0]= (char*) ""; + } + if (embedded_server_arg_count == MAX_EMBEDDED_SERVER_ARGS-1 || + !(embedded_server_args[embedded_server_arg_count++]= + my_strdup(argument, MYF(MY_FAE)))) + { + die("Can't use server argument"); + } + break; + case OPT_LOG_DIR: + /* Check that the file exists */ + if (access(opt_logdir, F_OK) != 0) + die("The specified log directory does not exist: '%s'", opt_logdir); + break; + case 'F': + read_embedded_server_arguments(argument); + break; + case OPT_SKIP_SAFEMALLOC: +#ifdef SAFEMALLOC + sf_malloc_quick=1; +#endif + break; + case 'V': + print_version(); + exit(0); + case '?': + usage(); + exit(0); + } + return 0; +} + + +int parse_args(int argc, char **argv) +{ + load_defaults("my",load_default_groups,&argc,&argv); + default_argv= argv; + + if ((handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(1); + + if (argc > 1) + { + usage(); + exit(1); + } + if (argc == 1) + opt_db= *argv; + if (tty_password) + opt_pass= get_tty_password(NullS); /* purify tested */ + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + + if (!record) + { + /* Check that the result file exists */ + if (result_file_name && access(result_file_name, F_OK) != 0) + die("The specified result file '%s' does not exist", result_file_name); + } + + return 0; +} + +/* + Write the content of str into file + + SYNOPSIS + str_to_file2 + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file + append - append to file instead of overwriting old file +*/ + +void str_to_file2(const char *fname, char *str, int size, my_bool append) +{ + int fd; + char buff[FN_REFLEN]; + int flags= O_WRONLY | O_CREAT; + if (!test_if_hard_path(fname)) + { + strxmov(buff, opt_basedir, fname, NullS); + fname= buff; + } + fn_format(buff, fname, "", "", MY_UNPACK_FILENAME); + + if (!append) + flags|= O_TRUNC; + if ((fd= my_open(buff, flags, + MYF(MY_WME | MY_FFNF))) < 0) + die("Could not open '%s' for writing, errno: %d", buff, errno); + if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR) + die("Could not find end of file '%s', errno: %d", buff, errno); + if (my_write(fd, (uchar*)str, size, MYF(MY_WME|MY_FNABP))) + die("write failed, errno: %d", errno); + my_close(fd, MYF(0)); +} + +/* + Write the content of str into file + + SYNOPSIS + str_to_file + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file +*/ + +void str_to_file(const char *fname, char *str, int size) +{ + str_to_file2(fname, str, size, FALSE); +} + + +void check_regerr(my_regex_t* r, int err) +{ + char err_buf[1024]; + + if (err) + { + my_regerror(err,r,err_buf,sizeof(err_buf)); + die("Regex error: %s\n", err_buf); + } +} + + +#ifdef __WIN__ + +DYNAMIC_ARRAY patterns; + +/* + init_win_path_patterns + + DESCRIPTION + Setup string patterns that will be used to detect filenames that + needs to be converted from Win to Unix format + +*/ + +void init_win_path_patterns() +{ + /* List of string patterns to match in order to find paths */ + const char* paths[] = { "$MYSQL_TEST_DIR", + "$MYSQL_TMP_DIR", + "$MYSQLTEST_VARDIR", + "$MASTER_MYSOCK", + "./test/" }; + int num_paths= sizeof(paths)/sizeof(char*); + int i; + char* p; + + DBUG_ENTER("init_win_path_patterns"); + + my_init_dynamic_array(&patterns, sizeof(const char*), 16, 16); + + /* Loop through all paths in the array */ + for (i= 0; i < num_paths; i++) + { + VAR* v; + if (*(paths[i]) == '$') + { + v= var_get(paths[i], 0, 0, 0); + p= my_strdup(v->str_val, MYF(MY_FAE)); + } + else + p= my_strdup(paths[i], MYF(MY_FAE)); + + /* Don't insert zero length strings in patterns array */ + if (strlen(p) == 0) + { + my_free(p, MYF(0)); + continue; + } + + if (insert_dynamic(&patterns, (uchar*) &p)) + die(NullS); + + DBUG_PRINT("info", ("p: %s", p)); + while (*p) + { + if (*p == '/') + *p='\\'; + p++; + } + } + DBUG_VOID_RETURN; +} + +void free_win_path_patterns() +{ + uint i= 0; + for (i=0 ; i < patterns.elements ; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + my_free((char*) *pattern, MYF(0)); + } + delete_dynamic(&patterns); +} + +/* + fix_win_paths + + DESCRIPTION + Search the string 'val' for the patterns that are known to be + strings that contain filenames. Convert all \ to / in the + filenames that are found. + + Ex: + val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' + => $MYSQL_TEST_DIR is found by strstr + => all \ from c:\mysql\m... until next space is converted into / +*/ + +void fix_win_paths(const char *val, int len) +{ + uint i; + char *p; + + DBUG_ENTER("fix_win_paths"); + for (i= 0; i < patterns.elements; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + DBUG_PRINT("info", ("pattern: %s", *pattern)); + + /* Search for the path in string */ + while ((p= strstr(val, *pattern))) + { + DBUG_PRINT("info", ("Found %s in val p: %s", *pattern, p)); + + while (*p && !my_isspace(charset_info, *p)) + { + if (*p == '\\') + *p= '/'; + p++; + } + DBUG_PRINT("info", ("Converted \\ to /, p: %s", p)); + } + } + DBUG_PRINT("exit", (" val: %s, len: %d", val, len)); + DBUG_VOID_RETURN; +} +#endif + + + +/* + Append the result for one field to the dynamic string ds +*/ + +void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, + char* val, ulonglong len, my_bool is_null) +{ + char null[]= "NULL"; + + if (col_idx < max_replace_column && replace_column[col_idx]) + { + val= replace_column[col_idx]; + len= strlen(val); + } + else if (is_null) + { + val= null; + len= 4; + } +#ifdef __WIN__ + else if ((field->type == MYSQL_TYPE_DOUBLE || + field->type == MYSQL_TYPE_FLOAT ) && + field->decimals >= 31) + { + /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */ + char *start= strchr(val, 'e'); + if (start && strlen(start) >= 5 && + (start[1] == '-' || start[1] == '+') && start[2] == '0') + { + start+=2; /* Now points at first '0' */ + if (field->flags & ZEROFILL_FLAG) + { + /* Move all chars before the first '0' one step right */ + memmove(val + 1, val, start - val); + *val= '0'; + } + else + { + /* Move all chars after the first '0' one step left */ + memmove(start, start + 1, strlen(start)); + len--; + } + } + } +#endif + + if (!display_result_vertically) + { + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, (int)len); + } + else + { + dynstr_append(ds, field->name); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, (int)len); + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append all results to the dynamic string separated with '\t' + Values may be converted with 'replace_column' +*/ + +void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) +{ + MYSQL_ROW row; + uint num_fields= mysql_num_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); + ulong *lengths; + + while ((row = mysql_fetch_row(res))) + { + uint i; + lengths = mysql_fetch_lengths(res); + for (i = 0; i < num_fields; i++) + append_field(ds, i, &fields[i], + row[i], lengths[i], !row[i]); + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append all results from ps execution to the dynamic string separated + with '\t'. Values may be converted with 'replace_column' +*/ + +void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, + MYSQL_FIELD *fields, uint num_fields) +{ + MYSQL_BIND *my_bind; + my_bool *is_null; + ulong *length; + uint i; + + /* Allocate array with bind structs, lengths and NULL flags */ + my_bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), + MYF(MY_WME | MY_FAE | MY_ZEROFILL)); + length= (ulong*) my_malloc(num_fields * sizeof(ulong), + MYF(MY_WME | MY_FAE)); + is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), + MYF(MY_WME | MY_FAE)); + + /* Allocate data for the result of each field */ + for (i= 0; i < num_fields; i++) + { + uint max_length= fields[i].max_length + 1; + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].buffer= my_malloc(max_length, MYF(MY_WME | MY_FAE)); + my_bind[i].buffer_length= max_length; + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + + DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu", + i, my_bind[i].buffer_type, my_bind[i].buffer_length)); + } + + if (mysql_stmt_bind_result(stmt, my_bind)) + die("mysql_stmt_bind_result failed: %d: %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + + while (mysql_stmt_fetch(stmt) == 0) + { + for (i= 0; i < num_fields; i++) + append_field(ds, i, &fields[i], (char*)my_bind[i].buffer, + *my_bind[i].length, *my_bind[i].is_null); + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } + + if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + die("fetch didn't end with MYSQL_NO_DATA from statement: %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + + for (i= 0; i < num_fields; i++) + { + /* Free data for output */ + my_free(my_bind[i].buffer, MYF(MY_WME | MY_FAE)); + } + /* Free array with bind structs, lengths and NULL flags */ + my_free(my_bind , MYF(MY_WME | MY_FAE)); + my_free(length , MYF(MY_WME | MY_FAE)); + my_free(is_null , MYF(MY_WME | MY_FAE)); +} + + +/* + Append metadata for fields to output +*/ + +void append_metadata(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) +{ + MYSQL_FIELD *field_end; + dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" + "Column_alias\tType\tLength\tMax length\tIs_null\t" + "Flags\tDecimals\tCharsetnr\n"); + + for (field_end= field+num_fields ; + field < field_end ; + field++) + { + 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); + replace_dynstr_append_uint(ds, field->type); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->length); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->max_length); + 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); + replace_dynstr_append_uint(ds, field->flags); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->decimals); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->charsetnr); + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append affected row count and other info to output +*/ + +void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, + const char *info) +{ + char buf[40], buff2[21]; + sprintf(buf,"affected rows: %s\n", llstr(affected_rows, buff2)); + dynstr_append(ds, buf); + if (info) + { + dynstr_append(ds, "info: "); + dynstr_append(ds, info); + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Display the table headings with the names tab separated +*/ + +void append_table_headings(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) +{ + uint col_idx; + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append(ds, field[col_idx].name); + } + dynstr_append_mem(ds, "\n", 1); +} + +/* + Fetch warnings from server and append to ds + + RETURN VALUE + Number of warnings appended to ds +*/ + +int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) +{ + uint count; + MYSQL_RES *warn_res; + DBUG_ENTER("append_warnings"); + + if (!(count= mysql_warning_count(mysql))) + DBUG_RETURN(0); + + /* + If one day we will support execution of multi-statements + through PS API we should not issue SHOW WARNINGS until + we have not read all results... + */ + DBUG_ASSERT(!mysql_more_results(mysql)); + + if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) + die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); + + if (!(warn_res= mysql_store_result(mysql))) + die("Warning count is %u but didn't get any warnings", + count); + + append_result(ds, warn_res); + mysql_free_result(warn_res); + + DBUG_PRINT("warnings", ("%s", ds->str)); + + DBUG_RETURN(count); +} + + +/* + Run query using MySQL C API + + SYNOPSIS + run_query_normal() + mysql mysql handle + command current command pointer + flags flags indicating if we should SEND and/or REAP + query query string to execute + query_len length query string to execute + ds output buffer where to store result form query +*/ + +void run_query_normal(struct st_connection *cn, struct st_command *command, + int flags, char *query, int query_len, + DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= 0; + MYSQL *mysql= &cn->mysql; + int err= 0, counter= 0; + DBUG_ENTER("run_query_normal"); + DBUG_PRINT("enter",("flags: %d", flags)); + DBUG_PRINT("enter", ("query: '%-.60s'", query)); + + if (flags & QUERY_SEND_FLAG) + { + /* + Send the query + */ + if (do_send_query(cn, query, query_len, flags)) + { + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + } +#ifdef EMBEDDED_LIBRARY + /* + Here we handle 'reap' command, so we need to check if the + query's thread was finished and probably wait + */ + else if (flags & QUERY_REAP_FLAG) + wait_query_thread_end(cn); +#endif /*EMBEDDED_LIBRARY*/ + if (!(flags & QUERY_REAP_FLAG)) + DBUG_VOID_RETURN; + + do + { + /* + When on first result set, call mysql_read_query_result to retrieve + answer to the query sent earlier + */ + if ((counter==0) && mysql_read_query_result(mysql)) + { + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + + } + + /* + Store the result of the query if it will return any fields + */ + if (mysql_field_count(mysql) && ((res= mysql_store_result(mysql)) == 0)) + { + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + + if (!disable_result_log) + { + ulonglong affected_rows; /* Ok to be undef if 'disable_info' is set */ + LINT_INIT(affected_rows); + + if (res) + { + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + if (display_metadata) + append_metadata(ds, fields, num_fields); + + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); + + append_result(ds, res); + } + + /* + Need to call mysql_affected_rows() before the "new" + query to find the warnings + */ + if (!disable_info) + affected_rows= mysql_affected_rows(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)) + { + if (append_warnings(ds_warnings, mysql) || ds_warnings->length) + { + dynstr_append_mem(ds, "Warnings:\n", 10); + dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length); + } + } + + if (!disable_info) + append_info(ds, affected_rows, mysql_info(mysql)); + } + + if (res) + { + mysql_free_result(res); + res= 0; + } + counter++; + } while (!(err= mysql_next_result(mysql))); + if (err > 0) + { + /* We got an error from mysql_next_result, maybe expected */ + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + DBUG_ASSERT(err == -1); /* Successful and there are no more results */ + + /* If we come here the query is both executed and read successfully */ + handle_no_error(command); + +end: + + /* + We save the return code (mysql_errno(mysql)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ + var_set_errno(mysql_errno(mysql)); + DBUG_VOID_RETURN; +} + + +/* + Handle errors which occurred during execution + + SYNOPSIS + handle_error() + 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. +*/ + +void handle_error(struct st_command *command, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds) +{ + uint i; + + DBUG_ENTER("handle_error"); + + if (command->require_file[0]) + { + /* + The query after a "--require" failed. This is fine as long the server + returned a valid reponse. Don't allow 2013 or 2006 to trigger an + abort_not_supported_test + */ + if (err_errno == CR_SERVER_LOST || + err_errno == CR_SERVER_GONE_ERROR) + die("require query '%s' failed: %d: %s", command->query, + err_errno, err_error); + + /* Abort the run of this test, pass the failed query as reason */ + abort_not_supported_test("Query '%s' failed, required functionality " \ + "not supported", command->query); + } + + if (command->abort_on_error) + die("query '%s' failed: %d: %s", command->query, err_errno, err_error); + + DBUG_PRINT("info", ("expected_errors.count: %d", + command->expected_errors.count)); + for (i= 0 ; (uint) i < command->expected_errors.count ; i++) + { + if (((command->expected_errors.err[i].type == ERR_ERRNO) && + (command->expected_errors.err[i].code.errnum == err_errno)) || + ((command->expected_errors.err[i].type == ERR_SQLSTATE) && + (strncmp(command->expected_errors.err[i].code.sqlstate, + err_sqlstate, SQLSTATE_LENGTH) == 0))) + { + if (!disable_result_log) + { + if (command->expected_errors.count == 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 (command->expected_errors.err[0].type == ERR_SQLSTATE || + (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0)) + dynstr_append(ds,"Got one of the listed errors\n"); + } + /* OK */ + DBUG_VOID_RETURN; + } + } + + DBUG_PRINT("info",("i: %d expected_errors: %d", i, + command->expected_errors.count)); + + if (!disable_result_log) + { + dynstr_append_mem(ds, "ERROR ",6); + replace_dynstr_append(ds, err_sqlstate); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, err_error); + dynstr_append_mem(ds, "\n", 1); + } + + if (i) + { + if (command->expected_errors.err[0].type == ERR_ERRNO) + die("query '%s' failed with wrong errno %d: '%s', instead of %d...", + command->query, err_errno, err_error, + command->expected_errors.err[0].code.errnum); + else + die("query '%s' failed with wrong sqlstate %s: '%s', instead of %s...", + command->query, err_sqlstate, err_error, + command->expected_errors.err[0].code.sqlstate); + } + + DBUG_VOID_RETURN; +} + + +/* + Handle absence of errors after execution + + SYNOPSIS + handle_no_error() + q - context of query + + RETURN VALUE + error - function will not return +*/ + +void handle_no_error(struct st_command *command) +{ + DBUG_ENTER("handle_no_error"); + + if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[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...", + command->query, command->expected_errors.err[0].code.errnum); + } + else if (command->expected_errors.err[0].type == ERR_SQLSTATE && + strcmp(command->expected_errors.err[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...", + command->query, command->expected_errors.err[0].code.sqlstate); + } + + DBUG_VOID_RETURN; +} + + +/* + Run query using prepared statement C API + + SYNPOSIS + run_query_stmt + mysql - mysql handle + command - currrent command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_query_stmt(MYSQL *mysql, struct st_command *command, + char *query, int query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ + MYSQL_STMT *stmt; + DYNAMIC_STRING ds_prepare_warnings; + DYNAMIC_STRING ds_execute_warnings; + DBUG_ENTER("run_query_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + + /* + Init a new stmt if it's not already one created for this connection + */ + if(!(stmt= cur_con->stmt)) + { + if (!(stmt= mysql_stmt_init(mysql))) + die("unable to init stmt structure"); + cur_con->stmt= stmt; + } + + /* Init dynamic strings for warnings */ + if (!disable_warnings) + { + init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); + init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); + } + + /* + Prepare the query + */ + if (mysql_stmt_prepare(stmt, query, query_len)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* + Get the warnings from mysql_stmt_prepare and keep them in a + separate string + */ + if (!disable_warnings) + append_warnings(&ds_prepare_warnings, mysql); + + /* + No need to call mysql_stmt_bind_param() because we have no + parameter markers. + */ + +#if MYSQL_VERSION_ID >= 50000 + if (cursor_protocol_enabled) + { + /* + Use cursor when retrieving result + */ + ulong type= CURSOR_TYPE_READ_ONLY; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) + die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } +#endif + + /* + Execute the query + */ + if (mysql_stmt_execute(stmt)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* + When running in cursor_protocol get the warnings from execute here + and keep them in a separate string for later. + */ + if (cursor_protocol_enabled && !disable_warnings) + append_warnings(&ds_execute_warnings, mysql); + + /* + We instruct that we want to update the "max_length" field in + mysql_stmt_store_result(), this is our only way to know how much + buffer to allocate for result data + */ + { + my_bool one= 1; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) + die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } + + /* + If we got here the statement succeeded and was expected to do so, + get data. Note that this can still give errors found during execution! + Store the result of the query if if will return any fields + */ + if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* If we got here the statement was both executed and read successfully */ + handle_no_error(command); + if (!disable_result_log) + { + /* + Not all statements creates a result set. If there is one we can + now create another normal result set that contains the meta + data. This set can be handled almost like any other non prepared + statement result set. + */ + if ((res= mysql_stmt_result_metadata(stmt)) != NULL) + { + /* Take the column count from meta info */ + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + if (display_metadata) + append_metadata(ds, fields, num_fields); + + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); + + append_stmt_result(ds, stmt, fields, num_fields); + + mysql_free_result(res); /* Free normal result set with meta data */ + + /* Clear prepare warnings */ + dynstr_set(&ds_prepare_warnings, NULL); + } + else + { + /* + This is a query without resultset + */ + } + + if (!disable_warnings) + { + /* Get the warnings from execute */ + + /* Append warnings to ds - if there are any */ + if (append_warnings(&ds_execute_warnings, mysql) || + ds_execute_warnings.length || + ds_prepare_warnings.length || + ds_warnings->length) + { + dynstr_append_mem(ds, "Warnings:\n", 10); + if (ds_warnings->length) + dynstr_append_mem(ds, ds_warnings->str, + ds_warnings->length); + if (ds_prepare_warnings.length) + dynstr_append_mem(ds, ds_prepare_warnings.str, + ds_prepare_warnings.length); + if (ds_execute_warnings.length) + dynstr_append_mem(ds, ds_execute_warnings.str, + ds_execute_warnings.length); + } + } + + if (!disable_info) + append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql)); + + } + +end: + if (!disable_warnings) + { + dynstr_free(&ds_prepare_warnings); + dynstr_free(&ds_execute_warnings); + } + + + /* Close the statement if - no reconnect, need new prepare */ + if (mysql->reconnect) + { + mysql_stmt_close(stmt); + cur_con->stmt= NULL; + } + + /* + We save the return code (mysql_stmt_errno(stmt)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ + + var_set_errno(mysql_stmt_errno(stmt)); + + DBUG_VOID_RETURN; +} + + + +/* + Create a util connection if one does not already exists + and use that to run the query + This is done to avoid implict commit when creating/dropping objects such + as view, sp etc. +*/ + +int util_query(MYSQL* org_mysql, const char* query){ + + MYSQL* mysql; + DBUG_ENTER("util_query"); + + if(!(mysql= cur_con->util_mysql)) + { + DBUG_PRINT("info", ("Creating util_mysql")); + if (!(mysql= mysql_init(mysql))) + die("Failed in mysql_init()"); + + /* enable local infile, in non-binary builds often disabled by default */ + mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0); + safe_connect(mysql, "util", org_mysql->host, org_mysql->user, + org_mysql->passwd, org_mysql->db, org_mysql->port, + org_mysql->unix_socket); + + cur_con->util_mysql= mysql; + } + + return mysql_query(mysql, query); +} + + + +/* + Run query + + SYNPOSIS + run_query() + mysql mysql handle + command currrent command pointer + + flags control the phased/stages of query execution to be performed + if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG + is on the result will be read - for regular query, both bits must be on +*/ + +void run_query(struct st_connection *cn, struct st_command *command, int flags) +{ + MYSQL *mysql= &cn->mysql; + DYNAMIC_STRING *ds; + DYNAMIC_STRING *save_ds= NULL; + DYNAMIC_STRING ds_result; + DYNAMIC_STRING ds_sorted; + DYNAMIC_STRING ds_warnings; + DYNAMIC_STRING eval_query; + char *query; + int query_len; + my_bool view_created= 0, sp_created= 0; + my_bool complete_query= ((flags & QUERY_SEND_FLAG) && + (flags & QUERY_REAP_FLAG)); + DBUG_ENTER("run_query"); + + init_dynamic_string(&ds_warnings, NULL, 0, 256); + + /* + Evaluate query if this is an eval command + */ + if (command->type == Q_EVAL) + { + init_dynamic_string(&eval_query, "", command->query_len+256, 1024); + do_eval(&eval_query, command->query, command->end, FALSE); + query = eval_query.str; + query_len = eval_query.length; + } + else + { + query = command->query; + query_len = strlen(query); + } + + /* + When command->require_file is set the output of _this_ query + should be compared with an already existing file + Create a temporary dynamic string to contain the output from + this query. + */ + if (command->require_file[0]) + { + init_dynamic_string(&ds_result, "", 1024, 1024); + ds= &ds_result; + } + else + ds= &ds_res; + + /* + Log the query into the output buffer + */ + if (!disable_query_log && (flags & QUERY_SEND_FLAG)) + { + replace_dynstr_append_mem(ds, query, query_len); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + + if (view_protocol_enabled && + complete_query && + match_re(&view_re, query)) + { + /* + Create the query as a view. + Use replace since view can exist from a failed mysqltest run + */ + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ", + query_len+64, 256); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) + { + /* + Failed to create the view, this is not fatal + just run the query the normal way + */ + DBUG_PRINT("view_create_error", + ("Failed to create view '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); + + /* Log error to create view */ + verbose_msg("Failed to create view '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); + } + else + { + /* + Yes, it was possible to create this query as a view + */ + view_created= 1; + query= (char*)"SELECT * FROM mysqltest_tmp_v"; + query_len = strlen(query); + + /* + Collect warnings from create of the view that should otherwise + have been produced when the SELECT was executed + */ + append_warnings(&ds_warnings, cur_con->util_mysql); + } + + dynstr_free(&query_str); + + } + + if (sp_protocol_enabled && + complete_query && + match_re(&sp_re, query)) + { + /* + Create the query as a stored procedure + Drop first since sp can exist from a failed mysqltest run + */ + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;", + query_len+64, 256); + util_query(mysql, query_str.str); + dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n"); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) + { + /* + Failed to create the stored procedure for this query, + this is not fatal just run the query the normal way + */ + DBUG_PRINT("sp_create_error", + ("Failed to create sp '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); + + /* Log error to create sp */ + verbose_msg("Failed to create sp '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); + + } + else + { + sp_created= 1; + + query= (char*)"CALL mysqltest_tmp_sp()"; + query_len = strlen(query); + } + dynstr_free(&query_str); + } + + if (display_result_sorted) + { + /* + Collect the query output in a separate string + that can be sorted before it's added to the + global result string + */ + init_dynamic_string(&ds_sorted, "", 1024, 1024); + save_ds= ds; /* Remember original ds */ + ds= &ds_sorted; + } + + /* + Find out how to run this query + + Always run with normal C API if it's not a 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 && + complete_query && + match_re(&ps_re, query)) + run_query_stmt(mysql, command, query, query_len, ds, &ds_warnings); + else + run_query_normal(cn, command, flags, query, query_len, + ds, &ds_warnings); + + if (display_result_sorted) + { + /* Sort the result set and append it to result */ + dynstr_append_sorted(save_ds, &ds_sorted); + ds= save_ds; + dynstr_free(&ds_sorted); + } + + if (sp_created) + { + if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp ")) + die("Failed to drop sp: %d: %s", mysql_errno(mysql), mysql_error(mysql)); + } + + if (view_created) + { + if (util_query(mysql, "DROP VIEW mysqltest_tmp_v ")) + die("Failed to drop view: %d: %s", + mysql_errno(mysql), mysql_error(mysql)); + } + + if (command->require_file[0]) + { + /* A result file was specified for _this_ query + and the output should be checked against an already + existing file which has been specified using --require or --result + */ + check_require(ds, command->require_file); + } + + dynstr_free(&ds_warnings); + if (ds == &ds_result) + dynstr_free(&ds_result); + if (command->type == Q_EVAL) + dynstr_free(&eval_query); + DBUG_VOID_RETURN; +} + +/****************************************************************************/ +/* + Functions to detect different SQL statements +*/ + +char *re_eprint(int err) +{ + static char epbuf[100]; + size_t len= my_regerror(REG_ITOA|err, (my_regex_t *)NULL, + epbuf, sizeof(epbuf)); + assert(len <= sizeof(epbuf)); + return(epbuf); +} + +void init_re_comp(my_regex_t *re, const char* str) +{ + int err= my_regcomp(re, str, (REG_EXTENDED | REG_ICASE | REG_NOSUB), + &my_charset_latin1); + if (err) + { + char erbuf[100]; + int len= my_regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), len, (int)sizeof(erbuf), erbuf); + } +} + +void init_re(void) +{ + /* + Filter for queries that can be run using the + MySQL Prepared Statements C API + */ + const char *ps_re_str = + "^(" + "[[:space:]]*REPLACE[[:space:]]|" + "[[: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:]])"; + + /* + Filter for queries that can be run using the + Stored procedures + */ + const char *sp_re_str =ps_re_str; + + /* + Filter for queries that can be run as views + */ + const char *view_re_str = + "^(" + "[[:space:]]*SELECT[[:space:]])"; + + init_re_comp(&ps_re, ps_re_str); + init_re_comp(&sp_re, sp_re_str); + init_re_comp(&view_re, view_re_str); +} + + +int match_re(my_regex_t *re, char *str) +{ + int err= my_regexec(re, str, (size_t)0, NULL, 0); + + if (err == 0) + return 1; + else if (err == REG_NOMATCH) + return 0; + + { + char erbuf[100]; + int len= my_regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), len, (int)sizeof(erbuf), erbuf); + } + return 0; +} + +void free_re(void) +{ + my_regfree(&ps_re); + my_regfree(&sp_re); + my_regfree(&view_re); + my_regex_end(); +} + +/****************************************************************************/ + +void get_command_type(struct st_command* command) +{ + char save; + uint type; + DBUG_ENTER("get_command_type"); + + if (*command->query == '}') + { + command->type = Q_END_BLOCK; + DBUG_VOID_RETURN; + } + + save= command->query[command->first_word_len]; + command->query[command->first_word_len]= 0; + type= find_type(command->query, &command_typelib, 1+2); + command->query[command->first_word_len]= save; + if (type > 0) + { + command->type=(enum enum_commands) type; /* Found command */ + + /* + Look for case where "query" was explicitly specified to + force command being sent to server + */ + if (type == Q_QUERY) + { + /* Skip the "query" part */ + command->query= command->first_argument; + } + } + else + { + /* No mysqltest command matched */ + + if (command->type != Q_COMMENT_WITH_COMMAND) + { + /* A query that will sent to mysqld */ + command->type= Q_QUERY; + } + else + { + /* -- "comment" that didn't contain a mysqltest command */ + die("Found line beginning with -- that didn't contain "\ + "a valid mysqltest command, check your syntax or "\ + "use # if you intended to write a comment"); + } + } + + /* Set expected error on command */ + memcpy(&command->expected_errors, &saved_expected_errors, + sizeof(saved_expected_errors)); + DBUG_PRINT("info", ("There are %d expected errors", + command->expected_errors.count)); + command->abort_on_error= (command->expected_errors.count == 0 && + abort_on_error); + + DBUG_VOID_RETURN; +} + + + +/* + Record how many milliseconds it took to execute the test file + up until the current line and write it to .progress file + +*/ + +void mark_progress(struct st_command* command __attribute__((unused)), + int line) +{ + static ulonglong progress_start= 0; // < Beware + DYNAMIC_STRING ds_progress; + + char buf[32], *end; + ulonglong timer= timer_now(); + if (!progress_start) + progress_start= timer; + timer-= progress_start; + + if (init_dynamic_string(&ds_progress, "", 256, 256)) + die("Out of memory"); + + /* Milliseconds since start */ + end= longlong2str(timer, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + dynstr_append_mem(&ds_progress, "\t", 1); + + /* Parser line number */ + end= int10_to_str(line, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + dynstr_append_mem(&ds_progress, "\t", 1); + + /* Filename */ + dynstr_append(&ds_progress, cur_file->file_name); + dynstr_append_mem(&ds_progress, ":", 1); + + /* Line in file */ + end= int10_to_str(cur_file->lineno, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + + + dynstr_append_mem(&ds_progress, "\n", 1); + + progress_file.write(&ds_progress); + + dynstr_free(&ds_progress); + +} + +#ifdef HAVE_STACKTRACE + +static void dump_backtrace(void) +{ + struct st_connection *conn= cur_con; + + my_safe_print_str("read_command_buf", read_command_buf, + sizeof(read_command_buf)); + if (conn) + { + my_safe_print_str("conn->name", conn->name, conn->name_len); +#ifdef EMBEDDED_LIBRARY + my_safe_print_str("conn->cur_query", conn->cur_query, conn->cur_query_len); +#endif + } + fputs("Attempting backtrace...\n", stderr); + my_print_stacktrace(NULL, my_thread_stack_size); +} + +#else + +static void dump_backtrace(void) +{ + fputs("Backtrace not available.\n", stderr); +} + +#endif + +static sig_handler signal_handler(int sig) +{ + fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n", sig); + dump_backtrace(); +} + +#ifdef __WIN__ + +LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) +{ + __try + { + my_set_exception_pointers(exp); + signal_handler(exp->ExceptionRecord->ExceptionCode); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fputs("Got exception in exception handler!\n", stderr); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + + +static void init_signal_handling(void) +{ + UINT mode; + + /* Set output destination of messages to the standard error stream. */ + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + + /* Do not not display the a error message box. */ + mode= SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX; + SetErrorMode(mode); + + SetUnhandledExceptionFilter(exception_filter); +} + +#else /* __WIN__ */ + +static void init_signal_handling(void) +{ + struct sigaction sa; + DBUG_ENTER("init_signal_handling"); + +#ifdef HAVE_STACKTRACE + my_init_stacktrace(); +#endif + + sa.sa_flags = SA_RESETHAND | SA_NODEFER; + sigemptyset(&sa.sa_mask); + sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); + + sa.sa_handler= signal_handler; + + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGABRT, &sa, NULL); +#ifdef SIGBUS + sigaction(SIGBUS, &sa, NULL); +#endif + sigaction(SIGILL, &sa, NULL); + sigaction(SIGFPE, &sa, NULL); +} + +#endif /* !__WIN__ */ + +int main(int argc, char **argv) +{ + struct st_command *command; + my_bool q_send_flag= 0, abort_flag= 0; + uint command_executed= 0, last_command_executed= 0; + char save_file[FN_REFLEN]; + MY_INIT(argv[0]); + + save_file[0]= 0; + TMPDIR[0]= 0; + + init_signal_handling(); + + /* Init expected errors */ + memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); + + /* Init connections */ + memset(connections, 0, sizeof(connections)); + connections_end= connections + + (sizeof(connections)/sizeof(struct st_connection)) - 1; + next_con= connections + 1; + +#ifdef EMBEDDED_LIBRARY + /* set appropriate stack for the 'query' threads */ + (void) pthread_attr_init(&cn_thd_attrib); + pthread_attr_setstacksize(&cn_thd_attrib, DEFAULT_THREAD_STACK); +#endif /*EMBEDDED_LIBRARY*/ + + /* Init file stack */ + memset(file_stack, 0, sizeof(file_stack)); + file_stack_end= + file_stack + (sizeof(file_stack)/sizeof(struct st_test_file)) - 1; + cur_file= file_stack; + + /* Init block stack */ + memset(block_stack, 0, sizeof(block_stack)); + block_stack_end= + block_stack + (sizeof(block_stack)/sizeof(struct st_block)) - 1; + cur_block= block_stack; + cur_block->ok= TRUE; /* Outer block should always be executed */ + cur_block->cmd= cmd_none; + + my_init_dynamic_array(&q_lines, sizeof(struct st_command*), 1024, 1024); + + if (hash_init(&var_hash, charset_info, + 1024, 0, 0, get_var_key, var_free, MYF(0))) + die("Variable hash initialization failed"); + + var_set_string("$MYSQL_SERVER_VERSION", MYSQL_SERVER_VERSION); + + memset(&master_pos, 0, sizeof(master_pos)); + + parser.current_line= parser.read_lines= 0; + memset(&var_reg, 0, sizeof(var_reg)); + + init_builtin_echo(); +#ifdef __WIN__ +#ifndef USE_CYGWIN + is_windows= 1; +#endif + init_tmp_sh_file(); + init_win_path_patterns(); +#endif + + init_dynamic_string(&ds_res, "", 2048, 2048); + + parse_args(argc, argv); + + log_file.open(opt_logdir, result_file_name, ".log"); + if (opt_mark_progress) + progress_file.open(opt_logdir, result_file_name, ".progress"); + + var_set_int("$PS_PROTOCOL", ps_protocol); + var_set_int("$SP_PROTOCOL", sp_protocol); + var_set_int("$VIEW_PROTOCOL", view_protocol); + var_set_int("$CURSOR_PROTOCOL", cursor_protocol); + + DBUG_PRINT("info",("result_file: '%s'", + result_file_name ? result_file_name : "")); + if (mysql_server_init(embedded_server_arg_count, + embedded_server_args, + (char**) embedded_server_groups)) + die("Can't initialize MySQL server"); + server_initialized= 1; + if (cur_file == file_stack && cur_file->file == 0) + { + cur_file->file= stdin; + cur_file->file_name= my_strdup("", MYF(MY_WME)); + cur_file->lineno= 1; + } + init_re(); + ps_protocol_enabled= ps_protocol; + sp_protocol_enabled= sp_protocol; + view_protocol_enabled= view_protocol; + cursor_protocol_enabled= cursor_protocol; + /* Cursor protcol implies ps protocol */ + if (cursor_protocol_enabled) + ps_protocol_enabled= 1; + + cur_con= connections; + 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, + charset_info->csname); + if (opt_charsets_dir) + mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_DIR, + opt_charsets_dir); + +#ifdef HAVE_OPENSSL + + if (opt_use_ssl) + { + mysql_ssl_set(&cur_con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); +#if MYSQL_VERSION_ID >= 50000 + /* Turn on ssl_verify_server_cert only if host is "localhost" */ + opt_ssl_verify_server_cert= opt_host && !strcmp(opt_host, "localhost"); + mysql_options(&cur_con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); +#endif + } +#endif + + if (!(cur_con->name = my_strdup("default", MYF(MY_WME)))) + die("Out of memory"); + + safe_connect(&cur_con->mysql, cur_con->name, opt_host, opt_user, opt_pass, + opt_db, opt_port, unix_sock); + + /* Use all time until exit if no explicit 'start_timer' */ + timer_start= timer_now(); + + /* + Initialize $mysql_errno with -1, so we can + - distinguish it from valid values ( >= 0 ) and + - detect if there was never a command sent to the server + */ + var_set_errno(-1); + + /* Update $mysql_get_server_version to that of current connection */ + var_set_mysql_get_server_version(&cur_con->mysql); + + if (opt_include) + { + open_file(opt_include); + } + + while (!read_command(&command) && !abort_flag) + { + int current_line_inc = 1, processed = 0; + if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND) + get_command_type(command); + + if (parsing_disabled && + command->type != Q_ENABLE_PARSING && + command->type != Q_DISABLE_PARSING) + { + /* Parsing is disabled, silently convert this line to a comment */ + command->type= Q_COMMENT; + } + + if (cur_block->ok) + { + command->last_argument= command->first_argument; + processed = 1; + switch (command->type) { + case Q_CONNECT: + do_connect(command); + break; + case Q_CONNECTION: select_connection(command); break; + case Q_DISCONNECT: + case Q_DIRTY_CLOSE: + do_close_connection(command); break; + case Q_RPL_PROBE: do_rpl_probe(command); break; + case Q_ENABLE_RPL_PARSE: do_enable_rpl_parse(command); break; + case Q_DISABLE_RPL_PARSE: do_disable_rpl_parse(command); break; + case Q_ENABLE_QUERY_LOG: disable_query_log=0; break; + case Q_DISABLE_QUERY_LOG: disable_query_log=1; break; + case Q_ENABLE_ABORT_ON_ERROR: abort_on_error=1; break; + case Q_DISABLE_ABORT_ON_ERROR: abort_on_error=0; break; + case Q_ENABLE_RESULT_LOG: disable_result_log=0; break; + 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_INFO: disable_info=0; break; + case Q_DISABLE_INFO: disable_info=1; break; + case Q_ENABLE_METADATA: display_metadata=1; break; + case Q_DISABLE_METADATA: display_metadata=0; break; + case Q_SOURCE: do_source(command); break; + case Q_SLEEP: do_sleep(command, 0); break; + case Q_REAL_SLEEP: do_sleep(command, 1); break; + case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(command); break; + case Q_INC: do_modify_var(command, DO_INC); break; + case Q_DEC: do_modify_var(command, DO_DEC); break; + case Q_ECHO: do_echo(command); command_executed++; break; + case Q_SYSTEM: do_system(command); break; + case Q_REMOVE_FILE: do_remove_file(command); break; + case Q_MKDIR: do_mkdir(command); break; + case Q_RMDIR: do_rmdir(command); break; + case Q_FILE_EXIST: do_file_exist(command); break; + case Q_WRITE_FILE: do_write_file(command); break; + case Q_APPEND_FILE: do_append_file(command); break; + case Q_DIFF_FILES: do_diff_files(command); break; + case Q_SEND_QUIT: do_send_quit(command); break; + case Q_CHANGE_USER: do_change_user(command); break; + case Q_CAT_FILE: do_cat_file(command); break; + case Q_COPY_FILE: do_copy_file(command); break; + case Q_CHMOD_FILE: do_chmod_file(command); break; + case Q_PERL: do_perl(command); break; + case Q_DELIMITER: + do_delimiter(command); + break; + case Q_DISPLAY_VERTICAL_RESULTS: + display_result_vertically= TRUE; + break; + case Q_DISPLAY_HORIZONTAL_RESULTS: + display_result_vertically= FALSE; + break; + case Q_SORTED_RESULT: + /* + Turn on sorting of result set, will be reset after next + command + */ + display_result_sorted= TRUE; + break; + case Q_LET: do_let(command); break; + case Q_EVAL_RESULT: + die("'eval_result' command is deprecated"); + case Q_EVAL: + case Q_QUERY_VERTICAL: + case Q_QUERY_HORIZONTAL: + if (command->query == command->query_buf) + { + /* Skip the first part of command, i.e query_xxx */ + command->query= command->first_argument; + command->first_word_len= 0; + } + /* fall through */ + case Q_QUERY: + case Q_REAP: + { + my_bool old_display_result_vertically= display_result_vertically; + /* Default is full query, both reap and send */ + int flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG; + + if (q_send_flag) + { + /* Last command was an empty 'send' */ + flags= QUERY_SEND_FLAG; + q_send_flag= 0; + } + else if (command->type == Q_REAP) + { + flags= QUERY_REAP_FLAG; + } + + /* Check for special property for this query */ + display_result_vertically|= (command->type == Q_QUERY_VERTICAL); + + if (save_file[0]) + { + strmake(command->require_file, save_file, sizeof(save_file) - 1); + save_file[0]= 0; + } + run_query(cur_con, command, flags); + command_executed++; + command->last_argument= command->end; + + /* Restore settings */ + display_result_vertically= old_display_result_vertically; + + break; + } + case Q_SEND: + if (!*command->first_argument) + { + /* + This is a send without arguments, it indicates that _next_ query + should be send only + */ + q_send_flag= 1; + break; + } + + /* Remove "send" if this is first iteration */ + if (command->query == command->query_buf) + command->query= command->first_argument; + + /* + run_query() can execute a query partially, depending on the flags. + QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send + the query and read the result some time later when reap instruction + is given on this connection. + */ + run_query(cur_con, command, QUERY_SEND_FLAG); + command_executed++; + command->last_argument= command->end; + break; + case Q_REQUIRE: + do_get_file_name(command, save_file, sizeof(save_file)); + break; + case Q_ERROR: + do_get_errcodes(command); + break; + case Q_REPLACE: + do_get_replace(command); + break; + case Q_REPLACE_REGEX: + do_get_replace_regex(command); + break; + case Q_REPLACE_COLUMN: + do_get_replace_column(command); + break; + case Q_SAVE_MASTER_POS: do_save_master_pos(); break; + case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break; + case Q_SYNC_SLAVE_WITH_MASTER: + { + do_save_master_pos(); + if (*command->first_argument) + select_connection(command); + else + select_connection_name("slave"); + do_sync_with_master2(command, 0); + break; + } + case Q_COMMENT: /* Ignore row */ + command->last_argument= command->end; + break; + case Q_PING: + handle_command_error(command, mysql_ping(&cur_con->mysql)); + break; + case Q_SEND_SHUTDOWN: + handle_command_error(command, + mysql_shutdown(&cur_con->mysql, + SHUTDOWN_DEFAULT)); + break; + case Q_SHUTDOWN_SERVER: + do_shutdown_server(command); + break; + case Q_EXEC: + do_exec(command); + command_executed++; + 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(); + break; + case Q_CHARACTER_SET: + do_set_charset(command); + break; + case Q_DISABLE_PS_PROTOCOL: + ps_protocol_enabled= 0; + /* Close any open statements */ + close_statements(); + break; + case Q_ENABLE_PS_PROTOCOL: + ps_protocol_enabled= ps_protocol; + break; + case Q_DISABLE_RECONNECT: + set_reconnect(&cur_con->mysql, 0); + break; + case Q_ENABLE_RECONNECT: + set_reconnect(&cur_con->mysql, 1); + /* Close any open statements - no reconnect, need new prepare */ + close_statements(); + break; + case Q_DISABLE_PARSING: + if (parsing_disabled == 0) + parsing_disabled= 1; + else + die("Parsing is already disabled"); + break; + case Q_ENABLE_PARSING: + /* + Ensure we don't get parsing_disabled < 0 as this would accidentally + disable code we don't want to have disabled + */ + if (parsing_disabled == 1) + parsing_disabled= 0; + else + die("Parsing is already enabled"); + break; + case Q_DIE: + /* Abort test with error code and error message */ + die("%s", command->first_argument); + break; + case Q_EXIT: + /* Stop processing any more commands */ + abort_flag= 1; + break; + case Q_SKIP: + abort_not_supported_test("%s", command->first_argument); + break; + + case Q_RESULT: + die("result, deprecated command"); + break; + + default: + processed= 0; + break; + } + } + + if (!processed) + { + current_line_inc= 0; + switch (command->type) { + case Q_WHILE: do_block(cmd_while, command); break; + case Q_IF: do_block(cmd_if, command); break; + case Q_END_BLOCK: do_done(command); break; + default: current_line_inc = 1; break; + } + } + else + check_eol_junk(command->last_argument); + + if (command->type != Q_ERROR && + command->type != Q_COMMENT) + { + /* + As soon as any non "error" command or comment has been executed, + the array with expected errors should be cleared + */ + memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); + } + + if (command_executed != last_command_executed) + { + /* + As soon as any command has been executed, + the replace structures should be cleared + */ + free_all_replace(); + + /* Also reset "sorted_result" */ + display_result_sorted= FALSE; + } + last_command_executed= command_executed; + + parser.current_line += current_line_inc; + if ( opt_mark_progress ) + mark_progress(command, parser.current_line); + + /* Write result from command to log file */ + log_file.write(&ds_res); + dynstr_set(&ds_res, 0); + } + + log_file.close(); + + start_lineno= 0; + + if (parsing_disabled) + die("Test ended with parsing disabled"); + + /* + The whole test has been executed _sucessfully_. + Time to compare result or save it to record file. + The entire output from test is in the log file + */ + if (log_file.bytes_written()) + { + if (result_file_name) + { + /* A result file has been specified */ + + if (record) + { + /* Recording */ + + /* save a copy of the log to result file */ + if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0) + die("Failed to copy '%s' to '%s', errno: %d", + log_file.file_name(), result_file_name, errno); + + } + else + { + /* Check that the output from test is equal to result file */ + check_result(); + } + } + else + { + /* + No result_file_name specified, the result + has been printed to stdout, exit with error + unless script has called "exit" to indicate success + */ + if (abort_flag == 0) + die("Exit with failure! Call 'exit' in script to return with sucess"); + } + } + else + { + die("The test didn't produce any output"); + } + + if (!command_executed && result_file_name) + die("No queries executed but result file found!"); + + timer_output(); + /* Yes, if we got this far the test has suceeded! Sakila smiles */ + cleanup_and_exit(0); + return 0; /* Keep compiler happy too */ +} + + +/* + A primitive timer that give results in milliseconds if the + --timer-file= 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. +*/ + +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)); + /* Timer has been written to the file, don't use it anymore */ + timer_file= 0; + } +} + + +ulonglong timer_now(void) +{ + return my_micro_time() / 1000; +} + + +/* + Get arguments for replace_columns. The syntax is: + replace-column column_number to_string [column_number to_string ...] + Where each argument may be quoted with ' or " + A argument may also be a variable, in which case the value of the + variable is replaced. +*/ + +void do_get_replace_column(struct st_command *command) +{ + char *from= command->first_argument; + char *buff, *start; + DBUG_ENTER("get_replace_columns"); + + free_replace_column(); + if (!*from) + die("Missing argument in %s", command->query); + + /* Allocate a buffer for results */ + start= buff= (char*)my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); + while (*from) + { + char *to; + uint column_number; + to= get_string(&buff, &from, command); + if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS) + die("Wrong column number to replace_column in '%s'", command->query); + if (!*from) + die("Wrong number of arguments to replace_column in '%s'", command->query); + to= get_string(&buff, &from, command); + my_free(replace_column[column_number-1], MY_ALLOW_ZERO_PTR); + replace_column[column_number-1]= my_strdup(to, MYF(MY_WME | MY_FAE)); + set_if_bigger(max_replace_column, column_number); + } + my_free(start, MYF(0)); + command->last_argument= command->end; +} + + +void free_replace_column() +{ + uint i; + for (i=0 ; i < max_replace_column ; i++) + { + if (replace_column[i]) + { + my_free(replace_column[i], 0); + replace_column[i]= 0; + } + } + max_replace_column= 0; +} + + +/****************************************************************************/ +/* + Replace functions +*/ + +/* Definitions for replace result */ + +typedef struct st_pointer_array { /* when using array-strings */ + TYPELIB typelib; /* Pointer to strings */ + uchar *str; /* Strings is here */ + int7 *flag; /* Flag about each var. */ + uint array_allocs,max_count,length,max_length; +} POINTER_ARRAY; + +struct st_replace; +struct st_replace *init_replace(char * *from, char * *to, uint count, + char * word_end_chars); +int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name); +void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, + const char *from, int len); +void free_pointer_array(POINTER_ARRAY *pa); + +struct st_replace *glob_replace; + +/* + Get arguments for replace. The syntax is: + replace from to [from to ...] + Where each argument may be quoted with ' or " + A argument may also be a variable, in which case the value of the + variable is replaced. +*/ + +void do_get_replace(struct st_command *command) +{ + uint i; + char *from= command->first_argument; + char *buff, *start; + char word_end_chars[256], *pos; + POINTER_ARRAY to_array, from_array; + DBUG_ENTER("get_replace"); + + free_replace(); + + bzero((char*) &to_array,sizeof(to_array)); + bzero((char*) &from_array,sizeof(from_array)); + if (!*from) + die("Missing argument in %s", command->query); + start= buff= (char*)my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); + while (*from) + { + char *to= buff; + to= get_string(&buff, &from, command); + if (!*from) + die("Wrong number of arguments to replace_result in '%s'", + command->query); +#ifdef __WIN__ + fix_win_paths(to, from - to); +#endif + insert_pointer_name(&from_array,to); + to= get_string(&buff, &from, command); + insert_pointer_name(&to_array,to); + } + for (i= 1,pos= word_end_chars ; i < 256 ; i++) + if (my_isspace(charset_info,i)) + *pos++= i; + *pos=0; /* End pointer */ + if (!(glob_replace= init_replace((char**) from_array.typelib.type_names, + (char**) to_array.typelib.type_names, + (uint) from_array.typelib.count, + word_end_chars))) + die("Can't initialize replace from '%s'", command->query); + free_pointer_array(&from_array); + free_pointer_array(&to_array); + my_free(start, MYF(0)); + command->last_argument= command->end; + DBUG_VOID_RETURN; +} + + +void free_replace() +{ + DBUG_ENTER("free_replace"); + if (glob_replace) + { + my_free(glob_replace,MYF(0)); + glob_replace=0; + } + DBUG_VOID_RETURN; +} + + +typedef struct st_replace { + my_bool found; + struct st_replace *next[256]; +} REPLACE; + +typedef struct st_replace_found { + my_bool found; + char *replace_string; + uint to_offset; + int from_offset; +} REPLACE_STRING; + + +void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, + const char *str, + int len __attribute__((unused))) +{ + reg1 REPLACE *rep_pos; + reg2 REPLACE_STRING *rep_str; + const char *start, *from; + DBUG_ENTER("replace_strings_append"); + + start= from= str; + rep_pos=rep+1; + for (;;) + { + /* Loop through states */ + DBUG_PRINT("info", ("Looping through states")); + while (!rep_pos->found) + rep_pos= rep_pos->next[(uchar) *from++]; + + /* Does this state contain a string to be replaced */ + if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string) + { + /* No match found */ + dynstr_append_mem(ds, start, from - start - 1); + DBUG_PRINT("exit", ("Found no more string to replace, appended: %s", start)); + DBUG_VOID_RETURN; + } + + /* Found a string that needs to be replaced */ + DBUG_PRINT("info", ("found: %d, to_offset: %d, from_offset: %d, string: %s", + rep_str->found, rep_str->to_offset, + rep_str->from_offset, rep_str->replace_string)); + + /* Append part of original string before replace string */ + dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start); + + /* Append replace string */ + dynstr_append_mem(ds, rep_str->replace_string, + strlen(rep_str->replace_string)); + + if (!*(from-=rep_str->from_offset) && rep_pos->found != 2) + { + /* End of from string */ + DBUG_PRINT("exit", ("Found end of from string")); + DBUG_VOID_RETURN; + } + DBUG_ASSERT(from <= str+len); + start= from; + rep_pos=rep; + } +} + + +/* + Regex replace functions +*/ + + +/* Stores regex substitutions */ + +struct st_regex +{ + char* pattern; /* Pattern to be replaced */ + char* replace; /* String or expression to replace the pattern with */ + int icase; /* true if the match is case insensitive */ +}; + +struct st_replace_regex +{ + DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */ + + /* + Temporary storage areas for substitutions. To reduce unnessary copying + and memory freeing/allocation, we pre-allocate two buffers, and alternate + their use, one for input/one for output, the roles changing on the next + st_regex substition. At the end of substitutions buf points to the + one containing the final result. + */ + char* buf; + char* even_buf; + char* odd_buf; + int even_buf_len; + int odd_buf_len; +}; + +struct st_replace_regex *glob_replace_regex= 0; + +int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, + char *string, int icase); + + + +/* + Finds the next (non-escaped) '/' in the expression. + (If the character '/' is needed, it can be escaped using '\'.) +*/ + +#define PARSE_REGEX_ARG \ + while (p < expr_end) \ + { \ + char c= *p; \ + if (c == '/') \ + { \ + if (last_c == '\\') \ + { \ + buf_p[-1]= '/'; \ + } \ + else \ + { \ + *buf_p++ = 0; \ + break; \ + } \ + } \ + else \ + *buf_p++ = c; \ + \ + last_c= c; \ + p++; \ + } \ + \ +/* + Initializes the regular substitution expression to be used in the + result output of test. + + Returns: st_replace_regex struct with pairs of substitutions +*/ + +struct st_replace_regex* init_replace_regex(char* expr) +{ + struct st_replace_regex* res; + char* buf,*expr_end; + char* p; + char* buf_p; + uint expr_len= strlen(expr); + char last_c = 0; + struct st_regex reg; + + /* my_malloc() will die on fail with MY_FAE */ + res=(struct st_replace_regex*)my_malloc( + sizeof(*res)+expr_len ,MYF(MY_FAE+MY_WME)); + my_init_dynamic_array(&res->regex_arr,sizeof(struct st_regex),128,128); + + buf= (char*)res + sizeof(*res); + expr_end= expr + expr_len; + p= expr; + buf_p= buf; + + /* for each regexp substitution statement */ + while (p < expr_end) + { + bzero(®,sizeof(reg)); + /* find the start of the statement */ + while (p < expr_end) + { + if (*p == '/') + break; + p++; + } + + if (p == expr_end || ++p == expr_end) + { + if (res->regex_arr.elements) + break; + else + goto err; + } + /* we found the start */ + reg.pattern= buf_p; + + /* Find first argument -- pattern string to be removed */ + PARSE_REGEX_ARG + + if (p == expr_end || ++p == expr_end) + goto err; + + /* buf_p now points to the replacement pattern terminated with \0 */ + reg.replace= buf_p; + + /* Find second argument -- replace string to replace pattern */ + PARSE_REGEX_ARG + + if (p == expr_end) + goto err; + + /* skip the ending '/' in the statement */ + p++; + + /* Check if we should do matching case insensitive */ + if (p < expr_end && *p == 'i') + reg.icase= 1; + + /* done parsing the statement, now place it in regex_arr */ + if (insert_dynamic(&res->regex_arr,(uchar*) ®)) + die("Out of memory"); + } + res->odd_buf_len= res->even_buf_len= 8192; + res->even_buf= (char*)my_malloc(res->even_buf_len,MYF(MY_WME+MY_FAE)); + res->odd_buf= (char*)my_malloc(res->odd_buf_len,MYF(MY_WME+MY_FAE)); + res->buf= res->even_buf; + + return res; + +err: + my_free(res,0); + die("Error parsing replace_regex \"%s\"", expr); + return 0; +} + +/* + Execute all substitutions on val. + + Returns: true if substituition was made, false otherwise + Side-effect: Sets r->buf to be the buffer with all substitutions done. + + IN: + struct st_replace_regex* r + char* val + Out: + struct st_replace_regex* r + r->buf points at the resulting buffer + r->even_buf and r->odd_buf might have been reallocated + r->even_buf_len and r->odd_buf_len might have been changed + + TODO: at some point figure out if there is a way to do everything + in one pass +*/ + +int multi_reg_replace(struct st_replace_regex* r,char* val) +{ + uint i; + char* in_buf, *out_buf; + int* buf_len_p; + + in_buf= val; + out_buf= r->even_buf; + buf_len_p= &r->even_buf_len; + r->buf= 0; + + /* For each substitution, do the replace */ + for (i= 0; i < r->regex_arr.elements; i++) + { + struct st_regex re; + char* save_out_buf= out_buf; + + get_dynamic(&r->regex_arr,(uchar*)&re,i); + + if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace, + in_buf, re.icase)) + { + /* if the buffer has been reallocated, make adjustements */ + if (save_out_buf != out_buf) + { + if (save_out_buf == r->even_buf) + r->even_buf= out_buf; + else + r->odd_buf= out_buf; + } + + r->buf= out_buf; + if (in_buf == val) + in_buf= r->odd_buf; + + swap_variables(char*,in_buf,out_buf); + + buf_len_p= (out_buf == r->even_buf) ? &r->even_buf_len : + &r->odd_buf_len; + } + } + + return (r->buf == 0); +} + +/* + Parse the regular expression to be used in all result files + from now on. + + The syntax is --replace_regex /from/to/i /from/to/i ... + i means case-insensitive match. If omitted, the match is + case-sensitive + +*/ +void do_get_replace_regex(struct st_command *command) +{ + char *expr= command->first_argument; + free_replace_regex(); + if (!(glob_replace_regex=init_replace_regex(expr))) + die("Could not init replace_regex"); + command->last_argument= command->end; +} + +void free_replace_regex() +{ + if (glob_replace_regex) + { + delete_dynamic(&glob_replace_regex->regex_arr); + my_free(glob_replace_regex->even_buf,MYF(MY_ALLOW_ZERO_PTR)); + my_free(glob_replace_regex->odd_buf,MYF(MY_ALLOW_ZERO_PTR)); + my_free(glob_replace_regex,MYF(0)); + glob_replace_regex=0; + } +} + + + +/* + auxiluary macro used by reg_replace + makes sure the result buffer has sufficient length +*/ +#define SECURE_REG_BUF if (buf_len < need_buf_len) \ + { \ + int off= res_p - buf; \ + buf= (char*)my_realloc(buf,need_buf_len,MYF(MY_WME+MY_FAE)); \ + res_p= buf + off; \ + buf_len= need_buf_len; \ + } \ + \ +/* + Performs a regex substitution + + IN: + + buf_p - result buffer pointer. Will change if reallocated + buf_len_p - result buffer length. Will change if the buffer is reallocated + pattern - regexp pattern to match + replace - replacement expression + string - the string to perform substituions in + icase - flag, if set to 1 the match is case insensitive +*/ +int reg_replace(char** buf_p, int* buf_len_p, char *pattern, + char *replace, char *string, int icase) +{ + my_regex_t r; + my_regmatch_t *subs; + char *replace_end; + char *buf= *buf_p; + int len; + int buf_len, need_buf_len; + int cflags= REG_EXTENDED; + int err_code; + char *res_p,*str_p,*str_end; + + buf_len= *buf_len_p; + len= strlen(string); + str_end= string + len; + + /* start with a buffer of a reasonable size that hopefully will not + need to be reallocated + */ + need_buf_len= len * 2 + 1; + res_p= buf; + + SECURE_REG_BUF + + if (icase) + cflags|= REG_ICASE; + + if ((err_code= my_regcomp(&r,pattern,cflags,&my_charset_latin1))) + { + check_regerr(&r,err_code); + return 1; + } + + subs= (my_regmatch_t*)my_malloc(sizeof(my_regmatch_t) * (r.re_nsub+1), + MYF(MY_WME+MY_FAE)); + + *res_p= 0; + str_p= string; + replace_end= replace + strlen(replace); + + /* for each pattern match instance perform a replacement */ + while (!err_code) + { + /* find the match */ + err_code= my_regexec(&r,str_p, r.re_nsub+1, subs, + (str_p == string) ? REG_NOTBOL : 0); + + /* if regular expression error (eg. bad syntax, or out of memory) */ + if (err_code && err_code != REG_NOMATCH) + { + check_regerr(&r,err_code); + my_regfree(&r); + return 1; + } + + /* if match found */ + if (!err_code) + { + char* expr_p= replace; + int c; + + /* + we need at least what we have so far in the buffer + the part + before this match + */ + need_buf_len= (res_p - buf) + (int) subs[0].rm_so; + + /* on this pass, calculate the memory for the result buffer */ + while (expr_p < replace_end) + { + int back_ref_num= -1; + c= *expr_p; + + if (c == '\\' && expr_p + 1 < replace_end) + { + back_ref_num= (int) (expr_p[1] - '0'); + } + + /* found a valid back_ref (eg. \1)*/ + if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) + { + regoff_t start_off, end_off; + if ((start_off=subs[back_ref_num].rm_so) > -1 && + (end_off=subs[back_ref_num].rm_eo) > -1) + { + need_buf_len += (int) (end_off - start_off); + } + expr_p += 2; + } + else + { + expr_p++; + need_buf_len++; + } + } + need_buf_len++; + /* + now that we know the size of the buffer, + make sure it is big enough + */ + SECURE_REG_BUF + + /* copy the pre-match part */ + if (subs[0].rm_so) + { + memcpy(res_p, str_p, (size_t) subs[0].rm_so); + res_p+= subs[0].rm_so; + } + + expr_p= replace; + + /* copy the match and expand back_refs */ + while (expr_p < replace_end) + { + int back_ref_num= -1; + c= *expr_p; + + if (c == '\\' && expr_p + 1 < replace_end) + { + back_ref_num= expr_p[1] - '0'; + } + + if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) + { + regoff_t start_off, end_off; + if ((start_off=subs[back_ref_num].rm_so) > -1 && + (end_off=subs[back_ref_num].rm_eo) > -1) + { + int block_len= (int) (end_off - start_off); + memcpy(res_p,str_p + start_off, block_len); + res_p += block_len; + } + expr_p += 2; + } + else + { + *res_p++ = *expr_p++; + } + } + + /* handle the post-match part */ + if (subs[0].rm_so == subs[0].rm_eo) + { + if (str_p + subs[0].rm_so >= str_end) + break; + str_p += subs[0].rm_eo ; + *res_p++ = *str_p++; + } + else + { + str_p += subs[0].rm_eo; + } + } + else /* no match this time, just copy the string as is */ + { + int left_in_str= str_end-str_p; + need_buf_len= (res_p-buf) + left_in_str; + SECURE_REG_BUF + memcpy(res_p,str_p,left_in_str); + res_p += left_in_str; + str_p= str_end; + } + } + my_free(subs, MYF(0)); + my_regfree(&r); + *res_p= 0; + *buf_p= buf; + *buf_len_p= buf_len; + return 0; +} + + +#ifndef WORD_BIT +#define WORD_BIT (8*sizeof(uint)) +#endif + +#define SET_MALLOC_HUNC 64 +#define LAST_CHAR_CODE 259 + +typedef struct st_rep_set { + uint *bits; /* Pointer to used sets */ + short next[LAST_CHAR_CODE]; /* Pointer to next sets */ + uint found_len; /* Best match to date */ + int found_offset; + uint table_offset; + uint size_of_bits; /* For convinience */ +} REP_SET; + +typedef struct st_rep_sets { + uint count; /* Number of sets */ + uint extra; /* Extra sets in buffer */ + uint invisible; /* Sets not chown */ + uint size_of_bits; + REP_SET *set,*set_buffer; + uint *bit_buffer; +} REP_SETS; + +typedef struct st_found_set { + uint table_offset; + int found_offset; +} FOUND_SET; + +typedef struct st_follow { + int chr; + uint table_offset; + uint len; +} FOLLOWS; + + +int init_sets(REP_SETS *sets,uint states); +REP_SET *make_new_set(REP_SETS *sets); +void make_sets_invisible(REP_SETS *sets); +void free_last_set(REP_SETS *sets); +void free_sets(REP_SETS *sets); +void internal_set_bit(REP_SET *set, uint bit); +void internal_clear_bit(REP_SET *set, uint bit); +void or_bits(REP_SET *to,REP_SET *from); +void copy_bits(REP_SET *to,REP_SET *from); +int cmp_bits(REP_SET *set1,REP_SET *set2); +int get_next_bit(REP_SET *set,uint lastpos); +int find_set(REP_SETS *sets,REP_SET *find); +int find_found(FOUND_SET *found_set,uint table_offset, + int found_offset); +uint start_at_word(char * pos); +uint end_of_word(char * pos); + +static uint found_sets=0; + + +uint replace_len(char * str) +{ + uint len=0; + while (*str) + { + str++; + len++; + } + return len; +} + +/* Init a replace structure for further calls */ + +REPLACE *init_replace(char * *from, char * *to,uint count, + char * word_end_chars) +{ + static const int SPACE_CHAR= 256; + static const int END_OF_LINE= 258; + + uint i,j,states,set_nr,len,result_len,max_length,found_end,bits_set,bit_nr; + int used_sets,chr,default_state; + char used_chars[LAST_CHAR_CODE],is_word_end[256]; + char * pos, *to_pos, **to_array; + REP_SETS sets; + REP_SET *set,*start_states,*word_states,*new_set; + FOLLOWS *follow,*follow_ptr; + REPLACE *replace; + FOUND_SET *found_set; + REPLACE_STRING *rep_str; + DBUG_ENTER("init_replace"); + + /* Count number of states */ + for (i=result_len=max_length=0 , states=2 ; i < count ; i++) + { + len=replace_len(from[i]); + if (!len) + { + errno=EINVAL; + DBUG_RETURN(0); + } + states+=len+1; + result_len+=(uint) strlen(to[i])+1; + if (len > max_length) + max_length=len; + } + bzero((char*) is_word_end,sizeof(is_word_end)); + for (i=0 ; word_end_chars[i] ; i++) + is_word_end[(uchar) word_end_chars[i]]=1; + + if (init_sets(&sets,states)) + DBUG_RETURN(0); + found_sets=0; + if (!(found_set= (FOUND_SET*) my_malloc(sizeof(FOUND_SET)*max_length*count, + MYF(MY_WME)))) + { + free_sets(&sets); + DBUG_RETURN(0); + } + VOID(make_new_set(&sets)); /* Set starting set */ + make_sets_invisible(&sets); /* Hide previus sets */ + used_sets=-1; + word_states=make_new_set(&sets); /* Start of new word */ + start_states=make_new_set(&sets); /* This is first state */ + if (!(follow=(FOLLOWS*) my_malloc((states+2)*sizeof(FOLLOWS),MYF(MY_WME)))) + { + free_sets(&sets); + my_free(found_set,MYF(0)); + DBUG_RETURN(0); + } + + /* Init follow_ptr[] */ + for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++) + { + if (from[i][0] == '\\' && from[i][1] == '^') + { + internal_set_bit(start_states,states+1); + if (!from[i][2]) + { + start_states->table_offset=i; + start_states->found_offset=1; + } + } + else if (from[i][0] == '\\' && from[i][1] == '$') + { + internal_set_bit(start_states,states); + internal_set_bit(word_states,states); + if (!from[i][2] && start_states->table_offset == (uint) ~0) + { + start_states->table_offset=i; + start_states->found_offset=0; + } + } + else + { + internal_set_bit(word_states,states); + if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2])) + internal_set_bit(start_states,states+1); + else + internal_set_bit(start_states,states); + } + for (pos=from[i], len=0; *pos ; pos++) + { + follow_ptr->chr= (uchar) *pos; + follow_ptr->table_offset=i; + follow_ptr->len= ++len; + follow_ptr++; + } + follow_ptr->chr=0; + follow_ptr->table_offset=i; + follow_ptr->len=len; + follow_ptr++; + states+=(uint) len+1; + } + + + for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++) + { + set=sets.set+set_nr; + default_state= 0; /* Start from beginning */ + + /* If end of found-string not found or start-set with current set */ + + for (i= (uint) ~0; (i=get_next_bit(set,i)) ;) + { + if (!follow[i].chr) + { + if (! default_state) + default_state= find_found(found_set,set->table_offset, + set->found_offset+1); + } + } + copy_bits(sets.set+used_sets,set); /* Save set for changes */ + if (!default_state) + or_bits(sets.set+used_sets,sets.set); /* Can restart from start */ + + /* Find all chars that follows current sets */ + bzero((char*) used_chars,sizeof(used_chars)); + for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;) + { + used_chars[follow[i].chr]=1; + if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr && + follow[i].len > 1) || follow[i].chr == END_OF_LINE) + used_chars[0]=1; + } + + /* Mark word_chars used if \b is in state */ + if (used_chars[SPACE_CHAR]) + for (pos= word_end_chars ; *pos ; pos++) + used_chars[(int) (uchar) *pos] = 1; + + /* Handle other used characters */ + for (chr= 0 ; chr < 256 ; chr++) + { + if (! used_chars[chr]) + set->next[chr]= chr ? default_state : -1; + else + { + new_set=make_new_set(&sets); + set=sets.set+set_nr; /* if realloc */ + new_set->table_offset=set->table_offset; + new_set->found_len=set->found_len; + new_set->found_offset=set->found_offset+1; + found_end=0; + + for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; ) + { + if (!follow[i].chr || follow[i].chr == chr || + (follow[i].chr == SPACE_CHAR && + (is_word_end[chr] || + (!chr && follow[i].len > 1 && ! follow[i+1].chr))) || + (follow[i].chr == END_OF_LINE && ! chr)) + { + if ((! chr || (follow[i].chr && !follow[i+1].chr)) && + follow[i].len > found_end) + found_end=follow[i].len; + if (chr && follow[i].chr) + internal_set_bit(new_set,i+1); /* To next set */ + else + internal_set_bit(new_set,i); + } + } + if (found_end) + { + new_set->found_len=0; /* Set for testing if first */ + bits_set=0; + for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;) + { + if ((follow[i].chr == SPACE_CHAR || + follow[i].chr == END_OF_LINE) && ! chr) + bit_nr=i+1; + else + bit_nr=i; + if (follow[bit_nr-1].len < found_end || + (new_set->found_len && + (chr == 0 || !follow[bit_nr].chr))) + internal_clear_bit(new_set,i); + else + { + if (chr == 0 || !follow[bit_nr].chr) + { /* best match */ + new_set->table_offset=follow[bit_nr].table_offset; + if (chr || (follow[i].chr == SPACE_CHAR || + follow[i].chr == END_OF_LINE)) + new_set->found_offset=found_end; /* New match */ + new_set->found_len=found_end; + } + bits_set++; + } + } + if (bits_set == 1) + { + set->next[chr] = find_found(found_set, + new_set->table_offset, + new_set->found_offset); + free_last_set(&sets); + } + else + set->next[chr] = find_set(&sets,new_set); + } + else + set->next[chr] = find_set(&sets,new_set); + } + } + } + + /* Alloc replace structure for the replace-state-machine */ + + if ((replace=(REPLACE*) my_malloc(sizeof(REPLACE)*(sets.count)+ + sizeof(REPLACE_STRING)*(found_sets+1)+ + sizeof(char *)*count+result_len, + MYF(MY_WME | MY_ZEROFILL)))) + { + rep_str=(REPLACE_STRING*) (replace+sets.count); + to_array= (char **) (rep_str+found_sets+1); + to_pos=(char *) (to_array+count); + for (i=0 ; i < count ; i++) + { + to_array[i]=to_pos; + to_pos=strmov(to_pos,to[i])+1; + } + rep_str[0].found=1; + rep_str[0].replace_string=0; + for (i=1 ; i <= found_sets ; i++) + { + pos=from[found_set[i-1].table_offset]; + rep_str[i].found= !bcmp((const uchar*) pos, + (const uchar*) "\\^", 3) ? 2 : 1; + rep_str[i].replace_string=to_array[found_set[i-1].table_offset]; + rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos); + rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+ + end_of_word(pos); + } + for (i=0 ; i < sets.count ; i++) + { + for (j=0 ; j < 256 ; j++) + if (sets.set[i].next[j] >= 0) + replace[i].next[j]=replace+sets.set[i].next[j]; + else + replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1)); + } + } + my_free(follow,MYF(0)); + free_sets(&sets); + my_free(found_set,MYF(0)); + DBUG_PRINT("exit",("Replace table has %d states",sets.count)); + DBUG_RETURN(replace); +} + + +int init_sets(REP_SETS *sets,uint states) +{ + bzero((char*) sets,sizeof(*sets)); + sets->size_of_bits=((states+7)/8); + if (!(sets->set_buffer=(REP_SET*) my_malloc(sizeof(REP_SET)*SET_MALLOC_HUNC, + MYF(MY_WME)))) + return 1; + if (!(sets->bit_buffer=(uint*) my_malloc(sizeof(uint)*sets->size_of_bits* + SET_MALLOC_HUNC,MYF(MY_WME)))) + { + my_free(sets->set,MYF(0)); + return 1; + } + return 0; +} + +/* Make help sets invisible for nicer codeing */ + +void make_sets_invisible(REP_SETS *sets) +{ + sets->invisible=sets->count; + sets->set+=sets->count; + sets->count=0; +} + +REP_SET *make_new_set(REP_SETS *sets) +{ + uint i,count,*bit_buffer; + REP_SET *set; + if (sets->extra) + { + sets->extra--; + set=sets->set+ sets->count++; + bzero((char*) set->bits,sizeof(uint)*sets->size_of_bits); + bzero((char*) &set->next[0],sizeof(set->next[0])*LAST_CHAR_CODE); + set->found_offset=0; + set->found_len=0; + set->table_offset= (uint) ~0; + set->size_of_bits=sets->size_of_bits; + return set; + } + count=sets->count+sets->invisible+SET_MALLOC_HUNC; + if (!(set=(REP_SET*) my_realloc((uchar*) sets->set_buffer, + sizeof(REP_SET)*count, + MYF(MY_WME)))) + return 0; + sets->set_buffer=set; + sets->set=set+sets->invisible; + if (!(bit_buffer=(uint*) my_realloc((uchar*) sets->bit_buffer, + (sizeof(uint)*sets->size_of_bits)*count, + MYF(MY_WME)))) + return 0; + sets->bit_buffer=bit_buffer; + for (i=0 ; i < count ; i++) + { + sets->set_buffer[i].bits=bit_buffer; + bit_buffer+=sets->size_of_bits; + } + sets->extra=SET_MALLOC_HUNC; + return make_new_set(sets); +} + +void free_last_set(REP_SETS *sets) +{ + sets->count--; + sets->extra++; + return; +} + +void free_sets(REP_SETS *sets) +{ + my_free(sets->set_buffer,MYF(0)); + my_free(sets->bit_buffer,MYF(0)); + return; +} + +void internal_set_bit(REP_SET *set, uint bit) +{ + set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT); + return; +} + +void internal_clear_bit(REP_SET *set, uint bit) +{ + set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT)); + return; +} + + +void or_bits(REP_SET *to,REP_SET *from) +{ + reg1 uint i; + for (i=0 ; i < to->size_of_bits ; i++) + to->bits[i]|=from->bits[i]; + return; +} + +void copy_bits(REP_SET *to,REP_SET *from) +{ + memcpy((uchar*) to->bits,(uchar*) from->bits, + (size_t) (sizeof(uint) * to->size_of_bits)); +} + +int cmp_bits(REP_SET *set1,REP_SET *set2) +{ + return bcmp((uchar*) set1->bits,(uchar*) set2->bits, + sizeof(uint) * set1->size_of_bits); +} + + +/* Get next set bit from set. */ + +int get_next_bit(REP_SET *set,uint lastpos) +{ + uint pos,*start,*end,bits; + + start=set->bits+ ((lastpos+1) / WORD_BIT); + end=set->bits + set->size_of_bits; + bits=start[0] & ~((1 << ((lastpos+1) % WORD_BIT)) -1); + + while (! bits && ++start < end) + bits=start[0]; + if (!bits) + return 0; + pos=(uint) (start-set->bits)*WORD_BIT; + while (! (bits & 1)) + { + bits>>=1; + pos++; + } + return pos; +} + +/* find if there is a same set in sets. If there is, use it and + free given set, else put in given set in sets and return its + position */ + +int find_set(REP_SETS *sets,REP_SET *find) +{ + uint i; + for (i=0 ; i < sets->count-1 ; i++) + { + if (!cmp_bits(sets->set+i,find)) + { + free_last_set(sets); + return i; + } + } + return i; /* return new postion */ +} + +/* find if there is a found_set with same table_offset & found_offset + If there is return offset to it, else add new offset and return pos. + Pos returned is -offset-2 in found_set_structure because it is + saved in set->next and set->next[] >= 0 points to next set and + set->next[] == -1 is reserved for end without replaces. +*/ + +int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) +{ + int i; + for (i=0 ; (uint) i < found_sets ; i++) + if (found_set[i].table_offset == table_offset && + found_set[i].found_offset == found_offset) + return -i-2; + found_set[i].table_offset=table_offset; + found_set[i].found_offset=found_offset; + found_sets++; + return -i-2; /* return new postion */ +} + +/* Return 1 if regexp starts with \b or ends with \b*/ + +uint start_at_word(char * pos) +{ + return (((!bcmp((const uchar*) pos, (const uchar*) "\\b",2) && pos[2]) || + !bcmp((const uchar*) pos, (const uchar*) "\\^", 2)) ? 1 : 0); +} + +uint end_of_word(char * pos) +{ + char * end=strend(pos); + return ((end > pos+2 && !bcmp((const uchar*) end-2, + (const uchar*) "\\b", 2)) || + (end >= pos+2 && !bcmp((const uchar*) end-2, + (const uchar*) "\\$",2))) ? 1 : 0; +} + +/**************************************************************************** + * Handle replacement of strings + ****************************************************************************/ + +#define PC_MALLOC 256 /* Bytes for pointers */ +#define PS_MALLOC 512 /* Bytes for data */ + +int insert_pointer_name(reg1 POINTER_ARRAY *pa,char * name) +{ + uint i,length,old_count; + uchar *new_pos; + const char **new_array; + DBUG_ENTER("insert_pointer_name"); + + if (! pa->typelib.count) + { + if (!(pa->typelib.type_names=(const char **) + my_malloc(((PC_MALLOC-MALLOC_OVERHEAD)/ + (sizeof(char *)+sizeof(*pa->flag))* + (sizeof(char *)+sizeof(*pa->flag))),MYF(MY_WME)))) + DBUG_RETURN(-1); + if (!(pa->str= (uchar*) my_malloc((uint) (PS_MALLOC-MALLOC_OVERHEAD), + MYF(MY_WME)))) + { + my_free((char*) pa->typelib.type_names,MYF(0)); + DBUG_RETURN (-1); + } + pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(uchar*)+ + sizeof(*pa->flag)); + pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); + pa->length=0; + pa->max_length=PS_MALLOC-MALLOC_OVERHEAD; + pa->array_allocs=1; + } + length=(uint) strlen(name)+1; + if (pa->length+length >= pa->max_length) + { + if (!(new_pos= (uchar*) my_realloc((uchar*) pa->str, + (uint) (pa->max_length+PS_MALLOC), + MYF(MY_WME)))) + DBUG_RETURN(1); + if (new_pos != pa->str) + { + my_ptrdiff_t diff=PTR_BYTE_DIFF(new_pos,pa->str); + for (i=0 ; i < pa->typelib.count ; i++) + pa->typelib.type_names[i]= ADD_TO_PTR(pa->typelib.type_names[i],diff, + char*); + pa->str=new_pos; + } + pa->max_length+=PS_MALLOC; + } + if (pa->typelib.count >= pa->max_count-1) + { + int len; + pa->array_allocs++; + len=(PC_MALLOC*pa->array_allocs - MALLOC_OVERHEAD); + if (!(new_array=(const char **) my_realloc((uchar*) pa->typelib.type_names, + (uint) len/ + (sizeof(uchar*)+sizeof(*pa->flag))* + (sizeof(uchar*)+sizeof(*pa->flag)), + MYF(MY_WME)))) + DBUG_RETURN(1); + pa->typelib.type_names=new_array; + old_count=pa->max_count; + pa->max_count=len/(sizeof(uchar*) + sizeof(*pa->flag)); + pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); + memcpy((uchar*) pa->flag,(char *) (pa->typelib.type_names+old_count), + old_count*sizeof(*pa->flag)); + } + pa->flag[pa->typelib.count]=0; /* Reset flag */ + pa->typelib.type_names[pa->typelib.count++]= (char*) pa->str+pa->length; + pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ + VOID(strmov((char*) pa->str+pa->length,name)); + pa->length+=length; + DBUG_RETURN(0); +} /* insert_pointer_name */ + + +/* free pointer array */ + +void free_pointer_array(POINTER_ARRAY *pa) +{ + if (pa->typelib.count) + { + pa->typelib.count=0; + my_free((char*) pa->typelib.type_names,MYF(0)); + pa->typelib.type_names=0; + my_free(pa->str,MYF(0)); + } +} /* free_pointer_array */ + + +/* Functions that uses replace and replace_regex */ + +/* Append the string to ds, with optional replace */ +void replace_dynstr_append_mem(DYNAMIC_STRING *ds, + const char *val, int len) +{ +#ifdef __WIN__ + fix_win_paths(val, len); +#endif + + if (glob_replace_regex) + { + /* Regex replace */ + if (!multi_reg_replace(glob_replace_regex, (char*)val)) + { + val= glob_replace_regex->buf; + len= strlen(val); + } + } + + if (glob_replace) + { + /* Normal replace */ + replace_strings_append(glob_replace, ds, val, len); + } + else + dynstr_append_mem(ds, val, len); +} + + +/* Append zero-terminated string to ds, with optional replace */ +void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) +{ + replace_dynstr_append_mem(ds, val, strlen(val)); +} + +/* Append uint to ds, with optional replace */ +void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) +{ + char buff[22]; /* This should be enough for any int */ + char *end= longlong10_to_str(val, buff, 10); + replace_dynstr_append_mem(ds, buff, end - buff); +} + + + +/* + Build a list of pointer to each line in ds_input, sort + the list and use the sorted list to append the strings + sorted to the output ds + + SYNOPSIS + dynstr_append_sorted + ds - string where the sorted output will be appended + ds_input - string to be sorted + +*/ + +static int comp_lines(const char **a, const char **b) +{ + return (strcmp(*a,*b)); +} + +void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING *ds_input) +{ + unsigned i; + char *start= ds_input->str; + DYNAMIC_ARRAY lines; + DBUG_ENTER("dynstr_append_sorted"); + + if (!*start) + DBUG_VOID_RETURN; /* No input */ + + my_init_dynamic_array(&lines, sizeof(const char*), 32, 32); + + /* First line is result header, skip past it */ + while (*start && *start != '\n') + start++; + start++; /* Skip past \n */ + dynstr_append_mem(ds, ds_input->str, start - ds_input->str); + + /* Insert line(s) in array */ + while (*start) + { + char* line_end= (char*)start; + + /* Find end of line */ + while (*line_end && *line_end != '\n') + line_end++; + *line_end= 0; + + /* Insert pointer to the line in array */ + if (insert_dynamic(&lines, (uchar*) &start)) + die("Out of memory inserting lines to sort"); + + start= line_end+1; + } + + /* Sort array */ + qsort(lines.buffer, lines.elements, + sizeof(char**), (qsort_cmp)comp_lines); + + /* Create new result */ + for (i= 0; i < lines.elements ; i++) + { + const char **line= dynamic_element(&lines, i, const char**); + dynstr_append(ds, *line); + dynstr_append(ds, "\n"); + } + + delete_dynamic(&lines); + DBUG_VOID_RETURN; +} diff --git a/libmysqld/examples/CMakeLists.txt b/libmysqld/examples/CMakeLists.txt index a07d089c757..13e5ed1eb7f 100644 --- a/libmysqld/examples/CMakeLists.txt +++ b/libmysqld/examples/CMakeLists.txt @@ -32,7 +32,7 @@ ADD_EXECUTABLE(mysql_embedded ../../client/completion_hash.cc TARGET_LINK_LIBRARIES(mysql_embedded mysys yassl taocrypt zlib debug dbug regex strings wsock32) ADD_DEPENDENCIES(mysql_embedded libmysqld) -ADD_EXECUTABLE(mysqltest_embedded ../../client/mysqltest.c) +ADD_EXECUTABLE(mysqltest_embedded ../../client/mysqltest.cc TARGET_LINK_LIBRARIES(mysqltest_embedded mysys yassl taocrypt zlib debug dbug regex strings wsock32) ADD_DEPENDENCIES(mysqltest_embedded libmysqld) diff --git a/libmysqld/examples/Makefile.am b/libmysqld/examples/Makefile.am index 3425b48645f..109d33a85ae 100644 --- a/libmysqld/examples/Makefile.am +++ b/libmysqld/examples/Makefile.am @@ -41,7 +41,7 @@ LDADD = @CLIENT_EXTRA_LDFLAGS@ ../libmysqld.a @LIBDL@ $(CXXLDFLAGS) \ @NDB_SCI_LIBS@ mysqltest_embedded_LINK = $(CXXLINK) -nodist_mysqltest_embedded_SOURCES = mysqltest.c +nodist_mysqltest_embedded_SOURCES = mysqltest.cc mysqltest_embedded_LDADD = $(LDADD) $(top_builddir)/regex/libregex.a nodist_mysql_SOURCES = mysql.cc readline.cc completion_hash.cc \ diff --git a/mysql-test/include/analyze-sync_with_master.test b/mysql-test/include/analyze-sync_with_master.test new file mode 100644 index 00000000000..684c0dbbab7 --- /dev/null +++ b/mysql-test/include/analyze-sync_with_master.test @@ -0,0 +1,6 @@ +SHOW PROCESSLIST; + +let $binlog_name= query_get_value("SHOW MASTER STATUS", File, 1); +eval SHOW BINLOG EVENTS IN '$binlog_name'; + +exit; \ No newline at end of file diff --git a/mysql-test/include/analyze_failure_sync_with_master.test b/mysql-test/include/analyze_failure_sync_with_master.test deleted file mode 100644 index e6fd32d2f46..00000000000 --- a/mysql-test/include/analyze_failure_sync_with_master.test +++ /dev/null @@ -1,15 +0,0 @@ -# Connect to both master and slave -connect (master,127.0.0.1,root,,test,$MASTER_MYPORT,); -connect (slave,127.0.0.1,root,,test,$SLAVE_MYPORT,); - -vertical_results; - -echo == MASTER ===========================================================; -connection master; -show master status; -show slave status; - -echo == SLAVE ===========================================================; -connection slave; -show master status; -show slave status; diff --git a/mysql-test/include/mysqltest-x.inc b/mysql-test/include/mysqltest-x.inc index dd1468aed07..797c5c39f3f 100644 --- a/mysql-test/include/mysqltest-x.inc +++ b/mysql-test/include/mysqltest-x.inc @@ -1,2 +1,3 @@ echo Output from mysqltest-x.inc; +exit; diff --git a/mysql-test/lib/mtr_cases.pm b/mysql-test/lib/mtr_cases.pm index ad0c82e8a74..904276854ee 100644 --- a/mysql-test/lib/mtr_cases.pm +++ b/mysql-test/lib/mtr_cases.pm @@ -677,12 +677,16 @@ sub collect_one_test_case { name => "$suitename.$tname", path => "$testdir/$filename", - # TODO allow nonexistsing result file - # in that case .test must issue "exit" otherwise test - # should fail by default - result_file => "$resdir/$tname.result", ); + my $result_file= "$resdir/$tname.result"; + if (-f $result_file) { + # Allow nonexistsing result file + # in that case .test must issue "exit" otherwise test + # should fail by default + $tinfo->{result_file}= $result_file; + } + # ---------------------------------------------------------------------- # Skip some tests but include in list, just mark them as skipped # ---------------------------------------------------------------------- diff --git a/mysql-test/lib/mtr_report.pm b/mysql-test/lib/mtr_report.pm index b5c868cb20a..6a46a372c45 100644 --- a/mysql-test/lib/mtr_report.pm +++ b/mysql-test/lib/mtr_report.pm @@ -103,17 +103,24 @@ sub mtr_report_test ($) { my ($tinfo)= @_; _mtr_report_test_name($tinfo); - if ($tinfo->{'result'} eq 'MTR_RES_FAILED'){ + my $comment= $tinfo->{'comment'}; + my $logfile= $tinfo->{'logfile'}; + my $warnings= $tinfo->{'warnings'}; + my $result= $tinfo->{'result'}; - if ( defined $tinfo->{'warnings'} ) + if ($result eq 'MTR_RES_FAILED'){ + + if ( $warnings ) { mtr_report("[ fail ] Found warnings in server log file!"); - mtr_report($tinfo->{'warnings'}); + mtr_report($warnings); return; } - if ( defined $tinfo->{'timeout'} ) + my $timeout= $tinfo->{'timeout'}; + if ( $timeout ) { - mtr_report("[ fail ] timeout"); + mtr_report("[ fail ] timeout after $timeout minutes"); + mtr_report("\n$tinfo->{'comment'}"); return; } else @@ -121,43 +128,42 @@ sub mtr_report_test ($) { mtr_report("[ fail ]"); } - if ( $tinfo->{'comment'} ) + if ( $logfile ) + { + # Test failure was detected by test tool and its report + # about what failed has been saved to file. Display the report. + mtr_report("\n$logfile\n"); + } + if ( $comment ) { # The test failure has been detected by mysql-test-run.pl # when starting the servers or due to other error, the reason for # failing the test is saved in "comment" - mtr_report("\nERROR: $tinfo->{'comment'}"); + mtr_report("\n$comment\n"); } - elsif ( $tinfo->{logfile} ) - { - # Test failure was detected by test tool and its report - # about what failed has been saved to file. Display the report. - mtr_report("\n"); - mtr_report($tinfo->{logfile}, "\n"); - } - else + if ( !$logfile and !$comment ) { # Neither this script or the test tool has recorded info # about why the test has failed. Should be debugged. - mtr_report("\nUnexpected termination, probably when starting mysqld");; + mtr_report("\nUnknown result, neither 'comment' or 'logfile' set"); } } - elsif ($tinfo->{'result'} eq 'MTR_RES_SKIPPED') + elsif ($result eq 'MTR_RES_SKIPPED') { if ( $tinfo->{'disable'} ) { - mtr_report("[ disabled ] $tinfo->{'comment'}"); + mtr_report("[ disabled ] $comment"); } - elsif ( $tinfo->{'comment'} ) + elsif ( $comment ) { if ( $tinfo->{skip_detected_by_test} ) { - mtr_report("[ skip ]. $tinfo->{'comment'}"); + mtr_report("[ skip ]. $comment"); } else { - mtr_report("[ skip ] $tinfo->{'comment'}"); + mtr_report("[ skip ] $comment"); } } else @@ -165,7 +171,7 @@ sub mtr_report_test ($) { mtr_report("[ skip ]"); } } - elsif ($tinfo->{'result'} eq 'MTR_RES_PASSED') + elsif ($result eq 'MTR_RES_PASSED') { my $timer_str= $tinfo->{timer} || ""; $tot_real_time += ($timer_str/1000); diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index e7589562e87..19178bb5482 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -164,7 +164,7 @@ my $opt_repeat= 1; my $opt_retry= 3; my $opt_retry_failure= 2; -my $opt_parallel; +my $opt_parallel= $ENV{MTR_PARALLEL}; my $opt_strace_client; @@ -291,8 +291,9 @@ sub main { my $tinfo = My::Test->new ( name => 'report_features', - result_file => undef, # Prints result + # No result_file => Prints result path => 'include/report-features.test'. + template_path => "include/default_my.cnf", master_opt => [], slave_opt => [], ); @@ -615,6 +616,7 @@ sub run_worker ($) { } setup_vardir(); + check_running_as_root(); mysql_install_db($thread_num); if ( using_extern() ) { @@ -2326,7 +2328,6 @@ sub initialize_servers { mysql_install_db(0); } } - check_running_as_root(); } @@ -2685,6 +2686,120 @@ sub check_testcase($$) } +# Start run mysqltest on one server +# +# RETURN VALUE +# 0 OK +# 1 Check failed +# +sub start_run_one ($$) { + my ($mysqld, $run)= @_; + + my $name= "$run-".$mysqld->name(); + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + + mtr_add_arg($args, "--silent"); + mtr_add_arg($args, "--skip-safemalloc"); + mtr_add_arg($args, "--test-file=%s", "include/$run.test"); + + my $errfile= "$opt_vardir/tmp/$name.err"; + my $proc= My::SafeProcess->new + ( + name => $name, + path => $exe_mysqltest, + error => $errfile, + output => $errfile, + args => \$args, + user_data => $errfile, + ); + mtr_verbose("Started $proc"); + return $proc; +} + + +# +# Run script on all servers, collect results +# +# RETURN VALUE +# 0 ok +# 1 Failure + +sub run_on_all($$) +{ + my ($tinfo, $run)= @_; + my $tname= $tinfo->{name}; + + # Start the mysqltest processes in parallel to save time + # also makes it possible to wait for any process to exit during the check + # and to have a timeout process + my %started; + foreach my $mysqld ( mysqlds() ) + { + if ( defined $mysqld->{'proc'} ) + { + my $proc= start_run_one($mysqld, $run); + $started{$proc->pid()}= $proc; + } + } + + # Return immediately if no check proceess was started + return 0 unless ( keys %started ); + + my $timeout_proc= My::SafeProcess->timer(60); # Seconds + + while (1){ + my $result; + my $proc= My::SafeProcess->wait_any(); + mtr_report("Got $proc"); + + if ( delete $started{$proc->pid()} ) { + + # One mysqltest process returned + my $err_file= $proc->user_data(); + my $res= $proc->exit_status(); + + # Append the report from .err file + $tinfo->{comment}.= " == $err_file ==\n"; + $tinfo->{comment}.= mtr_grab_file($err_file); + $tinfo->{comment}.= "\n"; + + # Remove the .err file + unlink($err_file); + + if ( keys(%started) == 0){ + # All completed + $timeout_proc->kill(); + return 0; + } + + # Wait for next process to exit + next; + } + elsif ( $proc eq $timeout_proc ) { + $tinfo->{comment}.= "Timeout $timeout_proc expired for running '$run'"; + } + else { + # Unknown process returned, most likley a crash, abort everything + $tinfo->{comment}.= + "Unexpected process $proc returned during ". + "execution of '$run'"; + } + + # Kill any check processes still running + map($_->kill(), values(%started)); + + $timeout_proc->kill(); + + return 1; + } +} + + sub mark_log { my ($log, $tinfo)= @_; my $log_msg= "CURRENT_TEST: $tinfo->{name}\n"; @@ -2722,6 +2837,26 @@ sub find_testcase_skipped_reason($) } +sub find_analyze_request +{ + # Open the test log file + my $F= IO::File->new($path_current_testlog) + or return; + my $analyze; + + while ( my $line= <$F> ) + { + # Look for "reason: " + if ( $line =~ /analyze: (.*)/ ) + { + $analyze= $1; + } + } + + return $analyze; +} + + # Return timezone value of tinfo or default value sub timezone { my ($tinfo)= @_; @@ -2925,6 +3060,13 @@ sub run_testcase ($) { } elsif ( $res == 1 ) { + # Check if the test tool requests that + # an analyze script should be run + my $analyze= find_analyze_request(); + if ($analyze){ + run_on_all($tinfo, "analyze-$analyze"); + } + # Test case failure reported by mysqltest report_failure_and_restart($tinfo); } @@ -2983,8 +3125,10 @@ sub run_testcase ($) { # ---------------------------------------------------- if ( $proc eq $test_timeout_proc ) { - mtr_report("Test case timeout!"); - $tinfo->{'timeout'}= 1; # Mark as timeout + $tinfo->{comment}= + "Test case timeout after $opt_testcase_timeout minute(s)\n\n"; + $tinfo->{'timeout'}= $opt_testcase_timeout; # Mark as timeout + run_on_all($tinfo, 'analyze-timeout'); report_failure_and_restart($tinfo); return 1; } @@ -3301,7 +3445,6 @@ sub report_failure_and_restart ($) { $tinfo->{'failures'}= $test_failures + 1; - my $logfile= $path_current_testlog; if ( $tinfo->{comment} ) { # The test failure has been detected by mysql-test-run.pl @@ -3309,12 +3452,17 @@ sub report_failure_and_restart ($) { # failing the test is saved in "comment" ; } - elsif ( defined $logfile and -f $logfile ) + + if ( !defined $tinfo->{logfile} ) { - # Test failure was detected by test tool and its report - # about what failed has been saved to file. Save the report - # in tinfo - $tinfo->{logfile}= mtr_fromfile($logfile); + my $logfile= $path_current_testlog; + if ( defined $logfile and -f $logfile ) + { + # Test failure was detected by test tool and its report + # about what failed has been saved to file. Save the report + # in tinfo + $tinfo->{logfile}= mtr_fromfile($logfile); + } } after_failure($tinfo); @@ -3892,6 +4040,16 @@ sub start_servers($) { $mysqld->{'proc'}) == 0) { $tinfo->{comment}= "Failed to start ".$mysqld->name(); + + my $logfile= $mysqld->value('log-error'); + if ( defined $logfile and -f $logfile ) + { + $tinfo->{logfile}= mtr_fromfile($logfile); + } + else + { + $tinfo->{logfile}= "Could not open server logfile: '$logfile'"; + } return 1; } } @@ -4012,8 +4170,6 @@ sub start_mysqltest ($) { mtr_add_arg($args, "--sleep=%d", $opt_sleep); } - client_debug_arg($args, "mysqltest"); - if ( $opt_ssl ) { # Turn on SSL for _all_ test cases if option --ssl was used @@ -4069,6 +4225,8 @@ sub start_mysqltest ($) { mtr_add_arg($args, "--result-file=%s", $tinfo->{'result_file'}); } + client_debug_arg($args, "mysqltest"); + if ( $opt_record ) { mtr_add_arg($args, "--record"); diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index adf99185d89..d8fd01f86ee 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -293,7 +293,7 @@ var5 from query that returns no row failing query in let mysqltest: At line 1: Error running query 'failing query': 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'failing query' at line 1 mysqltest: At line 1: Missing required argument 'filename' to command 'source' -mysqltest: At line 1: Could not open './non_existingFile' for reading +mysqltest: At line 1: Could not open './non_existingFile' for reading, errno: 2 mysqltest: In included file "MYSQLTEST_VARDIR/tmp/recursive.sql": At line 1: Source directives are nesting too deep mysqltest: In included file "MYSQLTEST_VARDIR/tmp/error.sql": At line 1: query 'garbage ' failed: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'garbage' at line 1 @@ -443,7 +443,7 @@ ERROR 3D000: No database selected Output from mysqltest-x.inc Output from mysqltest-x.inc Output from mysqltest-x.inc -mysqltest: Could not open './non_existing_file.inc' for reading: errno = 2 +mysqltest: Could not open './non_existing_file.inc' for reading, errno: 2 failing_statement; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'failing_statement' at line 1 failing_statement; @@ -486,8 +486,6 @@ mysqltest: At line 3: query 'create table t1 (a int primary key); insert into t1 values (1); select 'select-me'; insertz 'error query'' failed: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insertz 'error query'' at line 1 - -More results from queries before failure can be found in MYSQLTEST_VARDIR/log/bug11731.log drop table t1; Multi statement using expected error create table t1 (a int primary key); @@ -728,4 +726,12 @@ mysqltest: At line 1: change user failed: Access denied for user 'root'@'localho SELECT 'c:\\a.txt' AS col; col z +select 1; +1 +1 +select 1; +1 +1 +-- a comment for the server; +mysqltest: At line 1: Found line beginning with -- that didn't contain a valid mysqltest command, check your syntax or use # if you intended to write a comment End of tests diff --git a/mysql-test/t/fix_priv_tables.test b/mysql-test/t/fix_priv_tables.test index 3051fd88076..c7cd500f8d2 100644 --- a/mysql-test/t/fix_priv_tables.test +++ b/mysql-test/t/fix_priv_tables.test @@ -51,7 +51,8 @@ echo; -- disable_query_log # Run the mysql_fix_privilege_tables.sql using "mysql --force" ---exec $MYSQL --force mysql < $MYSQL_FIX_PRIVILEGE_TABLES > $MYSQLTEST_VARDIR/log/fix_priv_tables.log 2>&1 +--exec $MYSQL --force mysql < $MYSQL_FIX_PRIVILEGE_TABLES > $MYSQLTEST_VARDIR/tmp/fix_priv_tables.log 2>&1 +--remove_file $MYSQLTEST_VARDIR/tmp/fix_priv_tables.log -- enable_query_log -- enable_result_log diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index b41f033d2af..5c5b74c968a 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1225,7 +1225,7 @@ select "a" as col1, "c" as col2; --exec echo "replace_result a;" | $MYSQL_TEST 2>&1 --error 1 --exec echo "replace_result a ;" | $MYSQL_TEST 2>&1 ---exec echo "replace_result a b; echo OK;" | $MYSQL_TEST 2>&1 +--exec echo "replace_result a b; echo OK; exit;" | $MYSQL_TEST 2>&1 --error 1 --exec echo "--replace_result a b c" | $MYSQL_TEST 2>&1 --error 1 @@ -1289,7 +1289,7 @@ while ($i) dec $i; } EOF ---exec echo "source $MYSQLTEST_VARDIR/tmp/mysqltest.sql; echo OK;" | $MYSQL_TEST 2>&1 +--exec echo "source $MYSQLTEST_VARDIR/tmp/mysqltest.sql; echo OK; exit;" | $MYSQL_TEST 2>&1 remove_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql; # Repeat connect/disconnect @@ -1496,6 +1496,7 @@ echo Multi statement using expected error; --exec echo "select 'select-me';" >> $MYSQLTEST_VARDIR/tmp/bug11731.sql --exec echo "insertz "error query"||||" >> $MYSQLTEST_VARDIR/tmp/bug11731.sql --exec echo "delimiter ;||||" >> $MYSQLTEST_VARDIR/tmp/bug11731.sql +--exec echo "exit;" >> $MYSQLTEST_VARDIR/tmp/bug11731.sql # These two should work since the error is expected --exec $MYSQL_TEST -x $MYSQLTEST_VARDIR/tmp/bug11731.sql 2>&1 @@ -1688,6 +1689,7 @@ EOF --diff_files $MYSQLTEST_VARDIR/tmp/diff1.tmp $MYSQLTEST_VARDIR/tmp/diff4.tmp --error 1 --diff_files $MYSQLTEST_VARDIR/tmp/diff4.tmp $MYSQLTEST_VARDIR/tmp/diff1.tmp +exit; EOF # Execute the above diffs, and send their output to /dev/null - only @@ -2131,5 +2133,19 @@ rmdir $MYSQLTEST_VARDIR/tmp/testdir; --replace_result c:\\a.txt z SELECT 'c:\\a.txt' AS col; +# ---------------------------------------------------------------------------- +# Test that -- is not allowed as comment, only as mysqltest builtin command +# ---------------------------------------------------------------------------- + +# valid +select 1; +--query select 1 +--query -- a comment for the server + +# Not valid, "select" is not a mysqltest command +--error 1 +--exec echo "--select 1;" | $MYSQL_TEST 2>&1 + + --echo End of tests diff --git a/mysql-test/t/openssl_1.test b/mysql-test/t/openssl_1.test index 111c723e19c..a76f62c9611 100644 --- a/mysql-test/t/openssl_1.test +++ b/mysql-test/t/openssl_1.test @@ -101,7 +101,7 @@ drop table t1; # - Apparently selecting a cipher doesn't work at all # - Usa a cipher that both yaSSL and OpenSSL supports # ---exec echo "SHOW STATUS LIKE 'Ssl_cipher';" > $MYSQLTEST_VARDIR/tmp/test.sql +--exec echo "SHOW STATUS LIKE 'Ssl_cipher'; exit;" > $MYSQLTEST_VARDIR/tmp/test.sql --exec $MYSQL_TEST --ssl-cipher=DHE-RSA-AES256-SHA < $MYSQLTEST_VARDIR/tmp/test.sql 2>&1 # @@ -155,18 +155,18 @@ SET GLOBAL event_scheduler=0; # # Test to connect using a list of ciphers # ---exec echo "SHOW STATUS LIKE 'Ssl_cipher';" > $MYSQLTEST_VARDIR/tmp/test.sql +--exec echo "SHOW STATUS LIKE 'Ssl_cipher'; exit;" > $MYSQLTEST_VARDIR/tmp/test.sql --exec $MYSQL_TEST --ssl-cipher=UNKNOWN-CIPHER:AES128-SHA < $MYSQLTEST_VARDIR/tmp/test.sql 2>&1 # Test to connect using a specifi cipher # ---exec echo "SHOW STATUS LIKE 'Ssl_cipher';" > $MYSQLTEST_VARDIR/tmp/test.sql +--exec echo "SHOW STATUS LIKE 'Ssl_cipher'; exit;" > $MYSQLTEST_VARDIR/tmp/test.sql --exec $MYSQL_TEST --ssl-cipher=AES128-SHA < $MYSQLTEST_VARDIR/tmp/test.sql 2>&1 # Test to connect using an unknown cipher # ---exec echo "SHOW STATUS LIKE 'Ssl_cipher';" > $MYSQLTEST_VARDIR/tmp/test.sql +--exec echo "SHOW STATUS LIKE 'Ssl_cipher'; exit" > $MYSQLTEST_VARDIR/tmp/test.sql --error 1 --exec $MYSQL_TEST --ssl-cipher=UNKNOWN-CIPHER < $MYSQLTEST_VARDIR/tmp/test.sql 2>&1 -- cgit v1.2.1