diff --git a/client/Makefile.am b/client/Makefile.am index d3e96dd126f..7d48e34b37b 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -46,7 +46,8 @@ mysqladmin_SOURCES = mysqladmin.cc mysql_LDADD = @readline_link@ @TERMCAP_LIB@ $(LDADD) $(CXXLDFLAGS) mysqltest_SOURCES= mysqltest.c $(top_srcdir)/mysys/my_getsystime.c \ $(yassl_dummy_link_fix) -mysqltest_LDADD = $(top_builddir)/regex/libregex.a $(LDADD) +mysqltest_LDADD = $(top_builddir)/regex/libregex.a $(LDADD) \ + $(top_builddir)/mysys/libmysys.a mysqlbinlog_SOURCES = mysqlbinlog.cc $(top_srcdir)/mysys/mf_tempdir.c \ $(top_srcdir)/mysys/my_new.cc \ $(top_srcdir)/mysys/my_bit.c \ diff --git a/client/mysqltest.c b/client/mysqltest.c index f9c4ae617fd..8d8c20c3e8f 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -14,35 +14,24 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* mysqltest test tool - * See the manual for more information - * TODO: document better how mysqltest works - * - * Written by: - * Sasha Pachev - * Matt Wagner - * Monty - * Jani - **/ +/* + mysqltest -/********************************************************************** - TODO: + Tool used for executing a .test file -- Do comparison line by line, instead of doing a full comparison of - the text file. This will save space as we don't need to keep many - results in memory. It will also make it possible to do simple - 'comparison' fixes like accepting the result even if a float differed - in the last decimals. + See the "MySQL Test framework manual" for more information + http://dev.mysql.com/doc/mysqltest/en/index.html -- Don't buffer lines from the test that you don't expect to need - again. + Please keep the test framework tools identical in all versions! -- Change 'read_line' to be faster by using the readline.cc code; - We can do better than calling feof() for each character! + Written by: + Sasha Pachev + Matt Wagner + Monty + Jani +*/ -**********************************************************************/ - -#define MTEST_VERSION "2.6" +#define MTEST_VERSION "3.0" #include #include @@ -51,18 +40,18 @@ #include #include #include +#include #include #include -#include /* Error codes */ #include #include #include -#include #include -#include "my_regex.h" /* Our own version of lib */ +#include "my_regex.h" /* Our own version of regex */ #ifdef HAVE_SYS_WAIT_H #include #endif + #ifndef WEXITSTATUS # ifdef __WIN__ # define WEXITSTATUS(stat_val) (stat_val) @@ -70,169 +59,116 @@ # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) # endif #endif -/* MAX_QUERY is 256K -- there is a test in sp-big that is >128K */ -#define MAX_QUERY (256*1024) -#define MAX_VAR_NAME 256 -#define MAX_COLUMNS 256 -#define MAX_CONS 128 -#define MAX_INCLUDE_DEPTH 16 -#define INIT_Q_LINES 1024 -#define MIN_VAR_ALLOC 32 -#define BLOCK_STACK_DEPTH 32 -#define MAX_EXPECTED_ERRORS 10 -#define QUERY_SEND 1 -#define QUERY_REAP 2 -#define MAX_SERVER_ARGS 64 +#define MAX_VAR_NAME_LENGTH 256 +#define MAX_COLUMNS 256 +#define MAX_EMBEDDED_SERVER_ARGS 64 +#define MAX_DELIMITER_LENGTH 16 -#define SLAVE_POLL_INTERVAL 300000 /* 0.3 of a sec */ -#define DEFAULT_DELIMITER ";" -#define MAX_DELIMITER 16 +/* Flags controlling send and reap */ +#define QUERY_SEND_FLAG 1 +#define QUERY_REAP_FLAG 2 -#define RESULT_OK 0 -#define RESULT_CONTENT_MISMATCH 1 -#define RESULT_LENGTH_MISMATCH 2 + enum { + RESULT_OK= 0, + RESULT_CONTENT_MISMATCH= 1, + RESULT_LENGTH_MISMATCH= 2 + }; -enum {OPT_SKIP_SAFEMALLOC=256, OPT_SSL_SSL, OPT_SSL_KEY, OPT_SSL_CERT, - OPT_SSL_CA, OPT_SSL_CAPATH, OPT_SSL_CIPHER, OPT_PS_PROTOCOL, - OPT_SP_PROTOCOL, OPT_CURSOR_PROTOCOL, OPT_VIEW_PROTOCOL, - OPT_SSL_VERIFY_SERVER_CERT, OPT_MAX_CONNECT_RETRIES, - OPT_MARK_PROGRESS}; - -/* ************************************************************************ */ -/* - The list of error codes to --error are stored in an internal array of - structs. This struct can hold numeric SQL error codes or SQLSTATE codes - as strings. The element next to the last active element in the list is - set to type ERR_EMPTY. When an SQL statement 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 +enum { + OPT_SKIP_SAFEMALLOC=256, OPT_SSL_SSL, OPT_SSL_KEY, OPT_SSL_CERT, + OPT_SSL_CA, OPT_SSL_CAPATH, OPT_SSL_CIPHER, OPT_PS_PROTOCOL, + OPT_SP_PROTOCOL, OPT_CURSOR_PROTOCOL, OPT_VIEW_PROTOCOL, + OPT_SSL_VERIFY_SERVER_CERT, OPT_MAX_CONNECT_RETRIES, + OPT_MARK_PROGRESS }; -typedef struct -{ - enum match_err_type type; - union - { - uint errnum; - char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ - } code; -} match_err; - -typedef struct -{ - const char *name; - long code; -} st_error; - -static st_error global_error[] = -{ -#include - { 0, 0 } -}; - -static match_err global_expected_errno[MAX_EXPECTED_ERRORS]; -static uint global_expected_errors; - -/* ************************************************************************ */ - static int record= 0, opt_sleep= -1; -static char *db = 0, *pass=0; -const char *user = 0, *host = 0, *unix_sock = 0, *opt_basedir="./"; +static char *db= 0, *pass= 0; +const char *user= 0, *host= 0, *unix_sock= 0, *opt_basedir= "./"; const char *opt_include= 0; -static int port = 0; +static int port= 0; static int opt_max_connect_retries; -static my_bool opt_big_test= 0, opt_compress= 0, silent= 0, verbose = 0; +static my_bool opt_compress= 0, silent= 0, verbose= 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 opt_valgrind_test= 0; -static int parsing_disabled= 0; +static my_bool parsing_disabled= 0; +static my_bool display_result_vertically= FALSE, display_metadata= FALSE; +static my_bool disable_query_log= 0, disable_result_log= 0; +static my_bool disable_warnings= 0, disable_ps_warnings= 0; +static my_bool disable_info= 1; +static my_bool abort_on_error= 1; static char **default_argv; -static const char *load_default_groups[]= { "mysqltest","client",0 }; -static char line_buffer[MAX_DELIMITER], *line_buffer_pos= line_buffer; +static const char *load_default_groups[]= { "mysqltest", "client", 0 }; +static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer; -typedef struct -{ - FILE* file; - const char *file_name; - uint lineno; /* Current line in file */ -} test_file; +static uint start_lineno= 0; /* Start line of current command */ -static test_file file_stack[MAX_INCLUDE_DEPTH]; -static test_file* cur_file; -static test_file* file_stack_end; -uint start_lineno= 0; /* Start line of query */ - -/* 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; - -static char TMPDIR[FN_REFLEN]; -static char delimiter[MAX_DELIMITER]= DEFAULT_DELIMITER; +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 }; -typedef struct +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 */ -} BLOCK; -static BLOCK block_stack[BLOCK_STACK_DEPTH]; -static BLOCK *cur_block, *block_stack_end; +}; + +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 *charset_name= "latin1"; /* Default character set name */ +static const char *embedded_server_groups[]= +{ + "server", + "embedded", + "mysqltest_SERVER", + NullS +}; + static int embedded_server_arg_count=0; -static char *embedded_server_args[MAX_SERVER_ARGS]; +static char *embedded_server_args[MAX_EMBEDDED_SERVER_ARGS]; -static my_bool display_result_vertically= FALSE, display_metadata= FALSE; - -/* See the timer_output() definition for details */ +/* + Timer related variables + See the timer_output() definition for details +*/ static char *timer_file = NULL; -static ulonglong timer_start, progress_start= 0; -static int got_end_timer= FALSE; +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 */ @@ -242,47 +178,25 @@ static void init_re(void); static int match_re(my_regex_t *, char *); static void free_re(void); -static int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, - char *string, int icase); - -static const char *embedded_server_groups[]= -{ - "server", - "embedded", - "mysqltest_SERVER", - NullS -}; - DYNAMIC_ARRAY q_lines; #include "sslopt-vars.h" -typedef struct +struct +{ + int read_lines,current_line; +} parser; + +struct { char file[FN_REFLEN]; ulong pos; -} MASTER_POS ; +} master_pos; -struct connection -{ - MYSQL mysql; - /* Used when creating views and sp, to avoid implicit commit */ - MYSQL* util_mysql; - char *name; - MYSQL_STMT* stmt; -}; - -typedef struct -{ - int read_lines,current_line; -} PARSER; - -PARSER parser; -MASTER_POS master_pos; /* if set, all results are concated and compared against this file */ -const char *result_file = 0; +const char *result_file_name= 0; -typedef struct +typedef struct st_var { char *name; int name_len; @@ -295,70 +209,68 @@ typedef struct char *env_s; } VAR; -VAR var_reg[10]; /*Perl/shell-like variable registers */ +VAR var_reg[10]; + HASH var_hash; -my_bool disable_query_log=0, disable_result_log=0, disable_warnings=0; -my_bool disable_ps_warnings= 0; -my_bool disable_info= 1; /* By default off */ -my_bool abort_on_error= 1; -struct connection cons[MAX_CONS]; -struct connection* cur_con, *next_con, *cons_end; - - /* 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_PS_WARNINGS, Q_DISABLE_PS_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_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_DIE, - -Q_UNKNOWN, /* Unknown command. */ -Q_COMMENT, /* Comments, ignored. */ -Q_COMMENT_WITH_COMMAND -}; - -/* this should really be called command */ -struct st_query +struct st_connection { - char *query, *query_buf,*first_argument,*last_argument,*end; - int first_word_len; - my_bool abort_on_error, require_file; - match_err expected_errno[MAX_EXPECTED_ERRORS]; - uint expected_errors; - char record_file[FN_REFLEN]; - enum enum_commands type; + MYSQL mysql; + /* Used when creating views and sp, to avoid implicit commit */ + MYSQL* util_mysql; + char *name; + MYSQL_STMT* stmt; }; +struct st_connection connections[128]; +struct st_connection* cur_con, *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_PS_WARNINGS, Q_DISABLE_PS_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_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_UNKNOWN, /* Unknown command. */ + Q_COMMENT, /* Comments, ignored. */ + Q_COMMENT_WITH_COMMAND +}; + const char *command_names[]= { @@ -427,69 +339,116 @@ const char *command_names[]= "disable_parsing", "enable_parsing", "replace_regex", + "remove_file", + "file_exists", + "write_file", + "copy_file", + "perl", "die", 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; -static void die(const char *fmt, ...); -static void init_var_hash(); -static VAR* var_from_env(const char *, const char *); -static byte* get_var_key(const byte* rec, uint* len, my_bool t); -static VAR* var_init(VAR* v, const char *name, int name_len, const char *val, - int val_len); +DYNAMIC_STRING ds_res, ds_progress, ds_warning_messages; -static void var_free(void* v); +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 dump_result_to_reject_file(const char *record_file, char *buf, int size); -void dump_result_to_log_file(const char *record_file, char *buf, int size); +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(); -int close_connection(struct st_query*); -static void set_charset(struct st_query*); -VAR* var_get(const char *var_name, const char** var_name_end, my_bool raw, - my_bool ignore_not_existing); -int eval_expr(VAR* v, const char *p, const char** p_end); -static int read_server_arguments(const char *name); - -/* Definitions for replace result */ - -typedef struct st_pointer_array { /* when using array-strings */ - TYPELIB typelib; /* Pointer to strings */ - byte *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(my_string *from, my_string *to, uint count, - my_string word_end_chars); -void free_replace(); -static void free_replace_regex(); -static int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string name); -static void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, - const char *from, int len); -void free_pointer_array(POINTER_ARRAY *pa); -static void do_eval(DYNAMIC_STRING *query_eval, const char *query, - my_bool pass_through_escape_chars); -static void str_to_file(const char *fname, char *str, int size); +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); #ifdef __WIN__ -static void free_tmp_sh_file(); -static void free_win_path_patterns(); +void free_tmp_sh_file(); +void free_win_path_patterns(); #endif -struct st_replace *glob_replace; static int eval_result = 0; -/* For column replace */ -char *replace_column[MAX_COLUMNS]; -uint max_replace_column= 0; +/* 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(); +} -static void get_replace_column(struct st_query *q); -static void free_replace_column(); /* Disable functions that only exist in MySQL 4.0 */ #if MYSQL_VERSION_ID < 40000 @@ -498,35 +457,40 @@ 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 -static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, - int len); -static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); -static void handle_error(const char *query, struct st_query *q, - unsigned int err_errno, const char *err_error, - const char *err_sqlstate, DYNAMIC_STRING *ds); -static void handle_no_error(struct st_query *q); -static void do_eval(DYNAMIC_STRING* query_eval, const char *query, - my_bool pass_through_escape_chars) +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 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*); + + + +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; + VAR *v; DBUG_ENTER("do_eval"); - for (p= query; (c = *p); ++p) + for (p= query; (c= *p) && p < query_end; ++p) { switch(c) { case '$': if (escaped) { - escaped = 0; + escaped= 0; dynstr_append_mem(query_eval, p, 1); } else { - if (!(v = var_get(p, &p, 0, 0))) + 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); } @@ -535,13 +499,13 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query, next_c= *(p+1); if (escaped) { - escaped = 0; + escaped= 0; dynstr_append_mem(query_eval, p, 1); } - else if (next_c == '\\' || next_c == '$') + else if (next_c == '\\' || next_c == '$' || next_c == '"') { - /* Set escaped only if next char is \ or $ */ - escaped = 1; + /* Set escaped only if next char is \, " or $ */ + escaped= 1; if (pass_through_escape_chars) { @@ -561,10 +525,131 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query, } -static void close_cons() +enum arg_type { - DBUG_ENTER("close_cons"); - for (--next_con; next_con >= cons; --next_con) + 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); + + } + 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); @@ -578,7 +663,7 @@ static void close_cons() } -static void close_files() +void close_files() { DBUG_ENTER("close_files"); for (; cur_file >= file_stack; cur_file--) @@ -593,22 +678,22 @@ static void close_files() } -static void free_used_memory() +void free_used_memory() { uint i; DBUG_ENTER("free_used_memory"); - close_cons(); + close_connections(); close_files(); hash_free(&var_hash); - for (i=0 ; i < q_lines.elements ; i++) + for (i= 0 ; i < q_lines.elements ; i++) { - struct st_query **q= dynamic_element(&q_lines, i, struct st_query**); + struct st_command **q= dynamic_element(&q_lines, i, struct st_command**); my_free((gptr) (*q)->query_buf,MYF(MY_ALLOW_ZERO_PTR)); my_free((gptr) (*q),MYF(0)); } - for (i=0; i < 10; i++) + for (i= 0; i < 10; i++) { if (var_reg[i].alloced_len) my_free(var_reg[i].str_val, MYF(MY_WME)); @@ -618,8 +703,8 @@ static void free_used_memory() delete_dynamic(&q_lines); dynstr_free(&ds_res); dynstr_free(&ds_progress); - free_replace(); - free_replace_column(); + dynstr_free(&ds_warning_messages); + free_all_replace(); my_free(pass,MYF(MY_ALLOW_ZERO_PTR)); free_defaults(default_argv); mysql_server_end(); @@ -631,15 +716,22 @@ static void free_used_memory() DBUG_VOID_RETURN; } -static void die(const char *fmt, ...) + +void die(const char *fmt, ...) { va_list args; DBUG_ENTER("die"); + DBUG_PRINT("enter", ("start_lineno: %d", start_lineno)); /* Print the error message */ va_start(args, fmt); if (fmt) { +#ifdef DBUG_ON + char buff[256]; + vsnprintf(buff, sizeof(buff), fmt, args); + DBUG_PRINT("error", ("%s", buff)); +#endif fprintf(stderr, "mysqltest: "); if (cur_file && cur_file != file_stack) fprintf(stderr, "In included file \"%s\": ", @@ -653,8 +745,12 @@ static void die(const char *fmt, ...) va_end(args); /* Dump the result that has been accumulated so far to .log file */ - if (result_file && ds_res.length) - dump_result_to_log_file(result_file, ds_res.str, ds_res.length); + 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(); /* Clean up and exit */ free_used_memory(); @@ -667,10 +763,10 @@ static void die(const char *fmt, ...) } -static void abort_not_supported_test(const char *fmt, ...) +void abort_not_supported_test(const char *fmt, ...) { va_list args; - test_file* err_file= cur_file; + struct st_test_file* err_file= cur_file; DBUG_ENTER("abort_not_supported_test"); /* Print include filestack */ @@ -706,7 +802,14 @@ static void abort_not_supported_test(const char *fmt, ...) exit(62); } -static void verbose_msg(const char *fmt, ...) + +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"); @@ -715,6 +818,9 @@ static void verbose_msg(const char *fmt, ...) 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); @@ -725,14 +831,44 @@ static void verbose_msg(const char *fmt, ...) } -void init_parser() +void warning_msg(const char *fmt, ...) { - parser.current_line= parser.read_lines= 0; - memset(&var_reg, 0, sizeof(var_reg)); + 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= 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; } -static int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) +/* + Compare content of the string ds to content of file fname +*/ + +int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) { MY_STAT stat_info; char *tmp, *res_ptr; @@ -761,19 +897,20 @@ static int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) DBUG_RETURN(RESULT_LENGTH_MISMATCH); } if (!(tmp = (char*) my_malloc(stat_info.st_size + 1, MYF(MY_WME)))) - die(NullS); + die("Out of memory"); if ((fd = my_open(eval_file, O_RDONLY, MYF(MY_WME))) < 0) - die(NullS); + die("Failed to open file %s", eval_file); if (my_read(fd, (byte*)tmp, stat_info.st_size, MYF(MY_WME|MY_NABP))) - die(NullS); + die("Failed to read from file %s, errno: %d", eval_file, errno); tmp[stat_info.st_size] = 0; - init_dynamic_string(&res_ds, "", 0, 65536); + init_dynamic_string(&res_ds, "", stat_info.st_size+256, 256); if (eval_result) { - do_eval(&res_ds, tmp, FALSE); - res_ptr = res_ds.str; - if ((res_len = res_ds.length) != ds->length) + do_eval(&res_ds, tmp, tmp + stat_info.st_size, FALSE); + res_ptr= res_ds.str; + res_len= res_ds.length; + if (res_len != ds->length) { res= RESULT_LENGTH_MISMATCH; goto err; @@ -794,48 +931,41 @@ err: MY_REPLACE_EXT), res_ptr, res_len); + dynstr_free(&res_ds); my_free((gptr) tmp, MYF(0)); my_close(fd, MYF(MY_WME)); - dynstr_free(&res_ds); DBUG_RETURN(res); } + /* - Check the content of ds against content of file fname + Check the content of ds against result file SYNOPSIS check_result ds - content to be checked - fname - name of file to check against - require_option - if set and check fails, the test will be aborted - with the special exit code "not supported test" RETURN VALUES - error - the function will not return + error - the function will not return */ -static void check_result(DYNAMIC_STRING* ds, const char *fname, - my_bool require_option) + +void check_result(DYNAMIC_STRING* ds) { - int res= dyn_string_cmp(ds, fname); + DBUG_ASSERT(result_file_name); DBUG_ENTER("check_result"); - if (res && require_option) + switch (dyn_string_cmp(ds, result_file_name)) { - char reason[FN_REFLEN]; - fn_format(reason, fname, "", "", MY_REPLACE_EXT | MY_REPLACE_DIR); - abort_not_supported_test("Test requires: '%s'", reason); - } - switch (res) { case RESULT_OK: break; /* ok */ case RESULT_LENGTH_MISMATCH: - dump_result_to_reject_file(fname, ds->str, ds->length); + dump_result_to_reject_file(ds->str, ds->length); die("Result length mismatch"); break; case RESULT_CONTENT_MISMATCH: - dump_result_to_reject_file(fname, ds->str, ds->length); + dump_result_to_reject_file(ds->str, ds->length); die("Result content mismatch"); break; default: /* impossible */ @@ -846,13 +976,109 @@ static void check_result(DYNAMIC_STRING* ds, const char *fname, } -VAR* var_get(const char *var_name, const char** var_name_end, my_bool raw, +/* + 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_result + 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; +} + + +static byte *get_var_key(const byte* var, uint* len, + my_bool __attribute__((unused)) t) +{ + register char* key; + key = ((VAR*)var)->name; + *len = ((VAR*)var)->name_len; + return (byte*)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((char*) 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, (byte*)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; + VAR *v; DBUG_ENTER("var_get"); - DBUG_PRINT("enter",("var_name: %s",var_name)); + DBUG_PRINT("enter", ("var_name: %s",var_name)); if (*var_name != '$') goto err; @@ -871,16 +1097,16 @@ VAR* var_get(const char *var_name, const char** var_name_end, my_bool raw, die("Empty variable"); } length= (uint) (var_name - save_var_name); - if (length >= MAX_VAR_NAME) + if (length >= MAX_VAR_NAME_LENGTH) die("Too long variable name: %s", save_var_name); if (!(v = (VAR*) hash_search(&var_hash, save_var_name, length))) { - char buff[MAX_VAR_NAME+1]; + char buff[MAX_VAR_NAME_LENGTH+1]; strmake(buff, save_var_name, length); v= var_from_env(buff, ""); } - var_name--; /* Point at last character */ + var_name--; /* Point at last character */ } else v = var_reg + digit; @@ -901,7 +1127,8 @@ err: DBUG_RETURN(0); } -static VAR *var_obtain(const char *name, int len) + +VAR *var_obtain(const char *name, int len) { VAR* v; if ((v = (VAR*)hash_search(&var_hash, name, len))) @@ -911,16 +1138,18 @@ static VAR *var_obtain(const char *name, int len) 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 */ -int var_set(const char *var_name, const char *var_name_end, - const char *var_val, const char *var_val_end) + +void var_set(const char *var_name, const char *var_name_end, + const char *var_val, const char *var_val_end) { - int digit, result, env_var= 0; - VAR* v; + 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, @@ -932,15 +1161,15 @@ int var_set(const char *var_name, const char *var_name_end, else var_name++; - digit = *var_name - '0'; + digit= *var_name - '0'; if (!(digit < 10 && digit >= 0)) { - v = var_obtain(var_name, (uint) (var_name_end - var_name)); + v= var_obtain(var_name, (uint) (var_name_end - var_name)); } else - v = var_reg + digit; + v= var_reg + digit; - result= eval_expr(v, var_val, (const char**) &var_val_end); + eval_expr(v, var_val, (const char**) &var_val_end); if (env_var) { @@ -957,7 +1186,183 @@ int var_set(const char *var_name, const char *var_name_end, putenv(v->env_s); my_free((gptr)old_env_s, MYF(MY_ALLOW_ZERO_PTR)); } - DBUG_RETURN(result); + DBUG_VOID_RETURN; +} + +/* + Store an integer (typically the returncode of the last SQL) + statement in the mysqltest builtin variable $mysql_errno, by + simulating of a user statement "let $mysql_errno= " +*/ + +void var_set_errno(int sql_errno) +{ + /* TODO MASV make easier */ + const char *var_name= "$mysql_errno"; + char var_val[21]; + uint length= my_sprintf(var_val, (var_val, "%d", sql_errno)); + var_set(var_name, var_name + 12, var_val, var_val + length); + return; +} + + +/* + Set variable from the result of a query + + SYNOPSIS + var_query_set() + var variable to set from query + query start of query string to execute + query_end end of the query string to execute + + + DESCRIPTION + let @ = `` + + 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; + DBUG_ENTER("var_query_set"); + LINT_INIT(res); + + while (end > query && *end != '`') + --end; + if (query == end) + die("Syntax error in query, missing '`'"); + ++query; + + if (mysql_real_query(mysql, query, (int)(end - query)) || + !(res = mysql_store_result(mysql))) + { + *end = 0; + die("Error running query '%s': %d %s", query, + mysql_errno(mysql), mysql_error(mysql)); + } + + if ((row = mysql_fetch_row(res)) && row[0]) + { + /* + Concatenate all row results with tab in between to allow us to work + with results from many columns (for example from SHOW VARIABLES) + */ + DYNAMIC_STRING result; + uint i; + ulong *lengths; + char *end; +#ifdef NOT_YET + MYSQL_FIELD *fields= mysql_fetch_fields(res); +#endif + + init_dynamic_string(&result, "", 2048, 2048); + lengths= mysql_fetch_lengths(res); + for (i=0; i < mysql_num_fields(res); i++) + { + if (row[0]) + { +#ifdef NOT_YET + /* Add to _ */ + uint j; + char var_col_name[MAX_VAR_NAME_LENGTH]; + uint length= snprintf(var_col_name, MAX_VAR_NAME_LENGTH, + "$%s_%s", var->name, fields[i].name); + /* Convert characters not allowed in variable names to '_' */ + for (j= 1; j < length; j++) + { + if (!my_isvar(charset_info,var_col_name[j])) + var_col_name[j]= '_'; + } + var_set(var_col_name, var_col_name + length, + row[i], row[i] + lengths[i]); +#endif + /* Add column to tab separated string */ + dynstr_append_mem(&result, row[i], lengths[i]); + } + dynstr_append_mem(&result, "\t", 1); + } + end= result.str + result.length-1; + eval_expr(var, result.str, (const char**) &end); + dynstr_free(&result); + } + else + eval_expr(var, "", 0); + + 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) +{ + static int MIN_VAR_ALLOC= 32; /* MASV why 32? */ + VAR *vp; + if (*p == '$') + { + if ((vp= var_get(p, p_end, 0, 0))) + { + var_copy(v, vp); + return; + } + } + else if (*p == '`') + { + var_query_set(v, p, p_end); + } + else + { + int new_val_len = (p_end && *p_end) ? + (int) (*p_end - p) : (int) strlen(p); + if (new_val_len + 1 >= v->alloced_len) + { + 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); + v->int_dirty=0; + } + return; } @@ -988,37 +1393,634 @@ int open_file(const char *name) /* - Check for unexpected "junk" after the end of query - This is normally caused by missing delimiters + Source and execute the given file + + SYNOPSIS + do_source() + query called command + + DESCRIPTION + source + + Open the file and execute it + */ -int check_eol_junk(const char *eol) +void do_source(struct st_command *command) { - const char *p= eol; - DBUG_ENTER("check_eol_junk"); - DBUG_PRINT("enter", ("eol: %s", eol)); - /* Remove all spacing chars except new line */ - while (*p && my_isspace(charset_info, *p) && (*p != '\n')) - p++; + DYNAMIC_STRING ds_filename; + const struct command_arg source_args[] = { + "filename", ARG_STRING, TRUE, &ds_filename, "File to source" + }; + DBUG_ENTER("do_source"); - /* Check for extra delimiter */ - if (*p && !strncmp(p, delimiter, delimiter_length)) - die("Extra delimiter \"%s\" found", delimiter); + check_command_args(command, command->first_argument, source_args, + sizeof(source_args)/sizeof(struct command_arg), + ' '); - /* Allow trailing # comment */ - if (*p && *p != '#') + /* + 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 { - if (*p == '\n') - die("Missing delimiter"); - die("End of line junk detected: \"%s\"", p); + DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str)); + open_file(ds_filename.str); } - DBUG_RETURN(0); + + dynstr_free(&ds_filename); + return; } -/* ugly long name, but we are following the convention */ -int do_wait_for_slave_to_stop(struct st_query *q __attribute__((unused))) +#ifdef __WIN__ +/* Variables used for temuprary sh files used for emulating Unix on Windows */ +char tmp_sh_name[64], tmp_sh_cmd[70]; + +void init_tmp_sh_file() { + /* Format a name for the tmp sh file that is unique for this process */ + my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); + /* Format the command to execute in order to run the script */ + my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); +} + + +void free_tmp_sh_file() +{ + my_delete(tmp_sh_name, MYF(0)); +} +#endif + + +FILE* my_popen(DYNAMIC_STRING *ds_cmd, const char *mode) +{ +#ifdef __WIN__ + /* Dump the command into a sh script file and execute with popen */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return popen(tmp_sh_cmd, mode); +#else + return popen(ds_cmd->str, mode); +#endif +} + + +/* + Execute given command. + + 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 + system for those commands. +*/ + +void do_exec(struct st_command *command) +{ + int error; + char buf[1024]; + FILE *res_file; + char *cmd= command->first_argument; + DYNAMIC_STRING ds_cmd; + DBUG_ENTER("do_exec"); + DBUG_PRINT("enter", ("cmd: '%s'", cmd)); + + 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, TRUE); + cmd= ds_cmd.str; + + DBUG_PRINT("info", ("Executing '%s' as '%s'", + command->first_argument, cmd)); + + if (!(res_file= my_popen(&ds_cmd, "r")) && command->abort_on_error) + 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) + 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) + 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 */ + 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) +{ +#ifdef __WIN__ + /* Dump the command into a sh script file and execute with system */ + str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); + return system(tmp_sh_cmd); +#else + return system(ds_cmd->str); +#endif +} + + +/* + SYNOPSIS + do_system + command called command + + DESCRIPTION + system + + 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, TRUE); + + DBUG_PRINT("info", ("running system command '%s' as '%s'", + command->first_argument, ds_cmd.str)); + if (my_system(&ds_cmd)) + { + if (command->abort_on_error) + die("system command '%s' failed", command->first_argument); + + /* If ! abort_on_error, log message and continue */ + dynstr_append(&ds_res, "system command '"); + replace_dynstr_append(&ds_res, command->first_argument); + dynstr_append(&ds_res, "' failed\n"); + } + + command->last_argument= command->end; + 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; + 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; + DYNAMIC_STRING ds_from_file; + 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_file_exists + command called command + + DESCRIPTION + fiile_exist + Check if file exists +*/ + +void do_file_exist(struct st_command *command) +{ + int error; + 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; +} + + +/* + 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) +{ + int c; + DBUG_ENTER("read_until_delimiter"); + DBUG_PRINT("enter", ("delimiter: %s, length: %d", + ds_delimiter->str, 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++; + + 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; +} + + +/* + 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! Overwrites existing file + + Default is EOF + +*/ + +void do_write_file(struct st_command *command) +{ + DYNAMIC_STRING ds_content; + DYNAMIC_STRING ds_filename; + 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"); + + 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_file(ds_filename.str, ds_content.str, ds_content.length); + dynstr_free(&ds_content); + dynstr_free(&ds_filename); + dynstr_free(&ds_delimiter); + 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; + char buf[FN_REFLEN]; + FILE *res_file; + DYNAMIC_STRING ds_script; + 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)); + + /* Format a name for a tmp .pl file that is unique for this process */ + my_snprintf(buf, sizeof(buf), "%s/tmp/tmp_%d.pl", + getenv("MYSQLTEST_VARDIR"), getpid()); + str_to_file(buf, ds_script.str, ds_script.length); + + /* Format the perl command */ + my_snprintf(buf, sizeof(buf), "perl %s/tmp/tmp_%d.pl", + getenv("MYSQLTEST_VARDIR"), getpid()); + + 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); + 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; + + 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; + 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 (;;) { @@ -1042,495 +2044,15 @@ int do_wait_for_slave_to_stop(struct st_query *q __attribute__((unused))) break; my_sleep(SLAVE_POLL_INTERVAL); } - return 0; + return; } -/* - Source and execute the given file - - SYNOPSIS - do_source() - query called command - - DESCRIPTION - source - - Open the file and execute it - -*/ - -int do_source(struct st_query *query) +void do_sync_with_master2(long offset) { - char *p= query->first_argument, *name; - if (!*p) - die("Missing file name in source"); - name= p; - while (*p && !my_isspace(charset_info,*p)) - p++; - if (*p) - *p++= 0; - query->last_argument= p; - /* - 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)) - return 0; - return open_file(name); -} - -#ifdef __WIN__ -/* Variables used for temuprary sh files used for emulating Unix on Windows */ -char tmp_sh_name[64], tmp_sh_cmd[70]; - -static void init_tmp_sh_file() -{ - /* Format a name for the tmp sh file that is unique for this process */ - my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh", getpid()); - /* Format the command to execute in order to run the script */ - my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s", tmp_sh_name); -} - -static void free_tmp_sh_file() -{ - my_delete(tmp_sh_name, MYF(0)); -} -#endif - -FILE* my_popen(DYNAMIC_STRING* ds_cmd, const char* mode) -{ -#ifdef __WIN__ - /* Dump the command into a sh script file and execute with popen */ - str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); - return popen(tmp_sh_cmd, mode); -#else - return popen(ds_cmd->str, mode); -#endif -} - - -/* - Execute given command. - - 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 - system for those commands. -*/ - -static void do_exec(struct st_query *query) -{ - int error; - char buf[1024]; - FILE *res_file; - char *cmd= query->first_argument; - DYNAMIC_STRING ds_cmd; - DBUG_ENTER("do_exec"); - DBUG_PRINT("enter", ("cmd: '%s'", cmd)); - - while (*cmd && my_isspace(charset_info, *cmd)) - cmd++; - if (!*cmd) - die("Missing argument in exec"); - query->last_argument= query->end; - - init_dynamic_string(&ds_cmd, 0, strlen(cmd)+256, 256); - /* Eval the command, thus replacing all environment variables */ - do_eval(&ds_cmd, cmd, TRUE); - cmd= ds_cmd.str; - - DBUG_PRINT("info", ("Executing '%s' as '%s'", - query->first_argument, cmd)); - - if (!(res_file= my_popen(&ds_cmd, "r")) && query->abort_on_error) - die("popen(\"%s\", \"r\") failed", query->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 (query->abort_on_error) - die("command \"%s\" failed", query->first_argument); - - DBUG_PRINT("info", - ("error: %d, status: %d", error, status)); - for (i= 0; i < query->expected_errors; i++) - { - DBUG_PRINT("info", ("expected error: %d", - query->expected_errno[i].code.errnum)); - if ((query->expected_errno[i].type == ERR_ERRNO) && - (query->expected_errno[i].code.errnum == status)) - { - ok= 1; - DBUG_PRINT("info", ("command \"%s\" failed with expected error: %d", - query->first_argument, status)); - } - } - if (!ok) - die("command \"%s\" failed with wrong error: %d", - query->first_argument, status); - } - else if (query->expected_errno[0].type == ERR_ERRNO && - query->expected_errno[0].code.errnum != 0) - { - /* Error code we wanted was != 0, i.e. not an expected success */ - die("command \"%s\" succeeded - should have failed with errno %d...", - query->first_argument, query->expected_errno[0].code.errnum); - } - - free_replace(); - DBUG_VOID_RETURN; -} - -/* - Set variable from the result of a query - - SYNOPSIS - var_query_set() - var variable to set from query - query start of query string to execute - query_end end of the query string to execute - - - DESCRIPTION - let @ = `` - - 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 $_ - -*/ - -int 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; - DBUG_ENTER("var_query_set"); - LINT_INIT(res); - - while (end > query && *end != '`') - --end; - if (query == end) - die("Syntax error in query, missing '`'"); - ++query; - - if (mysql_real_query(mysql, query, (int)(end - query)) || - !(res = mysql_store_result(mysql))) - { - *end = 0; - die("Error running query '%s': %d: %s", query, - mysql_errno(mysql) ,mysql_error(mysql)); - } - - if ((row = mysql_fetch_row(res)) && row[0]) - { - /* - Concatenate all row results with tab in between to allow us to work - with results from many columns (for example from SHOW VARIABLES) - */ - DYNAMIC_STRING result; - uint i; - ulong *lengths; - char *end; -#ifdef NOT_YET - MYSQL_FIELD *fields= mysql_fetch_fields(res); -#endif - - init_dynamic_string(&result, "", 16384, 65536); - lengths= mysql_fetch_lengths(res); - for (i=0; i < mysql_num_fields(res); i++) - { - if (row[0]) - { -#ifdef NOT_YET - /* Add to _ */ - uint j; - char var_col_name[MAX_VAR_NAME]; - uint length= snprintf(var_col_name, MAX_VAR_NAME, - "$%s_%s", var->name, fields[i].name); - /* Convert characters not allowed in variable names to '_' */ - for (j= 1; j < length; j++) - { - if (!my_isvar(charset_info,var_col_name[j])) - var_col_name[j]= '_'; - } - var_set(var_col_name, var_col_name + length, - row[i], row[i] + lengths[i]); -#endif - /* Add column to tab separated string */ - dynstr_append_mem(&result, row[i], lengths[i]); - } - dynstr_append_mem(&result, "\t", 1); - } - end= result.str + result.length-1; - eval_expr(var, result.str, (const char**) &end); - dynstr_free(&result); - } - else - eval_expr(var, "", 0); - - mysql_free_result(res); - DBUG_RETURN(0); -} - -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); -} - -int eval_expr(VAR* v, const char *p, const char** p_end) -{ - VAR* vp; - if (*p == '$') - { - if ((vp = var_get(p,p_end,0,0))) - { - var_copy(v, vp); - return 0; - } - } - else if (*p == '`') - { - return var_query_set(v, p, p_end); - } - else - { - int new_val_len = (p_end && *p_end) ? - (int) (*p_end - p) : (int) strlen(p); - if (new_val_len + 1 >= v->alloced_len) - { - 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); - v->int_dirty=0; - return 0; - } - - die("Invalid expr: %s", p); - return 1; -} - - -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_query *query, - enum enum_operator operator) -{ - const char *p= query->first_argument; - VAR* v; - if (!*p) - die("Missing argument to %.*s", query->first_word_len, query->query); - if (*p != '$') - die("The argument to %.*s must be a variable (start with $)", - query->first_word_len, query->query); - v= var_get(p, &p, 1, 0); - switch (operator) { - 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; - query->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) -{ -#ifdef __WIN__ - /* Dump the command into a sh script file and execute with system */ - str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); - return system(tmp_sh_cmd); -#else - return system(ds_cmd->str); -#endif -} - - -/* - - SYNOPSIS - do_system - command called command - - DESCRIPTION - system - - Eval the query to expand any $variables in the command. - Execute the command with the "system" command. - -*/ - -void do_system(struct st_query *command) -{ - DYNAMIC_STRING ds_cmd; - DBUG_ENTER("do_system"); - - if (strlen(command->first_argument) == 0) - die("Missing arguments to system, nothing to do!"); - - init_dynamic_string(&ds_cmd, 0, strlen(command->first_argument) + 64, 256); - - /* Eval the system command, thus replacing all environment variables */ - do_eval(&ds_cmd, command->first_argument, TRUE); - - DBUG_PRINT("info", ("running system command '%s' as '%s'", - command->first_argument, ds_cmd.str)); - if (my_system(&ds_cmd)) - { - if (command->abort_on_error) - die("system command '%s' failed", command->first_argument); - - /* If ! abort_on_error, log message and continue */ - dynstr_append(&ds_res, "system command '"); - replace_dynstr_append(&ds_res, command->first_argument); - dynstr_append(&ds_res, "' failed\n"); - } - - command->last_argument= command->end; - dynstr_free(&ds_cmd); - 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() - q 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_query *command) -{ - DYNAMIC_STRING *ds, ds_echo; - - ds= &ds_res; - - init_dynamic_string(&ds_echo, "", 256, 256); - do_eval(&ds_echo, command->first_argument, FALSE); - dynstr_append_mem(ds, ds_echo.str, ds_echo.length); - dynstr_append_mem(ds, "\n", 1); - dynstr_free(&ds_echo); - command->last_argument= command->end; - return(0); -} - - -int do_sync_with_master2(long offset) -{ - MYSQL_RES* res; - MYSQL_ROW row; - MYSQL* mysql= &cur_con->mysql; + MYSQL *mysql= &cur_con->mysql; char query_buf[FN_REFLEN+128]; int tries= 0; int rpl_parse; @@ -1546,7 +2068,7 @@ int do_sync_with_master2(long offset) wait_for_position: if (mysql_query(mysql, query_buf)) - die("failed in %s: %d: %s", query_buf, mysql_errno(mysql), + die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), mysql_error(mysql)); if (!(res= mysql_store_result(mysql))) @@ -1569,13 +2091,14 @@ wait_for_position: if (rpl_parse) mysql_enable_rpl_parse(mysql); - return 0; + return; } -int do_sync_with_master(struct st_query *query) + +void do_sync_with_master(struct st_command *command) { long offset= 0; - char *p= query->first_argument; + char *p= command->first_argument; const char *offset_start= p; if (*offset_start) { @@ -1584,20 +2107,22 @@ int do_sync_with_master(struct st_query *query) if(*p && !my_isspace(charset_info, *p)) die("Invalid integer argument \"%s\"", offset_start); - query->last_argument= p; + command->last_argument= p; } - return do_sync_with_master2(offset); + 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_RES *res; MYSQL_ROW row; - MYSQL* mysql = &cur_con->mysql; + MYSQL *mysql = &cur_con->mysql; const char *query; int rpl_parse; @@ -1606,19 +2131,18 @@ int do_save_master_pos() #ifdef HAVE_NDB_BINLOG /* - Wait for ndb binlog to be up-to-date with all changes - done on the local mysql server + 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("At line %u: failed in %s: %d: %s", start_lineno, query, + die("'%s' failed: %d %s", query, mysql_errno(mysql), mysql_error(mysql)); if (!(res= mysql_store_result(mysql))) - die("line %u: mysql_store_result() retuned NULL for '%s'", start_lineno, - query); + die("mysql_store_result() returned NULL for '%s'", query); if (!(row= mysql_fetch_row(res))) - die("line %u: empty result in %s", start_lineno, query); + die("Query '%s' returned empty result", query); have_ndbcluster= strcmp("YES", row[1]) == 0; mysql_free_result(res); @@ -1626,7 +2150,7 @@ int do_save_master_pos() if (have_ndbcluster) { ulonglong start_epoch= 0, applied_epoch= 0, - latest_epoch=0, latest_trans_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; @@ -1647,11 +2171,10 @@ int do_save_master_pos() if (count) sleep(1); if (mysql_query(mysql, query= "show engine ndb status")) - die("At line %u: failed in '%s': %d: %s", start_lineno, query, + die("failed in '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); if (!(res= mysql_store_result(mysql))) - die("line %u: mysql_store_result() retuned NULL for '%s'", - start_lineno, query); + die("mysql_store_result() returned NULL for '%s'", query); while ((row= mysql_fetch_row(res))) { if (strcmp(row[1], binlog) == 0) @@ -1668,8 +2191,8 @@ int do_save_master_pos() latest_epoch= strtoull(status, (char**) 0, 10); } else - die("line %u: result does not contain '%s' in '%s'", - start_lineno, latest_epoch_str, query); + 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)) @@ -1680,10 +2203,10 @@ int do_save_master_pos() latest_trans_epoch= strtoull(status, (char**) 0, 10); } else - die("line %u: result does not contain '%s' in '%s'", - start_lineno, latest_trans_epoch_str, query); + die("result does not contain '%s' in '%s'", + latest_trans_epoch_str, query); /* latest_received_binlog_epoch */ - while (*status && + while (*status && strncmp(status, latest_received_binlog_epoch_str, sizeof(latest_received_binlog_epoch_str)-1)) status++; @@ -1693,10 +2216,10 @@ int do_save_master_pos() latest_received_binlog_epoch= strtoull(status, (char**) 0, 10); } else - die("line %u: result does not contain '%s' in '%s'", - start_lineno, latest_received_binlog_epoch_str, query); + die("result does not contain '%s' in '%s'", + latest_received_binlog_epoch_str, query); /* latest_handled_binlog */ - while (*status && + while (*status && strncmp(status, latest_handled_binlog_epoch_str, sizeof(latest_handled_binlog_epoch_str)-1)) status++; @@ -1706,10 +2229,10 @@ int do_save_master_pos() latest_handled_binlog_epoch= strtoull(status, (char**) 0, 10); } else - die("line %u: result does not contain '%s' in '%s'", - start_lineno, latest_handled_binlog_epoch_str, query); + die("result does not contain '%s' in '%s'", + latest_handled_binlog_epoch_str, query); /* latest_applied_binlog_epoch */ - while (*status && + while (*status && strncmp(status, latest_applied_binlog_epoch_str, sizeof(latest_applied_binlog_epoch_str)-1)) status++; @@ -1719,16 +2242,16 @@ int do_save_master_pos() latest_applied_binlog_epoch= strtoull(status, (char**) 0, 10); } else - die("line %u: result does not contain '%s' in '%s'", - start_lineno, latest_applied_binlog_epoch_str, query); + 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("line %u: result does not contain '%s' in '%s'", - start_lineno, binlog, query); + die("result does not contain '%s' in '%s'", + binlog, query); if (latest_applied_binlog_epoch > applied_epoch) count= 0; applied_epoch= latest_applied_binlog_epoch; @@ -1745,7 +2268,7 @@ int do_save_master_pos() } #endif if (mysql_query(mysql, query= "show master status")) - die("failed in show master status: %d: %s", + die("failed in 'show master status': %d %s", mysql_errno(mysql), mysql_error(mysql)); if (!(res = mysql_store_result(mysql))) @@ -1767,28 +2290,29 @@ int do_save_master_pos() Assign the variable with SYNOPSIS - do_let() - query called command + do_let() + query called command DESCRIPTION - let $= + 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 ; + - 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 + Program will die if error detected */ -int do_let(struct st_query *query) +void do_let(struct st_command *command) { - int ret; - char *p= query->first_argument; + 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 */ @@ -1810,34 +2334,18 @@ int do_let(struct st_query *query) while (*p && my_isspace(charset_info,*p)) p++; - do_eval(&let_rhs_expr, p, FALSE); + do_eval(&let_rhs_expr, p, command->end, FALSE); - query->last_argument= query->end; + command->last_argument= command->end; /* Assign var_val to var_name */ - ret= var_set(var_name, var_name_end, let_rhs_expr.str, - (let_rhs_expr.str + let_rhs_expr.length)); + var_set(var_name, var_name_end, let_rhs_expr.str, + (let_rhs_expr.str + let_rhs_expr.length)); dynstr_free(&let_rhs_expr); - - return(ret); + DBUG_VOID_RETURN; } -/* - Store an integer (typically the returncode of the last SQL) - statement in the mysqltest builtin variable $mysql_errno, by - simulating of a user statement "let $mysql_errno= " -*/ - -int var_set_errno(int sql_errno) -{ - const char *var_name= "$mysql_errno"; - char var_val[21]; - uint length= my_sprintf(var_val, (var_val, "%d", sql_errno)); - return var_set(var_name, var_name + 12, var_val, var_val + length); -} - - -int do_rpl_probe(struct st_query *query __attribute__((unused))) +int do_rpl_probe(struct st_command *command __attribute__((unused))) { DBUG_ENTER("do_rpl_probe"); if (mysql_rpl_probe(&cur_con->mysql)) @@ -1846,14 +2354,14 @@ int do_rpl_probe(struct st_query *query __attribute__((unused))) } -int do_enable_rpl_parse(struct st_query *query __attribute__((unused))) +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_query *query __attribute__((unused))) +int do_disable_rpl_parse(struct st_command *command __attribute__((unused))) { mysql_disable_rpl_parse(&cur_con->mysql); return 0; @@ -1861,17 +2369,17 @@ int do_disable_rpl_parse(struct st_query *query __attribute__((unused))) /* - Sleep the number of specifed seconds + 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 + 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 + 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. @@ -1882,26 +2390,26 @@ int do_disable_rpl_parse(struct st_query *query __attribute__((unused))) used for cpu-independent delays. */ -int do_sleep(struct st_query *query, my_bool real_sleep) +int do_sleep(struct st_command *command, my_bool real_sleep) { int error= 0; - char *p= query->first_argument; - char *sleep_start, *sleep_end= query->end; + 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", query->first_word_len, query->query); + 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\"", query->first_word_len, query->query, - query->first_argument); + 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\"", query->first_word_len, query->query, - query->first_argument); + 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) @@ -1910,13 +2418,15 @@ int do_sleep(struct st_query *query, my_bool real_sleep) DBUG_PRINT("info", ("sleep_val: %f", sleep_val)); if (sleep_val) my_sleep((ulong) (sleep_val * 1000000L)); - query->last_argument= sleep_end; + command->last_argument= sleep_end; return 0; } -static void get_file_name(char *filename, struct st_query *q) + +void do_get_file_name(struct st_command *command, + char* dest, uint dest_max_len) { - char *p= q->first_argument, *name; + char *p= command->first_argument, *name; if (!*p) die("Missing file name argument"); name= p; @@ -1924,13 +2434,14 @@ static void get_file_name(char *filename, struct st_query *q) p++; if (*p) *p++= 0; - q->last_argument= p; - strmake(filename, name, FN_REFLEN); + command->last_argument= p; + strmake(dest, name, dest_max_len); } -static void set_charset(struct st_query *q) + +void do_set_charset(struct st_command *command) { - char *charset_name= q->first_argument; + char *charset_name= command->first_argument; char *p; if (!charset_name || !*charset_name) @@ -1941,78 +2452,181 @@ static void set_charset(struct st_query *q) p++; if(*p) *p++= 0; - q->last_argument= p; + 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); } -static uint get_errcodes(match_err *to,struct st_query *q) + +#if MYSQL_VERSION_ID >= 50000 +/* List of error names to error codes, available from 5.0 */ +typedef struct { - char *p= q->first_argument; + const char *name; + uint code; +} st_error; + +static st_error global_error_names[] = +{ +#include + { 0, 0 } +}; +#define HAVE_MYSQLD_ERNAME +#endif + + +uint get_errcode_from_name(char *error_name, char *error_end) +{ + DBUG_ENTER("get_errcode_from_name"); +#ifdef HAVE_MYSQLD_ERNAME + + /* SQL error as string */ + st_error *e= global_error_names; + 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); +#else + LINT_INIT(error_name); + LINT_INIT(error_end); + abort_not_in_this_version(); +#endif + DBUG_RETURN(0);; +} + + +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("get_errcodes"); + DBUG_ENTER("do_get_errcodes"); if (!*p) - die("Missing argument in %s", q->query); + 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') { - /* SQLSTATE string */ - char *end= ++p + SQLSTATE_LENGTH; - char *to_ptr= to[count].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 */ + char *to_ptr= to->code.sqlstate; + 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"); + } - for (; my_isalnum(charset_info, *p) && p != end; p++) - *to_ptr++= *p; *to_ptr= 0; - - to[count].type= ERR_SQLSTATE; + to->type= ERR_SQLSTATE; + DBUG_PRINT("info", ("ERR_SQLSTATE: %d", to->code.sqlstate)); + } + else if (*p == 's') + { + die("The sqlstate definition must start with an uppercase S"); } else if (*p == 'E') { - /* SQL error as string */ - st_error *e= global_error; - char *start= p++; + /* Error name string */ - for (; *p == '_' || my_isalnum(charset_info, *p); p++) - ; - 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(start, e->name, (int) (p - start)) && - (uint) strlen(e->name) == (uint) (p - start)) - { - to[count].code.errnum= (uint) e->code; - to[count].type= ERR_ERRNO; - break; - } - } - if (!e->name) - die("Unknown SQL error '%s'", start); + 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++; + } - if (!(p= str2int(p,10,(long) INT_MIN, (long) INT_MAX, &val))) - die("Invalid argument in %s", q->query); - to[count].code.errnum= (uint) val; - to[count].type= ERR_ERRNO; + /* 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++; - } while (*(p++) == ','); - q->last_argument= (p - 1); - to[count].type= ERR_EMPTY; /* End of data */ - DBUG_RETURN(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 ' @@ -2020,11 +2634,10 @@ static uint get_errcodes(match_err *to,struct st_query *q) If string is a '$variable', return the value of the variable. */ - -static char *get_string(char **to_ptr, char **from_ptr, - struct st_query *q) +char *get_string(char **to_ptr, char **from_ptr, + struct st_command *command) { - reg1 char c,sep; + char c, sep; char *to= *to_ptr, *from= *from_ptr, *start=to; DBUG_ENTER("get_string"); @@ -2070,7 +2683,7 @@ static char *get_string(char **to_ptr, char **from_ptr, *to++=c; } if (*from != ' ' && *from) - die("Wrong string argument in %s", q->query); + die("Wrong string argument in %s", command->query); while (my_isspace(charset_info,*from)) /* Point to next string */ from++; @@ -2093,282 +2706,22 @@ static char *get_string(char **to_ptr, char **from_ptr, DBUG_RETURN(start); } -/* - 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 -*/ - -static struct st_replace_regex* init_replace_regex(char* expr) +void set_reconnect(MYSQL* mysql, int val) { - 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,(gptr) ®)) - 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((gptr)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 -*/ - -static 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,(gptr)&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 - -*/ -static void get_replace_regex(struct st_query *q) -{ - char *expr= q->first_argument; - free_replace_regex(); - if (!(glob_replace_regex=init_replace_regex(expr))) - die("Could not init replace_regex"); - q->last_argument= q->end; +#if MYSQL_VERSION_ID < 50000 + mysql->reconnect= val; +#else + int reconnect= val; + mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); +#endif } -/* - 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. -*/ - -static void get_replace(struct st_query *q) +struct st_connection * find_connection_by_name(const char *name) { - uint i; - char *from= q->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", q->query); - start=buff=my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); - while (*from) - { - char *to=buff; - to=get_string(&buff, &from, q); - if (!*from) - die("Wrong number of arguments to replace_result in '%s'", q->query); - insert_pointer_name(&from_array,to); - to=get_string(&buff, &from, q); - 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'", q->query); - free_pointer_array(&from_array); - free_pointer_array(&to_array); - my_free(start, MYF(0)); - q->last_argument= q->end; - DBUG_VOID_RETURN; -} - -static void free_replace_regex() -{ - if (glob_replace_regex) - { - 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((char*) glob_replace_regex,MYF(0)); - glob_replace_regex=0; - } -} - - -void free_replace() -{ - DBUG_ENTER("free_replace"); - if (glob_replace) - { - my_free((char*) glob_replace,MYF(0)); - glob_replace=0; - } - DBUG_VOID_RETURN; -} - -struct connection * find_connection_by_name(const char *name) -{ - struct connection *con; - for (con= cons; con < next_con; con++) + struct st_connection *con; + for (con= connections; con < next_con; con++) { if (!strcmp(con->name, name)) { @@ -2390,10 +2743,10 @@ int select_connection_name(const char *name) } -int select_connection(struct st_query *query) +int select_connection(struct st_command *command) { char *name; - char *p= query->first_argument; + char *p= command->first_argument; DBUG_ENTER("select_connection"); if (!*p) @@ -2403,15 +2756,16 @@ int select_connection(struct st_query *query) p++; if (*p) *p++= 0; - query->last_argument= p; + command->last_argument= p; return select_connection_name(name); } -int close_connection(struct st_query *q) +void do_close_connection(struct st_command *command) { - char *p= q->first_argument, *name; - struct connection *con; + char *p= command->first_argument, *name; + struct st_connection *con; + DBUG_ENTER("close_connection"); DBUG_PRINT("enter",("name: '%s'",p)); @@ -2423,13 +2777,17 @@ int close_connection(struct st_query *q) if (*p) *p++= 0; - q->last_argument= p; - for (con= cons; con < next_con; con++) + command->last_argument= p; + + /* Loop through connection pool for connection to close */ + for (con= connections; con < next_con; con++) { + DBUG_PRINT("info", ("con->name: %s", con->name)); if (!strcmp(con->name, name)) { + DBUG_PRINT("info", ("Closing connection %s", con->name)); #ifndef EMBEDDED_LIBRARY - if (q->type == Q_DIRTY_CLOSE) + if (command->type == Q_DIRTY_CLOSE) { if (con->mysql.net.vio) { @@ -2443,59 +2801,19 @@ int close_connection(struct st_query *q) mysql_close(con->util_mysql); con->util_mysql= 0; my_free(con->name, MYF(0)); + /* - When the connection is closed set name to "closed_connection" - to make it possible to reuse the connection name. - The connection slot will not be reused - */ + When the connection is closed set name to "closed_connection" + to make it possible to reuse the connection name. + The connection slot will not be reused + */ if (!(con->name = my_strdup("closed_connection", MYF(MY_WME)))) die("Out of memory"); - DBUG_RETURN(0); + + DBUG_VOID_RETURN; } } die("connection '%s' not found in connection pool", name); - DBUG_RETURN(1); /* Never reached */ -} - - -/* - This one now is a hack - we may want to improve in in the - future to handle quotes. For now we assume that anything that is not - a comma, a space or ) belongs to the argument. space is a chopper, comma or - ) are delimiters/terminators - - SYNOPSIS - safe_get_param - str - string to get param from - arg - pointer to string where result will be stored - msg - Message to display if param is not found - if msg is 0 this param is not required and param may be empty - - RETURNS - pointer to str after param - -*/ - -char* safe_get_param(char *str, char** arg, const char *msg) -{ - DBUG_ENTER("safe_get_param"); - if(!*str) - { - if (msg) - die(msg); - *arg= str; - DBUG_RETURN(str); - } - while (*str && my_isspace(charset_info,*str)) - str++; - *arg= str; - while (*str && *str != ',' && *str != ')') - str++; - if (msg && !*arg) - die(msg); - - *str++= 0; - DBUG_RETURN(str); } @@ -2503,54 +2821,60 @@ char* safe_get_param(char *str, char** arg, const char *msg) 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 + 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 + Sometimes in a test the client starts before + the server - to solve the problem, we try again + after some sleep if connection fails the first + time - This function will try to connect to the given server - "opt_max_connect_retries" times and sleep "connection_retry_sleep" - seconds between attempts before finally giving up. - This helps in situation when the client starts - before the server (which happens sometimes). - It will ignore any errors during these retries. One should use - connect_n_handle_errors() if he expects a connection error and wants - handle as if it was an error from a usual statement. + 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. - RETURN VALUE - 0 - success, non-0 - failure */ -int safe_connect(MYSQL* mysql, const char *host, const char *user, - const char *pass, const char *db, int port, const char *sock) +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 con_error= 1; - my_bool reconnect= 1; + int failed_attempts= 0; static ulong connection_retry_sleep= 100000; /* Microseconds */ - int i; - for (i= 0; i < opt_max_connect_retries; i++) + + DBUG_ENTER("safe_connect"); + while(!mysql_real_connect(mysql, host,user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { - if (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 + */ + + if (mysql_errno(mysql) == CR_CONNECTION_ERROR && + failed_attempts < opt_max_connect_retries) + my_sleep(connection_retry_sleep); + else { - con_error= 0; - break; + 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)); } - my_sleep(connection_retry_sleep); + failed_attempts++; } - /* - TODO: change this to 0 in future versions, but the 'kill' test relies on - existing behavior - */ - mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); - return con_error; + DBUG_VOID_RETURN; } @@ -2558,45 +2882,41 @@ int safe_connect(MYSQL* mysql, const char *host, const char *user, Connect to a server and handle connection errors in case they occur. SYNOPSIS - connect_n_handle_errors() - q - context of connect "query" (command) - con - connection structure to be used - host, user, pass, - connection parameters - db, port, sock - create_conn - out parameter, set to zero if connection was - not established and is not touched otherwise + 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. + 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 - RETURN VALUE - 0 - success, non-0 - failure */ -int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host, +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, - int* create_conn) + const char* db, int port, const char* sock) { DYNAMIC_STRING *ds; - my_bool reconnect= 1; - int error= 0; ds= &ds_res; - if (!disable_query_log) + /* Only log if an error is expected */ + if (!command->abort_on_error && + !disable_query_log) { /* - It is nice to have connect() statement logged in result file - in this case. - QQ: Should we do this only if we are expecting an error ? + Log the connect to result log */ - char port_buff[22]; /* This should be enough for any int */ - char *port_end; dynstr_append_mem(ds, "connect(", 8); replace_dynstr_append(ds, host); dynstr_append_mem(ds, ",", 1); @@ -2607,8 +2927,7 @@ int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host, if (db) replace_dynstr_append(ds, db); dynstr_append_mem(ds, ",", 1); - port_end= int10_to_str(port, port_buff, 10); - replace_dynstr_append_mem(ds, port_buff, port_end - port_buff); + replace_dynstr_append_uint(ds, port); dynstr_append_mem(ds, ",", 1); if (sock) replace_dynstr_append(ds, sock); @@ -2619,148 +2938,150 @@ int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host, if (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock: 0, CLIENT_MULTI_STATEMENTS)) { - handle_error("connect", q, mysql_errno(con), mysql_error(con), + handle_error(command, mysql_errno(con), mysql_error(con), mysql_sqlstate(con), ds); - *create_conn= 0; - goto err; + return 0; /* Not connected */ } - handle_no_error(q); - - /* - TODO: change this to 0 in future versions, but the 'kill' test relies on - existing behavior - */ - mysql_options(con, MYSQL_OPT_RECONNECT, (char *)&reconnect); - -err: - free_replace(); - free_replace_regex(); - return error; + handle_no_error(command); + return 1; /* Connected */ } /* Open a new connection to MySQL Server with the parameters - specified + specified. Make the new connection the current connection. SYNOPSIS - do_connect() - q called command + do_connect() + q called command DESCRIPTION - connect(,,,,,[,[]]); + 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 + - 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 - */ +*/ -int do_connect(struct st_query *q) +void do_connect(struct st_command *command) { - char *con_name, *con_user,*con_pass, *con_host, *con_port_str, - *con_db, *con_sock, *con_options; - char *con_buf, *p; - char buff[FN_REFLEN]; - int con_port; - bool con_ssl= 0; - bool con_compress= 0; - int free_con_sock= 0; - int error= 0; - int create_conn= 1; - VAR *var_port, *var_sock; + int con_port= port; + char *con_options; + bool con_ssl= 0, con_compress= 0; + char *ptr; + + DYNAMIC_STRING ds_connection_name; + DYNAMIC_STRING ds_host; + DYNAMIC_STRING ds_user; + DYNAMIC_STRING ds_password; + DYNAMIC_STRING ds_database; + DYNAMIC_STRING ds_port; + DYNAMIC_STRING ds_sock; + 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, + "Dtabase 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", q->first_argument)); + DBUG_PRINT("enter",("connect: %s", command->first_argument)); - /* Make a copy of query before parsing, safe_get_param will modify */ - if (!(con_buf= my_strdup(q->first_argument, MYF(MY_WME)))) - die("Could not allocate con_buf"); - p= con_buf; - - if (*p != '(') - die("Syntax error in connect - expected '(' found '%c'", *p); - p++; - p= safe_get_param(p, &con_name, "Missing connection name"); - p= safe_get_param(p, &con_host, "Missing connection host"); - p= safe_get_param(p, &con_user, "Missing connection user"); - p= safe_get_param(p, &con_pass, "Missing connection password"); - p= safe_get_param(p, &con_db, "Missing connection db"); - - /* Port */ - p= safe_get_param(p, &con_port_str, 0); - if (*con_port_str) + /* Remove parenteses around connect arguments */ + if ((ptr= strstr(command->first_argument, "("))) { - if (*con_port_str == '$') + /* Replace it with a space */ + *ptr= ' '; + if ((ptr= strstr(command->first_argument, ")"))) { - if (!(var_port= var_get(con_port_str, 0, 0, 0))) - die("Unknown variable '%s'", con_port_str+1); - con_port= var_port->int_val; + /* Replace it with \0 */ + *ptr= 0; } else - { - con_port= atoi(con_port_str); - if (con_port == 0) - die("Illegal argument for port: '%s'", con_port_str); - } + die("connect - argument list started with '(' must be ended with ')'"); } - else + + check_command_args(command, command->first_argument, connect_args, + sizeof(connect_args)/sizeof(struct command_arg), + ','); + + /* Port */ + if (ds_port.length) { - con_port= port; + con_port= atoi(ds_port.str); + if (con_port == 0) + die("Illegal argument for port: '%s'", ds_port.str); } /* Sock */ - p= safe_get_param(p, &con_sock, 0); - if (*con_sock) + if (ds_sock.length) { - if (*con_sock == '$') + /* + If the socket is specified just as a name without path + append tmpdir in front + */ + if (*ds_sock.str != FN_LIBCHAR) { - if (!(var_sock= var_get(con_sock, 0, 0, 0))) - die("Unknown variable '%s'", con_sock+1); - if (!(con_sock= (char*)my_malloc(var_sock->str_val_len+1, MYF(0)))) - die("Out of memory"); - free_con_sock= 1; - memcpy(con_sock, var_sock->str_val, var_sock->str_val_len); - con_sock[var_sock->str_val_len]= 0; + char buff[FN_REFLEN]; + fn_format(buff, ds_sock.str, TMPDIR, "", 0); + dynstr_set(&ds_sock, buff); } } else { - con_sock= (char*) unix_sock; + /* No socket specified, use default */ + dynstr_set(&ds_sock, unix_sock); } + DBUG_PRINT("info", ("socket: %s", ds_sock.str)); + /* Options */ - p= safe_get_param(p, &con_options, 0); + con_options= ds_options.str; while (*con_options) { - char* str= con_options; - while (*str && !my_isspace(charset_info, *str)) - str++; - *str++= 0; - if (!strcmp(con_options, "SSL")) + 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 (!strcmp(con_options, "COMPRESS")) + else if (!strncmp(con_options, "COMPRESS", 8)) con_compress= 1; else - die("Illegal option to connect: %s", con_options); - con_options= str; + die("Illegal option to connect: %.*s", end - con_options, con_options); + /* Process next option */ + con_options= end; } - /* Note: 'p' is pointing into the copy 'con_buf' */ - q->last_argument= q->first_argument + (p - con_buf); - if (next_con == cons_end) - die("Connection limit exhausted - increase MAX_CONS in mysqltest.c"); + if (next_con == connections_end) + die("Connection limit exhausted, you can have max %d connections", + (sizeof(connections)/sizeof(struct st_connection))); - if (find_connection_by_name(con_name)) - die("Connection %s already exists", con_name); + if (find_connection_by_name(ds_connection_name.str)) + die("Connection %s already exists", ds_connection_name.str); if (!mysql_init(&next_con->mysql)) die("Failed on mysql_init()"); @@ -2772,53 +3093,56 @@ int do_connect(struct st_query *q) #ifdef HAVE_OPENSSL if (opt_use_ssl || con_ssl) { - /* Turn on ssl_verify_server_cert only if host is "localhost" */ - opt_ssl_verify_server_cert= !strcmp(con_host, "localhost"); - mysql_ssl_set(&next_con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, opt_ssl_cipher); +#if MYSQL_VERSION_ID >= 50000 + /* Turn on ssl_verify_server_cert only if host is "localhost" */ + opt_ssl_verify_server_cert= !strcmp(ds_connection_name.str, "localhost"); mysql_options(&next_con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &opt_ssl_verify_server_cert); +#endif } #endif - if (con_sock && !free_con_sock && *con_sock && *con_sock != FN_LIBCHAR) - con_sock=fn_format(buff, con_sock, TMPDIR, "", 0); - if (!con_db[0]) - con_db= db; - /* Special database to allow one to connect without a database name */ - if (con_db && !strcmp(con_db,"*NO-ONE*")) - con_db= 0; - if (q->abort_on_error) - { - if (safe_connect(&next_con->mysql, con_host, con_user, con_pass, - con_db, con_port, con_sock ? con_sock: 0)) - die("Could not open connection '%s': %d %s", con_name, - mysql_errno(&next_con->mysql), mysql_error(&next_con->mysql)); - } - else - error= connect_n_handle_errors(q, &next_con->mysql, con_host, con_user, - con_pass, con_db, con_port, con_sock, - &create_conn); - if (create_conn) + /* Use default db name */ + if (ds_database.length == 0) + dynstr_set(&ds_database, 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, &next_con->mysql, + ds_host.str,ds_user.str, + ds_password.str, ds_database.str, + con_port, ds_sock.str)) { - if (!(next_con->name= my_strdup(con_name, MYF(MY_WME)))) - die(NullS); + DBUG_PRINT("info", ("Inserting connection %s in connection pool", + ds_connection_name.str)); + if (!(next_con->name= my_strdup(ds_connection_name.str, MYF(MY_WME)))) + die("Out of memory"); cur_con= next_con++; } - if (free_con_sock) - my_free(con_sock, MYF(MY_WME)); - my_free(con_buf, MYF(MY_WME)); - DBUG_RETURN(error); + + 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_query *q) +int do_done(struct st_command *command) { /* Check if empty block stack */ if (cur_block == block_stack) { - if (*q->query != '}') + if (*command->query != '}') die("Stray 'end' command - end of block before beginning"); die("Stray '}' - end of block before beginning"); } @@ -2844,31 +3168,31 @@ int do_done(struct st_query *q) Process start of a "if" or "while" statement SYNOPSIS - do_block() - cmd Type of block - q called command + do_block() + cmd Type of block + q called command DESCRIPTION - if ([!]) - { - - } + if ([!]) + { + + } - while ([!]) - { - - } + 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. + 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_query* q) +void do_block(enum block_cmd cmd, struct st_command* command) { - char *p= q->first_argument; + char *p= command->first_argument; const char *expr_start, *expr_end; VAR v; const char *cmd_name= (cmd == cmd_while ? "while" : "if"); @@ -2912,9 +3236,7 @@ void do_block(enum block_cmd cmd, struct st_query* q) while (*p && my_isspace(charset_info, *p)) p++; - if (*p == '{') - die("Missing newline between %s and '{'", cmd_name); - if (*p) + if (*p && *p != '{') die("Missing '{' after %s. Found \"%s\"", cmd_name, p); var_init(&v,0,0,0,0); @@ -2935,43 +3257,41 @@ void do_block(enum block_cmd cmd, struct st_query* q) } -/* - Read characters from line buffer or file. This is needed to allow - my_ungetc() to buffer MAX_DELIMITER 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) +void do_delimiter(struct st_command* command) { - if (line_buffer_pos == line_buffer) - return fgetc(file); - return *--line_buffer_pos; -} + char* p= command->first_argument; + DBUG_ENTER("do_delimiter"); + DBUG_PRINT("enter", ("first_argument: %s", command->first_argument)); -void my_ungetc(int c) -{ - *line_buffer_pos++= (char) c; + 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 end_of_query(int c) +my_bool match_delimiter(int c, const char *delim, uint length) { uint i; - char tmp[MAX_DELIMITER]; + char tmp[MAX_DELIMITER_LENGTH]; - if (c != *delimiter) + if (c != *delim) return 0; - for (i= 1; i < delimiter_length && - (c= my_getc(cur_file->file)) == *(delimiter + i); + for (i= 1; i < length && + (c= my_getc(cur_file->file)) == *(delim + i); i++) tmp[i]= c; - if (i == delimiter_length) + if (i == length) return 1; /* Found delimiter */ /* didn't find delimiter, push back things that we read */ @@ -2982,45 +3302,51 @@ my_bool end_of_query(int c) } +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 + 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. + 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. + 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. + 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) { - int c; - char quote; + char c, last_quote; char *p= buf, *buf_end= buf + size - 1; - int no_save= 0; - enum {R_NORMAL, R_Q, R_Q_IN_Q, R_SLASH_IN_Q, - R_COMMENT, R_LINE_START} state= R_LINE_START; + 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(quote); + LINT_INIT(last_quote); start_lineno= cur_file->lineno; + DBUG_PRINT("info", ("start_lineno: %d", start_lineno)); for (; p < buf_end ;) { - no_save= 0; + skip_char= 0; c= my_getc(cur_file->file); if (feof(cur_file->file)) { @@ -3036,12 +3362,13 @@ int read_line(char *buf, int size) { /* 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")); - DBUG_RETURN(1); + DBUG_RETURN(1); } cur_file--; start_lineno= cur_file->lineno; @@ -3055,61 +3382,74 @@ int read_line(char *buf, int size) /* Convert cr/lf to lf */ if (p != buf && *(p-1) == '\r') - *(p-1)= 0; + p--; } switch(state) { case R_NORMAL: - /* Only accept '{' in the beginning of a line */ if (end_of_query(c)) { *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s'", delimiter)); + DBUG_RETURN(0); + } + else if ((c == '{' && + (!strncasecmp(buf, "while", min(5, p - buf)) || + !strncasecmp(buf, "if", min(2, p - buf))))) + { + /* Only if and while commands can be terminated by { */ + *p++= c; + *p= 0; + DBUG_PRINT("exit", ("Found '{' indicating begining of block")); DBUG_RETURN(0); } else if (c == '\'' || c == '"' || c == '`') { - quote= c; + last_quote= c; state= R_Q; } - else if (c == '\n') - { - state = R_LINE_START; - } break; + case R_COMMENT: if (c == '\n') { + /* Comments are terminated by newline */ *p= 0; + DBUG_PRINT("exit", ("Found newline in comment")); DBUG_RETURN(0); } break; + case R_LINE_START: - /* Only accept start of comment if this is the first line in query */ - if ((cur_file->lineno == start_lineno) && - (c == '#' || c == '-' || parsing_disabled)) + 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') start_lineno= cur_file->lineno; /* Query hasn't started yet */ - no_save= 1; + skip_char= 1; + } + else if (end_of_query(c)) + { + *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s'", delimiter)); + DBUG_RETURN(0); } else if (c == '}') { - *buf++= '}'; - *buf= 0; - DBUG_RETURN(0); - } - else if (end_of_query(c) || 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")); DBUG_RETURN(0); } else if (c == '\'' || c == '"' || c == '`') { - quote= c; + last_quote= c; state= R_Q; } else @@ -3117,29 +3457,19 @@ int read_line(char *buf, int size) break; case R_Q: - if (c == quote) - state= R_Q_IN_Q; + if (c == last_quote) + state= R_NORMAL; else if (c == '\\') state= R_SLASH_IN_Q; break; - case R_Q_IN_Q: - if (end_of_query(c)) - { - *p= 0; - DBUG_RETURN(0); - } - if (c != quote) - state= R_NORMAL; - else - state= R_Q; - break; + case R_SLASH_IN_Q: state= R_Q; break; } - if (!no_save) + if (!skip_char) { /* Could be a multibyte character */ /* This code is based on the code in "sql_load.cc" */ @@ -3157,7 +3487,7 @@ int read_line(char *buf, int size) for (i= 1; i < charlen; i++) { if (feof(cur_file->file)) - goto found_eof; /* FIXME: could we just break here?! */ + goto found_eof; c= my_getc(cur_file->file); *p++ = c; } @@ -3174,99 +3504,253 @@ int read_line(char *buf, int size) *p++= c; } } - *p= 0; /* Always end with \0 */ - DBUG_RETURN(feof(cur_file->file)); + die("The input buffer is too small for this query.x\n" \ + "check your query or increase MAX_QUERY and recompile"); + DBUG_RETURN(0); } + /* - Create a query from a set of lines + Convert the read query to result format version 1 - SYNOPSIS - read_query() - q_ptr pointer where to return the new query + That is: After newline, all spaces need to be skipped + unless the previous char was a quote - DESCRIPTION - Converts lines returned by read_line into a query, this involves - parsing the first word in the read line to find the query 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. + This is due to an old bug that has now been fixed, but the + version 1 output format is preserved by using this function */ -static char read_query_buf[MAX_QUERY]; - -int read_query(struct st_query** q_ptr) +void convert_to_format_v1(char* query) { - char *p= read_query_buf; - struct st_query* q; - DBUG_ENTER("read_query"); + int last_c_was_quote= 0; + char *p= query, *write= query; + char *end= strend(query); + char last_c; + + while (p <= end) + { + if (*p == '\n' && !last_c_was_quote) + { + *write++ = *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; + *write++ = *p++; + + /* Copy anything until the next quote of same type */ + while (*p && *p != last_c) + *write++ = *p++; + + *write++ = *p++; + + last_c_was_quote= 1; + } + else + { + *write++ = *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) /* 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, (gptr) q_ptr, parser.current_line) ; + get_dynamic(&q_lines, (gptr) command_ptr, parser.current_line) ; DBUG_RETURN(0); } - if (!(*q_ptr= q= (struct st_query*) my_malloc(sizeof(*q), MYF(MY_WME))) || - insert_dynamic(&q_lines, (gptr) &q)) + if (!(*command_ptr= command= + (struct st_command*) my_malloc(sizeof(*command), MYF(MY_WME))) || + insert_dynamic(&q_lines, (gptr) &command)) die(NullS); - q->record_file[0]= 0; - q->require_file= 0; - q->first_word_len= 0; + command->require_file[0]= 0; + command->first_word_len= 0; + command->query_len= 0; - q->type= Q_UNKNOWN; - q->query_buf= q->query= 0; - read_query_buf[0]= 0; - if (read_line(read_query_buf, sizeof(read_query_buf))) + command->type= Q_UNKNOWN; + command->query_buf= command->query= 0; + read_command_buf[0]= 0; + if (read_line(read_command_buf, sizeof(read_command_buf))) { - check_eol_junk(read_query_buf); + check_eol_junk(read_command_buf); DBUG_RETURN(1); } - - DBUG_PRINT("info", ("query: %s", read_query_buf)); + + convert_to_format_v1(read_command_buf); + + DBUG_PRINT("info", ("query: %s", read_command_buf)); if (*p == '#') { - q->type= Q_COMMENT; - /* This goto is to avoid losing the "expected error" info. */ - goto end; + command->type= Q_COMMENT; } - if (!parsing_disabled) + else if (p[0] == '-' && p[1] == '-') { - memcpy((gptr) q->expected_errno, (gptr) global_expected_errno, - sizeof(global_expected_errno)); - q->expected_errors= global_expected_errors; - q->abort_on_error= (global_expected_errors == 0 && abort_on_error); + command->type= Q_COMMENT_WITH_COMMAND; + p+= 2; /* Skip past -- */ } - if (p[0] == '-' && p[1] == '-') - { - q->type= Q_COMMENT_WITH_COMMAND; - p+= 2; /* To calculate first word */ - } - else if (!parsing_disabled) - { - while (*p && my_isspace(charset_info, *p)) - p++ ; - } - -end: + /* Skip leading spaces */ while (*p && my_isspace(charset_info, *p)) p++; - if (!(q->query_buf= q->query= my_strdup(p, MYF(MY_WME)))) - die(NullS); + if (!(command->query_buf= command->query= my_strdup(p, MYF(MY_WME)))) + die("Out of memory"); /* Calculate first word and first argument */ - for (p= q->query; *p && !my_isspace(charset_info, *p) ; p++) ; - q->first_word_len= (uint) (p - q->query); + for (p= command->query; *p && !my_isspace(charset_info, *p) ; p++) ; + command->first_word_len= (uint) (p - command->query); + + /* Skip spaces between command and first argument */ while (*p && my_isspace(charset_info, *p)) p++; - q->first_argument= p; - q->end= strend(q->query); + command->first_argument= p; + + command->end= strend(command->query); + command->query_len= (command->end - command->query); parser.read_lines++; DBUG_RETURN(0); } @@ -3278,8 +3762,6 @@ static struct my_option my_long_options[] = 0, 0, 0, 0, 0, 0}, {"basedir", 'b', "Basedir for tests.", (gptr*) &opt_basedir, (gptr*) &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"big-test", 'B', "Define BIG_TEST to 1.", (gptr*) &opt_big_test, - (gptr*) &opt_big_test, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"compress", 'C', "Use the compressed server/client protocol.", (gptr*) &opt_compress, (gptr*) &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -3319,8 +3801,8 @@ static struct my_option my_long_options[] = {"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.", - (gptr*) &result_file, (gptr*) &result_file, 0, GET_STR, REQUIRED_ARG, - 0, 0, 0, 0, 0, 0}, + (gptr*) &result_file_name, (gptr*) &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.", @@ -3348,8 +3830,6 @@ static struct my_option my_long_options[] = 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"user", 'u', "User for login.", (gptr*) &user, (gptr*) &user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"valgrind", 'N', "Define VALGRIND_TEST to 1.", (gptr*) &opt_valgrind_test, - (gptr*) &opt_valgrind_test, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"verbose", 'v', "Write more.", (gptr*) &verbose, (gptr*) &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"version", 'V', "Output version information and exit.", @@ -3363,7 +3843,7 @@ static struct my_option my_long_options[] = #include -static void print_version(void) +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); @@ -3384,6 +3864,52 @@ void usage() #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) @@ -3398,35 +3924,35 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), record = 1; break; case 'x': + { + char buff[FN_REFLEN]; + if (!test_if_hard_path(argument)) { - 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: errno = %d", buff, errno); - cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); - cur_file->lineno= 1; - break; + 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: 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)) { - 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; + 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) { @@ -3448,7 +3974,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), embedded_server_arg_count=1; embedded_server_args[0]= (char*) ""; } - if (embedded_server_arg_count == MAX_SERVER_ARGS-1 || + if (embedded_server_arg_count == MAX_EMBEDDED_SERVER_ARGS-1 || !(embedded_server_args[embedded_server_arg_count++]= my_strdup(argument, MYF(MY_FAE)))) { @@ -3456,8 +3982,7 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), } break; case 'F': - if (read_server_arguments(argument)) - die(NullS); + read_embedded_server_arguments(argument); break; case OPT_SKIP_SAFEMALLOC: #ifdef SAFEMALLOC @@ -3498,16 +4023,16 @@ int parse_args(int argc, char **argv) /* - Write the content of str into file + 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 + SYNOPSIS + str_to_file + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file */ -static void str_to_file(const char *fname, char *str, int size) +void str_to_file(const char *fname, char *str, int size) { int fd; char buff[FN_REFLEN]; @@ -3519,7 +4044,7 @@ static void str_to_file(const char *fname, char *str, int size) fn_format(buff, fname, "", "", MY_UNPACK_FILENAME); if ((fd= my_open(buff, O_WRONLY | O_CREAT | O_TRUNC, - MYF(MY_WME | MY_FFNF))) < 0) + MYF(MY_WME | MY_FFNF))) < 0) die("Could not open %s: errno = %d", buff, errno); if (my_write(fd, (byte*)str, size, MYF(MY_WME|MY_FNABP))) die("write failed"); @@ -3527,31 +4052,40 @@ static void str_to_file(const char *fname, char *str, int size) } -void dump_result_to_reject_file(const char *record_file, char *buf, int size) +void dump_result_to_reject_file(char *buf, int size) { char reject_file[FN_REFLEN]; - str_to_file(fn_format(reject_file, record_file, "", ".reject", + str_to_file(fn_format(reject_file, result_file_name, "", ".reject", MY_REPLACE_EXT), buf, size); } -void dump_result_to_log_file(const char *record_file, char *buf, int size) +void dump_result_to_log_file(char *buf, int size) { char log_file[FN_REFLEN]; - str_to_file(fn_format(log_file, record_file, "", ".log", + str_to_file(fn_format(log_file, result_file_name, "", ".log", MY_REPLACE_EXT), buf, size); } -void dump_progress(const char *record_file) +void dump_progress(void) { char log_file[FN_REFLEN]; - str_to_file(fn_format(log_file, record_file, "", ".progress", + str_to_file(fn_format(log_file, result_file_name, "", ".progress", MY_REPLACE_EXT), ds_progress.str, ds_progress.length); } -static void check_regerr(my_regex_t* r, int err) +void dump_warning_messages(void) +{ + char warn_file[FN_REFLEN]; + + str_to_file(fn_format(warn_file, result_file_name, "", ".warnings", + MY_REPLACE_EXT), + ds_warning_messages.str, ds_warning_messages.length); +} + +void check_regerr(my_regex_t* r, int err) { char err_buf[1024]; @@ -3559,204 +4093,7 @@ static void check_regerr(my_regex_t* r, int err) { my_regerror(err,r,err_buf,sizeof(err_buf)); die("Regex error: %s\n", err_buf); - } -} - -/* - 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 - */ -static 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* buf_end, *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 - - buf_end = buf + buf_len; - - 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) + 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= expr_p[1] - '0'; - } - - /* found a valid back_ref (eg. \1)*/ - if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) - { - int 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 += (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,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) - { - int 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= 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_regfree(&r); - *res_p= 0; - *buf_p= buf; - *buf_len_p= buf_len; - return 0; + } } @@ -3768,12 +4105,12 @@ 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 + Setup string patterns that will be used to detect filenames that + needs to be converted from Win to Unix format */ -static void init_win_path_patterns() +void init_win_path_patterns() { /* List of string patterns to match in order to find paths */ const char* paths[] = { "$MYSQL_TEST_DIR", @@ -3800,7 +4137,7 @@ static void init_win_path_patterns() p= my_strdup(paths[i], MYF(MY_FAE)); if (insert_dynamic(&patterns, (gptr) &p)) - die(NullS); + die(NullS); DBUG_PRINT("info", ("p: %s", p)); while (*p) @@ -3813,7 +4150,7 @@ static void init_win_path_patterns() DBUG_VOID_RETURN; } -static void free_win_path_patterns() +void free_win_path_patterns() { uint i= 0; for (i=0 ; i < patterns.elements ; i++) @@ -3828,17 +4165,17 @@ static void free_win_path_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. + 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 / + Ex: + val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' + => $MYSQL_TEST_DIR is found by strstr + => all \ from c:\mysql\m... until next space is converted into / */ -static void fix_win_paths(const char* val, int len) +void fix_win_paths(const char *val, int len) { uint i; char *p; @@ -3868,43 +4205,14 @@ static void fix_win_paths(const char* val, int len) } #endif -/* Append the string to ds, with optional replace */ -static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, - const char *val, int len) -{ -#ifdef __WIN__ - fix_win_paths(val, len); -#endif - - if (glob_replace_regex) - { - if (!multi_reg_replace(glob_replace_regex, (char*)val)) - { - val= glob_replace_regex->buf; - len= strlen(val); - } - } - - if (glob_replace) - replace_strings_append(glob_replace, ds, val, len); - else - dynstr_append_mem(ds, val, len); -} - - -/* Append zero-terminated string to ds, with optional replace */ -static void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) -{ - replace_dynstr_append_mem(ds, val, strlen(val)); -} /* Append the result for one field to the dynamic string ds */ -static void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, - const char* val, ulonglong len, bool is_null) +void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, + const char* val, ulonglong len, bool is_null) { if (col_idx < max_replace_column && replace_column[col_idx]) { @@ -3955,7 +4263,7 @@ static void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, Values may be converted with 'replace_column' */ -static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) +void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) { MYSQL_ROW row; uint num_fields= mysql_num_fields(res); @@ -3972,7 +4280,6 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1); } - free_replace_column(); } @@ -3981,8 +4288,8 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) with '\t'. Values may be converted with 'replace_column' */ -static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, - MYSQL_FIELD *fields, uint num_fields) +void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, + MYSQL_FIELD *fields, uint num_fields) { MYSQL_BIND *bind; my_bool *is_null; @@ -4028,8 +4335,6 @@ static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, die("fetch didn't end with MYSQL_NO_DATA from statement: %d %s", mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); - free_replace_column(); - for (i= 0; i < num_fields; i++) { /* Free data for output */ @@ -4046,9 +4351,9 @@ static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, Append metadata for fields to output */ -static void append_metadata(DYNAMIC_STRING *ds, - MYSQL_FIELD *field, - uint num_fields) +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" @@ -4059,44 +4364,36 @@ static void append_metadata(DYNAMIC_STRING *ds, field < field_end ; field++) { - char buff[22]; dynstr_append_mem(ds, field->catalog, - field->catalog_length); + 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); + field->org_table_length); dynstr_append_mem(ds, "\t", 1); dynstr_append_mem(ds, field->table, - field->table_length); + field->table_length); dynstr_append_mem(ds, "\t", 1); dynstr_append_mem(ds, field->org_name, - field->org_name_length); + field->org_name_length); dynstr_append_mem(ds, "\t", 1); dynstr_append_mem(ds, field->name, field->name_length); dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->type, buff, 10); - dynstr_append(ds, buff); + replace_dynstr_append_uint(ds, field->type); dynstr_append_mem(ds, "\t", 1); - longlong10_to_str((unsigned int) field->length, buff, 10); - dynstr_append(ds, buff); + replace_dynstr_append_uint(ds, field->length); dynstr_append_mem(ds, "\t", 1); - longlong10_to_str((unsigned int) field->max_length, buff, 10); - dynstr_append(ds, buff); + 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); - - int10_to_str((int) field->flags, buff, 10); - dynstr_append(ds, buff); + replace_dynstr_append_uint(ds, field->flags); dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->decimals, buff, 10); - dynstr_append(ds, buff); + replace_dynstr_append_uint(ds, field->decimals); dynstr_append_mem(ds, "\t", 1); - int10_to_str((int) field->charsetnr, buff, 10); - dynstr_append(ds, buff); + replace_dynstr_append_uint(ds, field->charsetnr); dynstr_append_mem(ds, "\n", 1); } } @@ -4106,8 +4403,8 @@ static void append_metadata(DYNAMIC_STRING *ds, Append affected row count and other info to output */ -static void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, - const char *info) +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)); @@ -4122,12 +4419,12 @@ static void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, /* - Display the table headings with the names tab separated + Display the table headings with the names tab separated */ -static void append_table_headings(DYNAMIC_STRING *ds, - MYSQL_FIELD *field, - uint num_fields) +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++) @@ -4143,10 +4440,10 @@ static void append_table_headings(DYNAMIC_STRING *ds, Fetch warnings from server and append to ds RETURN VALUE - Number of warnings appended to ds + Number of warnings appended to ds */ -static int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) +int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) { uint count; MYSQL_RES *warn_res; @@ -4195,9 +4492,9 @@ static int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) error - function will not return */ -static void run_query_normal(MYSQL *mysql, struct st_query *command, - int flags, char *query, int query_len, - DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) +void run_query_normal(MYSQL *mysql, struct st_command *command, + int flags, char *query, int query_len, + DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) { MYSQL_RES *res= 0; int err= 0, counter= 0; @@ -4205,20 +4502,20 @@ static void run_query_normal(MYSQL *mysql, struct st_query *command, DBUG_PRINT("enter",("flags: %d", flags)); DBUG_PRINT("enter", ("query: '%-.60s'", query)); - if (flags & QUERY_SEND) + if (flags & QUERY_SEND_FLAG) { /* - Send the query - */ + Send the query + */ if (mysql_send_query(mysql, query, query_len)) { - handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } } - if (!(flags & QUERY_REAP)) + if (!(flags & QUERY_REAP_FLAG)) DBUG_VOID_RETURN; do @@ -4226,22 +4523,22 @@ static void run_query_normal(MYSQL *mysql, struct st_query *command, /* 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(query, command, mysql_errno(mysql), mysql_error(mysql), + handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } /* - Store the result. If res is NULL, use mysql_field_count to - determine if that was expected - */ + Store the result. If res is NULL, use mysql_field_count to + determine if that was expected + */ if (!(res= mysql_store_result(mysql)) && mysql_field_count(mysql)) { - handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } @@ -4297,7 +4594,7 @@ static void run_query_normal(MYSQL *mysql, struct st_query *command, if (err > 0) { /* We got an error from mysql_next_result, maybe expected */ - handle_error(query, command, mysql_errno(mysql), mysql_error(mysql), + handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } @@ -4307,8 +4604,6 @@ static void run_query_normal(MYSQL *mysql, struct st_query *command, handle_no_error(command); end: - free_replace(); - free_replace_regex(); /* We save the return code (mysql_errno(mysql)) from the last call sent @@ -4324,58 +4619,61 @@ end: Handle errors which occurred during execution SYNOPSIS - handle_error() - query - query string - q - query context - err_errno - error number - err_error - error message - err_sqlstate - sql state - ds - dynamic string which is used for output buffer + 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. + If there is an unexpected error this function will abort mysqltest + immediately. RETURN VALUE - error - function will not return + error - function will not return */ -static void handle_error(const char *query, struct st_query *q, - unsigned int err_errno, const char *err_error, - const char *err_sqlstate, DYNAMIC_STRING *ds) +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 (q->require_file) + 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", query, err_errno, err_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", query); + abort_not_supported_test("Query '%s' failed, required functionality" \ + "not supported", command->query); } - if (q->abort_on_error) - die("query '%s' failed: %d: %s", query, err_errno, err_error); + if (command->abort_on_error) + die("query '%s' failed: %d: %s", command->query, err_errno, err_error); - for (i= 0 ; (uint) i < q->expected_errors ; i++) + DBUG_PRINT("info", ("expected_errors.count: %d", + command->expected_errors.count)); + for (i= 0 ; (uint) i < command->expected_errors.count ; i++) { - if (((q->expected_errno[i].type == ERR_ERRNO) && - (q->expected_errno[i].code.errnum == err_errno)) || - ((q->expected_errno[i].type == ERR_SQLSTATE) && - (strcmp(q->expected_errno[i].code.sqlstate, err_sqlstate) == 0))) + if (((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) && + (strcmp(command->expected_errors.err[i].code.sqlstate, + err_sqlstate) == 0))) { if (!disable_result_log) { - if (q->expected_errors == 1) + if (command->expected_errors.count == 1) { /* Only log error if there is one possible error */ dynstr_append_mem(ds, "ERROR ", 6); @@ -4385,9 +4683,9 @@ static void handle_error(const char *query, struct st_query *q, dynstr_append_mem(ds,"\n",1); } /* Don't log error if we may not get an error */ - else if (q->expected_errno[0].type == ERR_SQLSTATE || - (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0)) + 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 */ @@ -4395,7 +4693,8 @@ static void handle_error(const char *query, struct st_query *q, } } - DBUG_PRINT("info",("i: %d expected_errors: %d", i, q->expected_errors)); + DBUG_PRINT("info",("i: %d expected_errors: %d", i, + command->expected_errors)); if (!disable_result_log) { @@ -4408,13 +4707,14 @@ static void handle_error(const char *query, struct st_query *q, if (i) { - if (q->expected_errno[0].type == ERR_ERRNO) + if (command->expected_errors.err[0].type == ERR_ERRNO) die("query '%s' failed with wrong errno %d: '%s', instead of %d...", - q->query, err_errno, err_error, q->expected_errno[0].code.errnum); + 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...", - q->query, err_sqlstate, err_error, - q->expected_errno[0].code.sqlstate); + command->query, err_sqlstate, err_error, + command->expected_errors.err[0].code.sqlstate); } DBUG_VOID_RETURN; @@ -4425,30 +4725,30 @@ static void handle_error(const char *query, struct st_query *q, Handle absence of errors after execution SYNOPSIS - handle_no_error() - q - context of query + handle_no_error() + q - context of query RETURN VALUE - error - function will not return + error - function will not return */ -static void handle_no_error(struct st_query *q) +void handle_no_error(struct st_command *command) { DBUG_ENTER("handle_no_error"); - if (q->expected_errno[0].type == ERR_ERRNO && - q->expected_errno[0].code.errnum != 0) + 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...", - q->query, q->expected_errno[0].code.errnum); + command->query, command->expected_errors.err[0].code.errnum); } - else if (q->expected_errno[0].type == ERR_SQLSTATE && - strcmp(q->expected_errno[0].code.sqlstate,"00000") != 0) + 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...", - q->query, q->expected_errno[0].code.sqlstate); + command->query, command->expected_errors.err[0].code.sqlstate); } DBUG_VOID_RETURN; @@ -4470,9 +4770,9 @@ static void handle_no_error(struct st_query *q) error - function will not return */ -static void run_query_stmt(MYSQL *mysql, struct st_query *command, - char *query, int query_len, DYNAMIC_STRING *ds, - DYNAMIC_STRING *ds_warnings) +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; @@ -4503,7 +4803,7 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, */ if (mysql_stmt_prepare(stmt, query, query_len)) { - handle_error(query, command, mysql_stmt_errno(stmt), + handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); goto end; } @@ -4520,6 +4820,7 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, parameter markers. */ +#if MYSQL_VERSION_ID >= 50000 if (cursor_protocol_enabled) { /* @@ -4530,13 +4831,14 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, 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(query, command, mysql_stmt_errno(stmt), + handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); goto end; } @@ -4550,8 +4852,8 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, /* 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 + mysql_stmt_store_result(), this is our only way to know how much + buffer to allocate for result data */ { my_bool one= 1; @@ -4566,7 +4868,7 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, */ if (mysql_stmt_store_result(stmt)) { - handle_error(query, command, mysql_stmt_errno(stmt), + handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); goto end; } @@ -4636,9 +4938,6 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command, } end: - free_replace(); - free_replace_regex(); - if (!disable_warnings) { dynstr_free(&ds_prepare_warnings); @@ -4650,7 +4949,7 @@ end: to the server into the mysqltest builtin variable $mysql_errno. This variable then can be used from the test case itself. */ - + var_set_errno(mysql_stmt_errno(stmt)); #ifndef BUG15518_FIXED mysql_stmt_close(stmt); @@ -4668,7 +4967,7 @@ end: as view, sp etc. */ -static int util_query(MYSQL* org_mysql, const char* query){ +int util_query(MYSQL* org_mysql, const char* query){ MYSQL* mysql; DBUG_ENTER("util_query"); @@ -4679,11 +4978,9 @@ static int util_query(MYSQL* org_mysql, const char* query){ if (!(mysql= mysql_init(mysql))) die("Failed in mysql_init()"); - if (safe_connect(mysql, org_mysql->host, org_mysql->user, - org_mysql->passwd, org_mysql->db, org_mysql->port, - org_mysql->unix_socket)) - die("Could not open util connection: %d %s", - mysql_errno(mysql), mysql_error(mysql)); + 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; } @@ -4697,8 +4994,8 @@ static int util_query(MYSQL* org_mysql, const char* query){ Run query flags control the phased/stages of query execution to be performed - if QUERY_SEND bit is on, the query will be sent. If QUERY_REAP is on - the result will be read - for regular query, both bits must be on + 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 SYNPOSIS run_query @@ -4707,7 +5004,7 @@ static int util_query(MYSQL* org_mysql, const char* query){ */ -static void run_query(MYSQL *mysql, struct st_query *command, int flags) +void run_query(MYSQL *mysql, struct st_command *command, int flags) { DYNAMIC_STRING *ds; DYNAMIC_STRING ds_result; @@ -4716,17 +5013,21 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) char *query; int query_len; my_bool view_created= 0, sp_created= 0; - my_bool complete_query= ((flags & QUERY_SEND) && (flags & QUERY_REAP)); + my_bool complete_query= ((flags & QUERY_SEND_FLAG) && + (flags & QUERY_REAP_FLAG)); init_dynamic_string(&ds_warnings, NULL, 0, 256); + /* Scan for warning before sendign 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, "", 16384, 65536); - do_eval(&eval_query, command->query, FALSE); + 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; } @@ -4737,23 +5038,23 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) } /* - When command->record_file is set the output of _this_ 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->record_file[0]) + */ + if (command->require_file[0]) { - init_dynamic_string(&ds_result, "", 16384, 65536); + init_dynamic_string(&ds_result, "", 1024, 1024); ds= &ds_result; } else ds= &ds_res; /* - Log the query into the output buffer + Log the query into the output buffer */ - if (!disable_query_log && (flags & QUERY_SEND)) + if (!disable_query_log && (flags & QUERY_SEND_FLAG)) { replace_dynstr_append_mem(ds, query, query_len); dynstr_append_mem(ds, delimiter, delimiter_length); @@ -4765,8 +5066,8 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) match_re(&view_re, query)) { /* - Create the query as a view. - Use replace since view can exist from a failed mysqltest run + 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, @@ -4778,7 +5079,7 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) /* 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))); @@ -4791,14 +5092,14 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) { /* 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 + 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); } @@ -4877,26 +5178,14 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) mysql_errno(mysql), mysql_error(mysql)); } - if (command->record_file[0]) + if (command->require_file[0]) { - /* A result file was specified for _this_ query */ - if (record) - { - /* - Recording in progress - Dump the output from _this_ query to the specified record_file - */ - str_to_file(command->record_file, ds->str, ds->length); - - } else { - - /* - The output from _this_ query should be checked against an already - existing file which has been specified using --require or --result - */ - check_result(ds, command->record_file, command->require_file); - } + /* 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); @@ -4906,12 +5195,12 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) dynstr_free(&eval_query); } +/****************************************************************************/ +/* + Functions to detect different SQL statements +*/ -/****************************************************************************\ - * Functions to detect different SQL statements -\****************************************************************************/ - -static char *re_eprint(int err) +char *re_eprint(int err) { static char epbuf[100]; size_t len= my_regerror(REG_ITOA|err, (my_regex_t *)NULL, @@ -4920,7 +5209,7 @@ static char *re_eprint(int err) return(epbuf); } -static void init_re_comp(my_regex_t *re, const char* str) +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); @@ -4933,11 +5222,11 @@ static void init_re_comp(my_regex_t *re, const char* str) } } -static void init_re(void) +void init_re(void) { /* - Filter for queries that can be run using the - MySQL Prepared Statements C API + Filter for queries that can be run using the + MySQL Prepared Statements C API */ const char *ps_re_str = "^(" @@ -4954,13 +5243,13 @@ static void init_re(void) "[[:space:]]*INSERT[[:space:]]+SELECT[[:space:]])"; /* - Filter for queries that can be run using the - Stored procedures + 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 + Filter for queries that can be run as views */ const char *view_re_str = "^(" @@ -4972,7 +5261,7 @@ static void init_re(void) } -static int match_re(my_regex_t *re, char *str) +int match_re(my_regex_t *re, char *str) { int err= my_regexec(re, str, (size_t)0, NULL, 0); @@ -4990,7 +5279,7 @@ static int match_re(my_regex_t *re, char *str) return 0; } -static void free_re(void) +void free_re(void) { my_regfree(&ps_re); my_regfree(&sp_re); @@ -5000,141 +5289,84 @@ static void free_re(void) /****************************************************************************/ -void get_query_type(struct st_query* q) +void get_command_type(struct st_command* command) { char save; uint type; - DBUG_ENTER("get_query_type"); + DBUG_ENTER("get_command_type"); - if (!parsing_disabled && *q->query == '}') + if (*command->query == '}') { - q->type = Q_END_BLOCK; + command->type = Q_END_BLOCK; DBUG_VOID_RETURN; } - if (q->type != Q_COMMENT_WITH_COMMAND) - q->type= parsing_disabled ? Q_COMMENT : Q_QUERY; - save=q->query[q->first_word_len]; - q->query[q->first_word_len]=0; - type=find_type(q->query, &command_typelib, 1+2); - q->query[q->first_word_len]=save; + 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) { - q->type=(enum enum_commands) type; /* Found command */ + command->type=(enum enum_commands) type; /* Found command */ + /* - If queries are disabled, only recognize - --enable_parsing and --disable_parsing + Look for case where "query" was explicitly specified to + force command being sent to server */ - if (parsing_disabled && q->type != Q_ENABLE_PARSING && - q->type != Q_DISABLE_PARSING) - q->type= Q_COMMENT; + if (type == Q_QUERY) + { + /* Skip the "query" part */ + command->query= command->first_argument; + } } - else if (q->type == Q_COMMENT_WITH_COMMAND && - q->first_word_len && - q->query[q->first_word_len-1] == ';') + else { - /* - Detect comment with command using extra delimiter - Ex --disable_query_log; - ^ Extra delimiter causing the command - to be skipped - */ - save= q->query[q->first_word_len-1]; - q->query[q->first_word_len-1]= 0; - type= find_type(q->query, &command_typelib, 1+2); - q->query[q->first_word_len-1]= save; - if (type > 0) - die("Extra delimiter \";\" found"); + /* 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; } -static byte *get_var_key(const byte* var, uint* len, - my_bool __attribute__((unused)) t) -{ - register char* key; - key = ((VAR*)var)->name; - *len = ((VAR*)var)->name_len; - return (byte*)key; -} - -static 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; -} - -static void var_free(void *v) -{ - my_free(((VAR*) v)->str_val, MYF(MY_WME)); - if (((VAR*)v)->alloced) - my_free((char*) v, MYF(MY_WME)); -} - - -static 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, (byte*)v); - return v; -} - - -static void init_var_hash(MYSQL *mysql) -{ - VAR *v; - DBUG_ENTER("init_var_hash"); - if (hash_init(&var_hash, charset_info, - 1024, 0, 0, get_var_key, var_free, MYF(0))) - die("Variable hash initialization failed"); - my_hash_insert(&var_hash, (byte*) var_init(0,"BIG_TEST", 0, - (opt_big_test) ? "1" : "0", 0)); - my_hash_insert(&var_hash, (byte*) var_init(0,"VALGRIND_TEST", 0, - (opt_valgrind_test) ? "1" : "0", - 0)); - v= var_init(0,"MAX_TABLES", 0, (sizeof(ulong) == 4) ? "31" : "62",0); - my_hash_insert(&var_hash, (byte*) v); - v= var_init(0,"SERVER_VERSION", 0, mysql_get_server_info(mysql), 0); - my_hash_insert(&var_hash, (byte*) v); v= var_init(0,"DB", 2, db, 0); - my_hash_insert(&var_hash, (byte*) v); - DBUG_VOID_RETURN; -} - /* Record how many milliseconds it took to execute the test file @@ -5144,7 +5376,9 @@ static void init_var_hash(MYSQL *mysql) test run completes */ -static void mark_progress(struct st_query* q __attribute__((unused)), int line) + +void mark_progress(struct st_command* command __attribute__((unused)), + int line) { char buf[32], *end; ulonglong timer= timer_now(); @@ -5178,47 +5412,60 @@ static void mark_progress(struct st_query* q __attribute__((unused)), int line) int main(int argc, char **argv) { - struct st_query *q; - my_bool require_file=0, q_send_flag=0, - query_executed= 0; + struct st_command *command; + my_bool q_send_flag= 0; + uint command_executed= 0, last_command_executed= 0; char save_file[FN_REFLEN]; MY_STAT res_info; MY_INIT(argv[0]); - /* Use all time until exit if no explicit 'start_timer' */ - timer_start= timer_now(); + save_file[0]= 0; + TMPDIR[0]= 0; - save_file[0]=0; - TMPDIR[0]=0; - - /* Init cons */ - memset(cons, 0, sizeof(cons)); - cons_end = cons + MAX_CONS; - next_con = cons + 1; - cur_con = cons; + /* Init connections */ + memset(connections, 0, sizeof(connections)); + connections_end= connections + + (sizeof(connections)/sizeof(struct st_connection)) - 1; + next_con= connections + 1; + cur_con= connections; /* Init file stack */ memset(file_stack, 0, sizeof(file_stack)); - file_stack_end= file_stack + MAX_INCLUDE_DEPTH - 1; + 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 + BLOCK_STACK_DEPTH - 1; + 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_query*), INIT_Q_LINES, - INIT_Q_LINES); + 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"); memset(&master_pos, 0, sizeof(master_pos)); - init_dynamic_string(&ds_res, "", 0, 65536); + parser.current_line= parser.read_lines= 0; + memset(&var_reg, 0, sizeof(var_reg)); + +#ifdef __WIN__ + 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); - DBUG_PRINT("info",("result_file: '%s'", result_file ? result_file : "")); + 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)) @@ -5246,29 +5493,30 @@ int main(int argc, char **argv) mysql_options(&cur_con->mysql, MYSQL_SET_CHARSET_NAME, charset_name); #ifdef HAVE_OPENSSL + +#if MYSQL_VERSION_ID >= 50000 opt_ssl_verify_server_cert= TRUE; /* Always on in mysqltest */ +#endif + 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 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"); - if (safe_connect(&cur_con->mysql, host, user, pass, db, port, unix_sock)) - die("Could not open connection '%s': %d %s", cur_con->name, - mysql_errno(&cur_con->mysql), mysql_error(&cur_con->mysql)); + safe_connect(&cur_con->mysql, cur_con->name, host, user, pass, + db, port, unix_sock); - init_var_hash(&cur_con->mysql); - -#ifdef __WIN__ - init_tmp_sh_file(); - init_win_path_patterns(); -#endif + /* Use all time until exit if no explicit 'start_timer' */ + timer_start= timer_now(); /* Initialize $mysql_errno with -1, so we can @@ -5282,26 +5530,35 @@ int main(int argc, char **argv) open_file(opt_include); } - while (!read_query(&q)) + while (!read_command(&command)) { int current_line_inc = 1, processed = 0; - if (q->type == Q_UNKNOWN || q->type == Q_COMMENT_WITH_COMMAND) - get_query_type(q); + 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) { - q->last_argument= q->first_argument; + command->last_argument= command->first_argument; processed = 1; - switch (q->type) { + switch (command->type) { case Q_CONNECT: - do_connect(q); + do_connect(command); break; - case Q_CONNECTION: select_connection(q); break; + case Q_CONNECTION: select_connection(command); break; case Q_DISCONNECT: case Q_DIRTY_CLOSE: - close_connection(q); break; - case Q_RPL_PROBE: do_rpl_probe(q); break; - case Q_ENABLE_RPL_PARSE: do_enable_rpl_parse(q); break; - case Q_DISABLE_RPL_PARSE: do_disable_rpl_parse(q); break; + 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; @@ -5316,18 +5573,21 @@ int main(int argc, char **argv) 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(q); break; - case Q_SLEEP: do_sleep(q, 0); break; - case Q_REAL_SLEEP: do_sleep(q, 1); break; - case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(q); break; - case Q_INC: do_modify_var(q, DO_INC); break; - case Q_DEC: do_modify_var(q, DO_DEC); break; - case Q_ECHO: do_echo(q); query_executed= 1; break; - case Q_SYSTEM: do_system(q); break; + case Q_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_FILE_EXIST: do_file_exist(command); break; + case Q_WRITE_FILE: do_write_file(command); break; + case Q_COPY_FILE: do_copy_file(command); break; + case Q_PERL: do_perl(command); break; case Q_DELIMITER: - strmake(delimiter, q->first_argument, sizeof(delimiter) - 1); - delimiter_length= strlen(delimiter); - q->last_argument= q->first_argument+delimiter_length; + do_delimiter(command); break; case Q_DISPLAY_VERTICAL_RESULTS: display_result_vertically= TRUE; @@ -5335,133 +5595,128 @@ int main(int argc, char **argv) case Q_DISPLAY_HORIZONTAL_RESULTS: display_result_vertically= FALSE; break; - case Q_LET: do_let(q); break; + case Q_LET: do_let(command); break; case Q_EVAL_RESULT: eval_result = 1; break; case Q_EVAL: - if (q->query == q->query_buf) + if (command->query == command->query_buf) { - q->query= q->first_argument; - q->first_word_len= 0; + command->query= command->first_argument; + command->first_word_len= 0; } /* fall through */ case Q_QUERY_VERTICAL: case Q_QUERY_HORIZONTAL: { my_bool old_display_result_vertically= display_result_vertically; - /* fix up query pointer if this is first iteration for this line */ - if (q->query == q->query_buf) - q->query += q->first_word_len + 1; - display_result_vertically= (q->type==Q_QUERY_VERTICAL); + + /* Remove "query_*" if this is first iteration */ + if (command->query == command->query_buf) + command->query= command->first_argument; + + display_result_vertically= (command->type == Q_QUERY_VERTICAL); if (save_file[0]) { - strmov(q->record_file,save_file); - q->require_file=require_file; - save_file[0]=0; + strmake(command->require_file, save_file, sizeof(save_file)); + save_file[0]= 0; } - run_query(&cur_con->mysql, q, QUERY_REAP|QUERY_SEND); + run_query(&cur_con->mysql, command, QUERY_REAP_FLAG|QUERY_SEND_FLAG); display_result_vertically= old_display_result_vertically; - q->last_argument= q->end; - query_executed= 1; + command->last_argument= command->end; + command_executed++; break; } case Q_QUERY: case Q_REAP: { - /* - We read the result always regardless of the mode for both full - query and read-result only (reap) - */ - int flags = QUERY_REAP; - if (q->type != Q_REAP) /* for a full query, enable the send stage */ - flags |= QUERY_SEND; - if (q_send_flag) - { - flags= QUERY_SEND; - q_send_flag=0; - } + int flags; + 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; + } + else + { + /* full query, both reap and send */ + flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG; + } + if (save_file[0]) { - strmov(q->record_file,save_file); - q->require_file=require_file; - save_file[0]=0; + strmake(command->require_file, save_file, sizeof(save_file)); + save_file[0]= 0; } - /* - To force something being sent as a query to the mysqld one can - use the prefix "query". Remove "query" from string before executing - */ - if (strncmp(q->query, "query ", 6) == 0) - { - q->query= q->first_argument; - } - run_query(&cur_con->mysql, q, flags); - query_executed= 1; - q->last_argument= q->end; + run_query(&cur_con->mysql, command, flags); + command_executed++; + command->last_argument= command->end; break; } case Q_SEND: - if (!q->query[q->first_word_len]) - { - /* This happens when we use 'send' on its own line */ - q_send_flag=1; - break; - } - /* fix up query pointer if this is first iteration for this line */ - if (q->query == q->query_buf) - q->query += q->first_word_len; + 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 without QUERY_REAP tells it to just send the - query and read the result some time later when reap instruction + 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->mysql, q, QUERY_SEND); - query_executed= 1; - q->last_argument= q->end; - break; - case Q_RESULT: - get_file_name(save_file,q); - require_file=0; - break; - case Q_ERROR: - global_expected_errors=get_errcodes(global_expected_errno,q); + */ + run_query(&cur_con->mysql, command, QUERY_SEND_FLAG); + command_executed++; + command->last_argument= command->end; break; case Q_REQUIRE: - get_file_name(save_file,q); - require_file=1; + do_get_file_name(command, save_file, sizeof(save_file)); + break; + case Q_ERROR: + do_get_errcodes(command); break; case Q_REPLACE: - get_replace(q); + do_get_replace(command); break; case Q_REPLACE_REGEX: - get_replace_regex(q); + do_get_replace_regex(command); break; - case Q_REPLACE_COLUMN: - get_replace_column(q); + 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(q); break; + case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break; case Q_SYNC_SLAVE_WITH_MASTER: { do_save_master_pos(); - if (*q->first_argument) - select_connection(q); + if (*command->first_argument) + select_connection(command); else select_connection_name("slave"); do_sync_with_master2(0); break; } case Q_COMMENT: /* Ignore row */ - case Q_COMMENT_WITH_COMMAND: - q->last_argument= q->end; + command->last_argument= command->end; break; case Q_PING: (void) mysql_ping(&cur_con->mysql); break; case Q_EXEC: - do_exec(q); - query_executed= 1; + do_exec(command); + command_executed++; break; case Q_START_TIMER: /* Overwrite possible earlier start of timer */ @@ -5470,10 +5725,9 @@ int main(int argc, char **argv) case Q_END_TIMER: /* End timer before ending mysqltest */ timer_output(); - got_end_timer= TRUE; break; case Q_CHARACTER_SET: - set_charset(q); + do_set_charset(command); break; case Q_DISABLE_PS_PROTOCOL: ps_protocol_enabled= 0; @@ -5482,31 +5736,33 @@ int main(int argc, char **argv) ps_protocol_enabled= ps_protocol; break; case Q_DISABLE_RECONNECT: - { - my_bool reconnect= 0; - mysql_options(&cur_con->mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); + set_reconnect(&cur_con->mysql, 0); break; - } case Q_ENABLE_RECONNECT: - { - my_bool reconnect= 1; - mysql_options(&cur_con->mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect); + set_reconnect(&cur_con->mysql, 1); break; - } case Q_DISABLE_PARSING: - parsing_disabled++; + 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 > 0) - parsing_disabled--; + if (parsing_disabled == 1) + parsing_disabled= 0; + else + die("Parsing is already enabled"); + break; + case Q_DIE: + die("%s", command->first_argument); break; - case Q_DIE: - die("%s", q->first_argument); + case Q_RESULT: + die("result, deprecated command"); break; default: @@ -5518,33 +5774,46 @@ int main(int argc, char **argv) if (!processed) { current_line_inc= 0; - switch (q->type) { - case Q_WHILE: do_block(cmd_while, q); break; - case Q_IF: do_block(cmd_if, q); break; - case Q_END_BLOCK: do_done(q); break; + 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(q->last_argument); + check_eol_junk(command->last_argument); - if (q->type != Q_ERROR) + if (command->type != Q_ERROR && + command->type != Q_COMMENT) { /* - As soon as any non "error" command has been executed, + As soon as any non "error" command or comment has been executed, the array with expected errors should be cleared */ - global_expected_errors= 0; - bzero((gptr) global_expected_errno, sizeof(global_expected_errno)); + 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(); + } + last_command_executed= command_executed; + parser.current_line += current_line_inc; if ( opt_mark_progress ) - mark_progress(q, parser.current_line); + 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. @@ -5552,25 +5821,27 @@ int main(int argc, char **argv) */ if (ds_res.length) { - if (result_file) + if (result_file_name) { + /* A result file has been specified */ + if (record) { - /* Dump the output from test to result file */ - str_to_file(result_file, ds_res.str, ds_res.length); + /* 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, result_file, 0); + */ + check_result(&ds_res); } } else { - /* No result_file specified to compare with, print to stdout */ + /* No result_file_name specified to compare with, print to stdout */ printf("%s", ds_res.str); } } @@ -5579,7 +5850,8 @@ int main(int argc, char **argv) die("The test didn't produce any output"); } - if (!query_executed && result_file && my_stat(result_file, &res_info, 0)) + 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 @@ -5591,14 +5863,16 @@ int main(int argc, char **argv) die("No queries executed but result file found!"); } - if ( opt_mark_progress ) - dump_progress(result_file); - dynstr_free(&ds_progress); + if ( opt_mark_progress && result_file_name ) + dump_progress(); + + /* Dump warning messages */ + if (result_file_name && ds_warning_messages.length) + dump_warning_messages(); dynstr_free(&ds_res); - if (!got_end_timer) - timer_output(); /* No end_timer cmd, end it */ + timer_output(); free_used_memory(); my_end(MY_CHECK_ERROR); @@ -5611,75 +5885,27 @@ int main(int argc, char **argv) /* - Read arguments for embedded server and put them into - embedded_server_args_count and embedded_server_args[] + 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. */ - -static int read_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)))) - return 1; - while (embedded_server_arg_count < MAX_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)); - return 1; - } - embedded_server_arg_count++; - } - my_fclose(file,MYF(0)); - if (str) - { - fprintf(stderr,"Too many arguments in option file: %s\n",name); - return 1; - } - return 0; -} - -/****************************************************************************\ - * - * 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. - * -\****************************************************************************/ - -static void timer_output(void) +void timer_output(void) { if (timer_file) { @@ -5687,25 +5913,160 @@ static void timer_output(void) 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; } } -static ulonglong timer_now(void) + +ulonglong timer_now(void) { return my_getsystime() / 10000; } -/**************************************************************************** -* Handle replacement of strings -****************************************************************************/ -#define PC_MALLOC 256 /* Bytes for pointers */ -#define PS_MALLOC 512 /* Bytes for data */ +/* + 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 */ + byte *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(my_string *from, my_string *to, uint count, + my_string word_end_chars); +int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string 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); + 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((char*) glob_replace,MYF(0)); + glob_replace=0; + } + DBUG_VOID_RETURN; +} -#define SPACE_CHAR 256 -#define START_OF_LINE 257 -#define END_OF_LINE 258 -#define LAST_CHAR_CODE 259 typedef struct st_replace { bool found; @@ -5719,99 +6080,514 @@ typedef struct st_replace_found { int from_offset; } REPLACE_STRING; + +void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, + const char *str, int len) +{ + 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,(gptr) ®)) + 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((gptr)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,(gptr)&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((char*) 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 *buf_end, *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 + + buf_end= buf + buf_len; + + 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) + 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= expr_p[1] - '0'; + } + + /* found a valid back_ref (eg. \1)*/ + if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) + { + int 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 += (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, 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) + { + int 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= 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((gptr)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 - -static int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string name) -{ - uint i,length,old_count; - byte *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(my_string)+sizeof(*pa->flag))* - (sizeof(my_string)+sizeof(*pa->flag))),MYF(MY_WME)))) - DBUG_RETURN(-1); - if (!(pa->str= (byte*) my_malloc((uint) (PS_MALLOC-MALLOC_OVERHEAD), - MYF(MY_WME)))) - { - my_free((gptr) pa->typelib.type_names,MYF(0)); - DBUG_RETURN (-1); - } - pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(byte*)+ - 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= (byte*) my_realloc((gptr) 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((gptr) pa->typelib.type_names, - (uint) len/ - (sizeof(byte*)+sizeof(*pa->flag))* - (sizeof(byte*)+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(byte*) + sizeof(*pa->flag)); - pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); - memcpy((byte*) pa->flag,(my_string) (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++]= pa->str+pa->length; - pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ - VOID(strmov(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((gptr) pa->typelib.type_names,MYF(0)); - pa->typelib.type_names=0; - my_free((gptr) pa->str,MYF(0)); - } -} /* free_pointer_array */ - - - /* Code for replace rutines */ - #define SET_MALLOC_HUNC 64 +#define LAST_CHAR_CODE 259 typedef struct st_rep_set { uint *bits; /* Pointer to used sets */ @@ -5843,32 +6619,48 @@ typedef struct st_follow { } FOLLOWS; -static int init_sets(REP_SETS *sets,uint states); -static REP_SET *make_new_set(REP_SETS *sets); -static void make_sets_invisible(REP_SETS *sets); -static void free_last_set(REP_SETS *sets); -static void free_sets(REP_SETS *sets); -static void internal_set_bit(REP_SET *set, uint bit); -static void internal_clear_bit(REP_SET *set, uint bit); -static void or_bits(REP_SET *to,REP_SET *from); -static void copy_bits(REP_SET *to,REP_SET *from); -static int cmp_bits(REP_SET *set1,REP_SET *set2); -static int get_next_bit(REP_SET *set,uint lastpos); -static int find_set(REP_SETS *sets,REP_SET *find); -static int find_found(FOUND_SET *found_set,uint table_offset, - int found_offset); -static uint start_at_word(my_string pos); -static uint end_of_word(my_string pos); -static uint replace_len(my_string pos); +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(my_string pos); +uint end_of_word(my_string pos); static uint found_sets=0; - /* Init a replace structure for further calls */ +uint replace_len(my_string str) +{ + uint len=0; + while (*str) + { + if (str[0] == '\\' && str[1]) + str++; + str++; + len++; + } + return len; +} + +/* Init a replace structure for further calls */ REPLACE *init_replace(my_string *from, my_string *to,uint count, my_string word_end_chars) { + static const int SPACE_CHAR= 256; + static const int START_OF_LINE= 257; + 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]; @@ -5921,7 +6713,7 @@ REPLACE *init_replace(my_string *from, my_string *to,uint count, DBUG_RETURN(0); } - /* Init follow_ptr[] */ + /* Init follow_ptr[] */ for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++) { if (from[i][0] == '\\' && from[i][1] == '^') @@ -6104,7 +6896,7 @@ REPLACE *init_replace(my_string *from, my_string *to,uint count, } } - /* Alloc replace structure for the replace-state-machine */ + /* Alloc replace structure for the replace-state-machine */ if ((replace=(REPLACE*) my_malloc(sizeof(REPLACE)*(sets.count)+ sizeof(REPLACE_STRING)*(found_sets+1)+ @@ -6147,7 +6939,7 @@ REPLACE *init_replace(my_string *from, my_string *to,uint count, } -static int init_sets(REP_SETS *sets,uint states) +int init_sets(REP_SETS *sets,uint states) { bzero((char*) sets,sizeof(*sets)); sets->size_of_bits=((states+7)/8); @@ -6163,16 +6955,16 @@ static int init_sets(REP_SETS *sets,uint states) return 0; } - /* Make help sets invisible for nicer codeing */ +/* Make help sets invisible for nicer codeing */ -static void make_sets_invisible(REP_SETS *sets) +void make_sets_invisible(REP_SETS *sets) { sets->invisible=sets->count; sets->set+=sets->count; sets->count=0; } -static REP_SET *make_new_set(REP_SETS *sets) +REP_SET *make_new_set(REP_SETS *sets) { uint i,count,*bit_buffer; REP_SET *set; @@ -6190,7 +6982,7 @@ static REP_SET *make_new_set(REP_SETS *sets) } count=sets->count+sets->invisible+SET_MALLOC_HUNC; if (!(set=(REP_SET*) my_realloc((gptr) sets->set_buffer, - sizeof(REP_SET)*count, + sizeof(REP_SET)*count, MYF(MY_WME)))) return 0; sets->set_buffer=set; @@ -6209,34 +7001,34 @@ static REP_SET *make_new_set(REP_SETS *sets) return make_new_set(sets); } -static void free_last_set(REP_SETS *sets) +void free_last_set(REP_SETS *sets) { sets->count--; sets->extra++; return; } -static void free_sets(REP_SETS *sets) +void free_sets(REP_SETS *sets) { my_free((gptr)sets->set_buffer,MYF(0)); my_free((gptr)sets->bit_buffer,MYF(0)); return; } -static void internal_set_bit(REP_SET *set, uint bit) +void internal_set_bit(REP_SET *set, uint bit) { set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT); return; } -static void internal_clear_bit(REP_SET *set, uint bit) +void internal_clear_bit(REP_SET *set, uint bit) { set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT)); return; } -static void or_bits(REP_SET *to,REP_SET *from) +void or_bits(REP_SET *to,REP_SET *from) { reg1 uint i; for (i=0 ; i < to->size_of_bits ; i++) @@ -6244,22 +7036,22 @@ static void or_bits(REP_SET *to,REP_SET *from) return; } -static void copy_bits(REP_SET *to,REP_SET *from) +void copy_bits(REP_SET *to,REP_SET *from) { memcpy((byte*) to->bits,(byte*) from->bits, (size_t) (sizeof(uint) * to->size_of_bits)); } -static int cmp_bits(REP_SET *set1,REP_SET *set2) +int cmp_bits(REP_SET *set1,REP_SET *set2) { return bcmp((byte*) set1->bits,(byte*) set2->bits, sizeof(uint) * set1->size_of_bits); } - /* Get next set bit from set. */ +/* Get next set bit from set. */ -static int get_next_bit(REP_SET *set,uint lastpos) +int get_next_bit(REP_SET *set,uint lastpos) { uint pos,*start,*end,bits; @@ -6280,11 +7072,11 @@ static int get_next_bit(REP_SET *set,uint lastpos) 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 */ +/* 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 */ -static int find_set(REP_SETS *sets,REP_SET *find) +int find_set(REP_SETS *sets,REP_SET *find) { uint i; for (i=0 ; i < sets->count-1 ; i++) @@ -6298,14 +7090,14 @@ static int find_set(REP_SETS *sets,REP_SET *find) 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. - */ +/* 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. +*/ -static int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) +int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) { int i; for (i=0 ; (uint) i < found_sets ; i++) @@ -6318,145 +7110,155 @@ static int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) return -i-2; /* return new postion */ } - /* Return 1 if regexp starts with \b or ends with \b*/ +/* Return 1 if regexp starts with \b or ends with \b*/ -static uint start_at_word(my_string pos) +uint start_at_word(my_string pos) { return (((!bcmp(pos,"\\b",2) && pos[2]) || !bcmp(pos,"\\^",2)) ? 1 : 0); } -static uint end_of_word(my_string pos) +uint end_of_word(my_string pos) { my_string end=strend(pos); return ((end > pos+2 && !bcmp(end-2,"\\b",2)) || (end >= pos+2 && !bcmp(end-2,"\\$",2))) ? - 1 : 0; + 1 : 0; } - -static uint replace_len(my_string str) -{ - uint len=0; - while (*str) - { - if (str[0] == '\\' && str[1]) - str++; - str++; - len++; - } - return len; -} - - -/* Replace strings while appending to ds */ -void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, - const char *str, int len) -{ - reg1 REPLACE *rep_pos; - reg2 REPLACE_STRING *rep_str; - 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; - } -} - - /**************************************************************************** - Replace results for a column -*****************************************************************************/ + * Handle replacement of strings + ****************************************************************************/ -static void free_replace_column() +#define PC_MALLOC 256 /* Bytes for pointers */ +#define PS_MALLOC 512 /* Bytes for data */ + +int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string name) { - uint i; - for (i=0 ; i < max_replace_column ; i++) + uint i,length,old_count; + byte *new_pos; + const char **new_array; + DBUG_ENTER("insert_pointer_name"); + + if (! pa->typelib.count) { - if (replace_column[i]) + if (!(pa->typelib.type_names=(const char **) + my_malloc(((PC_MALLOC-MALLOC_OVERHEAD)/ + (sizeof(my_string)+sizeof(*pa->flag))* + (sizeof(my_string)+sizeof(*pa->flag))),MYF(MY_WME)))) + DBUG_RETURN(-1); + if (!(pa->str= (byte*) my_malloc((uint) (PS_MALLOC-MALLOC_OVERHEAD), + MYF(MY_WME)))) { - my_free(replace_column[i], 0); - replace_column[i]= 0; + my_free((gptr) pa->typelib.type_names,MYF(0)); + DBUG_RETURN (-1); + } + pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(byte*)+ + 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= (byte*) my_realloc((gptr) 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((gptr) pa->typelib.type_names, + (uint) len/ + (sizeof(byte*)+sizeof(*pa->flag))* + (sizeof(byte*)+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(byte*) + sizeof(*pa->flag)); + pa->flag= (int7*) (pa->typelib.type_names+pa->max_count); + memcpy((byte*) pa->flag,(my_string) (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++]= pa->str+pa->length; + pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ + VOID(strmov(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((gptr) pa->typelib.type_names,MYF(0)); + pa->typelib.type_names=0; + my_free((gptr) 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); } } - max_replace_column= 0; -} -/* - 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. -*/ - -static void get_replace_column(struct st_query *q) -{ - char *from=q->first_argument; - char *buff,*start; - DBUG_ENTER("get_replace_columns"); - - free_replace_column(); - if (!*from) - die("Missing argument in %s", q->query); - - /* Allocate a buffer for results */ - start=buff=my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); - while (*from) + if (glob_replace) { - char *to; - uint column_number; - - to= get_string(&buff, &from, q); - if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS) - die("Wrong column number to replace_column in '%s'", q->query); - if (!*from) - die("Wrong number of arguments to replace_column in '%s'", q->query); - to= get_string(&buff, &from, q); - 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); + /* Normal replace */ + replace_strings_append(glob_replace, ds, val, len); } - my_free(start, MYF(0)); - q->last_argument= q->end; + 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); +} diff --git a/mysql-test/extra/rpl_tests/rpl_flsh_tbls.test b/mysql-test/extra/rpl_tests/rpl_flsh_tbls.test index 7a097fd1eae..ee6b0ed1426 100644 --- a/mysql-test/extra/rpl_tests/rpl_flsh_tbls.test +++ b/mysql-test/extra/rpl_tests/rpl_flsh_tbls.test @@ -7,6 +7,8 @@ -- source include/master-slave.inc +let $SERVER_VERSION=`select version()`; + create table t1 (a int); insert into t1 values (10); create table t2 (a int); diff --git a/mysql-test/extra/rpl_tests/rpl_truncate_helper.test b/mysql-test/extra/rpl_tests/rpl_truncate_helper.test index 1e485baca36..7f1506c4010 100644 --- a/mysql-test/extra/rpl_tests/rpl_truncate_helper.test +++ b/mysql-test/extra/rpl_tests/rpl_truncate_helper.test @@ -36,6 +36,7 @@ SELECT * FROM t1; --echo **** On Master **** connection master; DROP TABLE t1; +let $SERVER_VERSION=`select version()`; --replace_result $SERVER_VERSION SERVER_VERSION --replace_regex /\/\* xid=[0-9]+ \*\//\/* xid= *\// /table_id: [0-9]+/table_id: #/ SHOW BINLOG EVENTS; diff --git a/mysql-test/include/ctype_like_escape.inc b/mysql-test/include/ctype_like_escape.inc index ac97fbaa1a0..d4abc33c178 100644 --- a/mysql-test/include/ctype_like_escape.inc +++ b/mysql-test/include/ctype_like_escape.inc @@ -11,8 +11,8 @@ insert into t1 values('ab_def'); insert into t1 values('abc_ef'); insert into t1 values('abcd_f'); insert into t1 values('abcde_'); --- should return ab_def +# should return ab_def select c1 as c1u from t1 where c1 like 'ab\_def'; --- should return ab_def +# should return ab_def select c1 as c2h from t1 where c1 like 'ab#_def' escape '#'; drop table t1; diff --git a/mysql-test/include/mysqltest_while.inc b/mysql-test/include/mysqltest_while.inc deleted file mode 100644 index 90b05ee2695..00000000000 --- a/mysql-test/include/mysqltest_while.inc +++ /dev/null @@ -1,137 +0,0 @@ -let $1 = 10; -while ($1) -{ -while ($1) -{ -while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - while ($1) -{ - echo $1; - dec $1; -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} -} diff --git a/mysql-test/include/sourced.inc b/mysql-test/include/sourced.inc deleted file mode 100644 index be1a270641a..00000000000 --- a/mysql-test/include/sourced.inc +++ /dev/null @@ -1 +0,0 @@ -echo here is the sourced script; diff --git a/mysql-test/include/sourced1.inc b/mysql-test/include/sourced1.inc deleted file mode 100644 index 920561e5de2..00000000000 --- a/mysql-test/include/sourced1.inc +++ /dev/null @@ -1 +0,0 @@ ---source include/sourced.inc diff --git a/mysql-test/lib/mtr_cases.pl b/mysql-test/lib/mtr_cases.pl index bb92730444c..d73c7a83dca 100644 --- a/mysql-test/lib/mtr_cases.pl +++ b/mysql-test/lib/mtr_cases.pl @@ -164,31 +164,36 @@ sub collect_test_cases ($) { # Make a mapping of test name to a string that represents how that test # should be sorted among the other tests. Put the most important criterion # first, then a sub-criterion, then sub-sub-criterion, et c. - foreach $tinfo (@$cases) + foreach $tinfo (@$cases) { my @this_criteria = (); + # # Append the criteria for sorting, in order of importance. - push(@this_criteria, join("!", sort @{$tinfo->{'master_opt'}}) . "~"); # Ending with "~" makes empty sort later than filled + # + push(@this_criteria, "ndb=" . ($tinfo->{'ndb_test'} ? "1" : "0")); push(@this_criteria, "restart=" . ($tinfo->{'master_restart'} ? "1" : "0")); - push(@this_criteria, "big_test=" . ($tinfo->{'big_test'} ? "1" : "0")); - push(@this_criteria, join("|", sort keys %{$tinfo})); # Group similar things together. The values may differ substantially. FIXME? - push(@this_criteria, $tinfo->{'name'}); # Finally, order by the name - + # Group test with similar options together. + # Ending with "~" makes empty sort later than filled + push(@this_criteria, join("!", sort @{$tinfo->{'master_opt'}}) . "~"); + + # Finally, order by the name + push(@this_criteria, $tinfo->{'name'}); + $sort_criteria{$tinfo->{"name"}} = join(" ", @this_criteria); } @$cases = sort { $sort_criteria{$a->{"name"}} cmp $sort_criteria{$b->{"name"}}; } @$cases; -### For debugging the sort-order -# foreach $tinfo (@$cases) -# { -# print $sort_criteria{$tinfo->{"name"}}; -# print " -> \t"; -# print $tinfo->{"name"}; -# print "\n"; -# } + if ( $::opt_script_debug ) + { + # For debugging the sort-order + foreach $tinfo (@$cases) + { + print("$sort_criteria{$tinfo->{'name'}} -> \t$tinfo->{'name'}\n"); + } + } } return $cases; diff --git a/mysql-test/lib/mtr_io.pl b/mysql-test/lib/mtr_io.pl index e8dcb0262c9..a1d7ffe87d8 100644 --- a/mysql-test/lib/mtr_io.pl +++ b/mysql-test/lib/mtr_io.pl @@ -12,6 +12,7 @@ sub mtr_fromfile ($); sub mtr_tofile ($@); sub mtr_tonewfile($@); sub mtr_lastlinefromfile($); +sub mtr_appendfile_to_file ($$); ############################################################################## # @@ -170,4 +171,17 @@ sub mtr_tonewfile ($@) { close FILE; } +sub mtr_appendfile_to_file ($$) { + my $from_file= shift; + my $to_file= shift; + + open(TOFILE,">>",$to_file) or mtr_error("can't open file \"$to_file\": $!"); + open(FROMFILE,"<",$from_file) + or mtr_error("can't open file \"$from_file\": $!"); + print TOFILE while (); + close FROMFILE; + close TOFILE; +} + + 1; diff --git a/mysql-test/lib/mtr_process.pl b/mysql-test/lib/mtr_process.pl index 868b6d4f1ec..5e21248790e 100644 --- a/mysql-test/lib/mtr_process.pl +++ b/mysql-test/lib/mtr_process.pl @@ -4,7 +4,6 @@ # and is part of the translation of the Bourne shell script with the # same name. -#use Carp qw(cluck); use Socket; use Errno; use strict; @@ -93,8 +92,6 @@ sub spawn_impl ($$$$$$$$) { my $pid_file= shift; # FIXME my $spawn_opts= shift; - mtr_error("Can't spawn with empty \"path\"") unless defined $path; - if ( $::opt_script_debug ) { print STDERR "\n"; @@ -118,6 +115,9 @@ sub spawn_impl ($$$$$$$$) { print STDERR "#### ", "-" x 78, "\n"; } + mtr_error("Can't spawn with empty \"path\"") unless defined $path; + + FORK: { my $pid= fork(); @@ -344,8 +344,8 @@ sub mtr_kill_leftovers () { # Stop or kill Instance Manager and all its children. If we failed to do # that, we can only abort -- there is nothing left to do. - mtr_error("Failed to stop Instance Manager.") - unless mtr_im_stop($::instance_manager); +# mtr_error("Failed to stop Instance Manager.") +# unless mtr_im_stop($::instance_manager); # Start shutdown of masters and slaves. Don't touch IM-managed mysqld # instances -- they should be stopped by mtr_im_stop(). @@ -377,40 +377,41 @@ sub mtr_kill_leftovers () { $srv->{'pid'}= 0; # Assume we are done with it } - # Start shutdown of clusters. - - mtr_debug("Shutting down cluster..."); - - foreach my $cluster (@{$::clusters}) + if ( ! $::opt_skip_ndbcluster ) { - mtr_debug(" - cluster " . - "(pid: $cluster->{pid}; " . - "pid file: '$cluster->{path_pid})"); + # Start shutdown of clusters. + mtr_debug("Shutting down cluster..."); - my $pid= mtr_ndbmgm_start($cluster, "shutdown"); - - # Save the pid of the ndb_mgm process - $admin_pids{$pid}= 1; - - push(@kill_pids,{ - pid => $cluster->{'pid'}, - pidfile => $cluster->{'path_pid'} - }); - - $cluster->{'pid'}= 0; # Assume we are done with it - - - foreach my $ndbd (@{$cluster->{'ndbds'}}) + foreach my $cluster (@{$::clusters}) { - mtr_debug(" - ndbd " . - "(pid: $ndbd->{pid}; " . - "pid file: '$ndbd->{path_pid})"); + mtr_debug(" - cluster " . + "(pid: $cluster->{pid}; " . + "pid file: '$cluster->{path_pid})"); + + my $pid= mtr_ndbmgm_start($cluster, "shutdown"); + + # Save the pid of the ndb_mgm process + $admin_pids{$pid}= 1; push(@kill_pids,{ - pid => $ndbd->{'pid'}, - pidfile => $ndbd->{'path_pid'}, + pid => $cluster->{'pid'}, + pidfile => $cluster->{'path_pid'} }); - $ndbd->{'pid'}= 0; # Assume we are done with it + + $cluster->{'pid'}= 0; # Assume we are done with it + + foreach my $ndbd (@{$cluster->{'ndbds'}}) + { + mtr_debug(" - ndbd " . + "(pid: $ndbd->{pid}; " . + "pid file: '$ndbd->{path_pid})"); + + push(@kill_pids,{ + pid => $ndbd->{'pid'}, + pidfile => $ndbd->{'path_pid'}, + }); + $ndbd->{'pid'}= 0; # Assume we are done with it + } } } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index e5ed8a99304..31a2c1e32a3 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -1,30 +1,24 @@ #!/usr/bin/perl # -*- cperl -*- -# This is a transformation of the "mysql-test-run" Bourne shell script -# to Perl. There are reasons this rewrite is not the prettiest Perl -# you have seen # -# - The original script is huge and for most part uncommented, -# not even a usage description of the flags. +############################################################################## # -# - There has been an attempt to write a replacement in C for the -# original Bourne shell script. It was kind of working but lacked -# lot of functionality to really be a replacement. Not to redo -# that mistake and catch all the obscure features of the original -# script, the rewrite in Perl is more close to the original script -# meaning it also share some of the ugly parts as well. +# mysql-test-run.pl # -# - The original intention was that this script was to be a prototype -# to be the base for a new C version with full functionality. Since -# then it was decided that the Perl version should replace the -# Bourne shell version, but the Perl style still reflects the wish -# to make the Perl to C step easy. +# Tool used for executing a suite of .test file # -# Some coding style from the original intent has been kept +# 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! +# +############################################################################## +# +# Coding style directions for this perl script # # - To make this Perl script easy to alter even for those that not -# code Perl that often, the coding style is as close as possible to +# code Perl that often, keeep the coding style as close as possible to # the C/C++ MySQL coding standard. # # - All lists of arguments to send to commands are Perl lists/arrays, @@ -42,15 +36,6 @@ # the information. This separates the "find information" from the # "do the work" and makes the program more easy to maintain. # -# - At the moment, there are tons of "global" variables that control -# this script, even accessed from the files in "lib/*.pl". This -# will change over time, for now global variables are used instead -# of using %opt, %path and %exe hashes, because I want more -# compile time checking, that hashes would not give me. Once this -# script is debugged, hashes will be used and passed as parameters -# to functions, to more closely mimic how it would be coded in C -# using structs. -# # - The rule when it comes to the logic of this program is # # command_line_setup() - is to handle the logic between flags @@ -66,10 +51,6 @@ # "http://www.plover.com/~mjd/perl/Trace/" and run this script like # "perl -d:Trace mysql-test-run.pl" # -# FIXME Save a PID file from this code as well, to record the process -# id we think it has. In Cygwin, a fork creates one Cygwin process, -# and then the real Win32 process. Cygwin Perl can only kill Cygwin -# processes. And "mysqld --bootstrap ..." doesn't save a PID file. $Devel::Trace::TRACE= 0; # Don't trace boring init stuff @@ -80,13 +61,17 @@ use File::Copy; use Cwd; use Getopt::Long; use Sys::Hostname; -#use Carp; use IO::Socket; use IO::Socket::INET; use Data::Dumper; use strict; #use diagnostics; +our $glob_win32_perl= ($^O eq "MSWin32"); # ActiveState Win32 Perl +our $glob_cygwin_perl= ($^O eq "cygwin"); # Cygwin Perl +our $glob_win32= ($glob_win32_perl or $glob_cygwin_perl); +our $glob_netware= ($^O eq "NetWare"); # NetWare + require "lib/mtr_cases.pl"; require "lib/mtr_im.pl"; require "lib/mtr_process.pl"; @@ -130,10 +115,7 @@ our @mysqld_src_dirs= # structs. We let each struct be a separate hash. # Misc global variables - -our $glob_win32= 0; # OS and native Win32 executables -our $glob_win32_perl= 0; # ActiveState Win32 Perl -our $glob_cygwin_perl= 0; # Cygwin Perl +our $mysql_version_id; our $glob_mysql_test_dir= undef; our $glob_mysql_bench_dir= undef; our $glob_hostname= undef; @@ -147,42 +129,36 @@ our @glob_test_mode; our $glob_basedir; -# The total result - our $path_charsetsdir; our $path_client_bindir; our $path_language; our $path_timefile; our $path_snapshot; -our $path_slave_load_tmpdir; # What is this?! our $path_mysqltest_log; our $path_current_test_log; our $path_my_basedir; + our $opt_vardir; # A path but set directly on cmd line -our $opt_vardir_trace; # unix formatted opt_vardir for trace files +our $path_vardir_trace; # unix formatted opt_vardir for trace files our $opt_tmpdir; # A path but set directly on cmd line our $opt_usage; our $opt_suite; -our $opt_netware; - our $opt_script_debug= 0; # Script debugging, enable with --script-debug our $opt_verbose= 0; # Verbose output, enable with --verbose -# Options FIXME not all.... - our $exe_master_mysqld; our $exe_mysql; our $exe_mysqladmin; our $exe_mysqlbinlog; our $exe_mysql_client_test; our $exe_mysqld; -our $exe_mysqlcheck; # Called from test case -our $exe_mysqldump; # Called from test case -our $exe_mysqlslap; # Called from test case -our $exe_mysqlimport; # Called from test case -our $exe_mysqlshow; # Called from test case +our $exe_mysqlcheck; +our $exe_mysqldump; +our $exe_mysqlslap; +our $exe_mysqlimport; +our $exe_mysqlshow; our $exe_mysql_fix_system_tables; our $exe_mysqltest; our $exe_ndbd; @@ -218,6 +194,7 @@ our $opt_fast; our $opt_force; our $opt_reorder= 0; our $opt_enable_disabled; +our $opt_mem; our $opt_gcov; our $opt_gcov_err; @@ -239,7 +216,7 @@ our $opt_gprof_dir; our $opt_gprof_master; our $opt_gprof_slave; -our $master; # Will be struct in C +our $master; our $slave; our $clusters; @@ -326,7 +303,7 @@ our $exe_ndb_waiter; our $path_ndb_tools_dir; our $path_ndb_examples_dir; our $exe_ndb_example; -our $file_ndb_testrun_log; +our $path_ndb_testrun_log; our @data_dir_lst; @@ -334,6 +311,9 @@ our $used_binlog_format; our $debug_compiled_binaries; our $glob_tot_real_time= 0; +# Default values read from mysqld +our $default_mysqld_port; + ###################################################################### # # Function declarations @@ -343,14 +323,14 @@ our $glob_tot_real_time= 0; sub main (); sub initial_setup (); sub command_line_setup (); -sub snapshot_setup (); +sub datadir_setup (); sub executable_setup (); sub environment_setup (); sub kill_running_server (); sub cleanup_stale_files (); -sub check_ssl_support (); +sub check_ssl_support ($); sub check_running_as_root(); -sub check_ndbcluster_support (); +sub check_ndbcluster_support ($); sub rm_ndbcluster_tables ($); sub ndbcluster_start_install ($); sub ndbcluster_start ($$); @@ -366,8 +346,8 @@ sub run_testcase_stop_servers ($$$); sub run_testcase_start_servers ($); sub run_testcase_check_skip_test($); sub report_failure_and_restart ($); -sub do_before_start_master ($$); -sub do_before_start_slave ($$); +sub do_before_start_master ($); +sub do_before_start_slave ($); sub ndbd_start ($$$); sub ndb_mgmd_start ($); sub mysqld_start ($$$); @@ -389,11 +369,6 @@ sub main () { initial_setup(); command_line_setup(); executable_setup(); - - check_ndbcluster_support(); - check_ssl_support(); - check_debug_support(); - environment_setup(); signal_setup(); @@ -441,7 +416,6 @@ sub main () { unless $need_ndbcluster; $opt_skip_im= 1 unless $need_im; - snapshot_setup(); initialize_servers(); run_suite($opt_suite, $tests); @@ -463,13 +437,8 @@ sub initial_setup () { $glob_scriptname= basename($0); - $glob_win32_perl= ($^O eq "MSWin32"); - $glob_cygwin_perl= ($^O eq "cygwin"); - $glob_win32= ($glob_win32_perl or $glob_cygwin_perl); - # We require that we are in the "mysql-test" directory # to run mysql-test-run - if (! -f $glob_scriptname) { mtr_error("Can't find the location for the mysql-test-run script\n" . @@ -493,13 +462,11 @@ sub initial_setup () { chomp($glob_mysql_test_dir); } $glob_basedir= dirname($glob_mysql_test_dir); + # Expect mysql-bench to be located adjacent to the source tree, by default $glob_mysql_bench_dir= "$glob_basedir/../mysql-bench" unless defined $glob_mysql_bench_dir; - # needs to be same length to test logging (FIXME what???) - $path_slave_load_tmpdir= "../../var/tmp"; - $path_my_basedir= $opt_source_dist ? $glob_mysql_test_dir : $glob_basedir; @@ -661,12 +628,12 @@ sub command_line_setup () { 'tmpdir=s' => \$opt_tmpdir, 'vardir=s' => \$opt_vardir, 'benchdir=s' => \$glob_mysql_bench_dir, + 'mem' => \$opt_mem, # Misc 'comment=s' => \$opt_comment, 'debug' => \$opt_debug, 'fast' => \$opt_fast, - 'netware' => \$opt_netware, 'reorder' => \$opt_reorder, 'enable-disabled' => \$opt_enable_disabled, 'script-debug' => \$opt_script_debug, @@ -734,6 +701,32 @@ sub command_line_setup () { } mtr_report("Using binlog format '$used_binlog_format'"); + # -------------------------------------------------------------------------- + # Check if we should speed up tests by trying to run on tmpfs + # -------------------------------------------------------------------------- + if ( $opt_mem ) + { + mtr_error("Can't use --mem and --vardir at the same time ") + if $opt_vardir; + mtr_error("Can't use --mem and --tmpdir at the same time ") + if $opt_tmpdir; + + # Use /dev/shm as the preferred location for vardir and + # thus implicitly also tmpdir. Add other locations to list + my @tmpfs_locations= ("/dev/shm"); + # One could maybe use "mount" to find tmpfs location(s) + foreach my $fs (@tmpfs_locations) + { + if ( -d $fs ) + { + mtr_report("Using tmpfs in $fs"); + $opt_mem= "$fs/var"; + $opt_mem .= $ENV{'MTR_BUILD_THREAD'} if $ENV{'MTR_BUILD_THREAD'}; + last; + } + } + } + # -------------------------------------------------------------------------- # Set the "var/" directory, as it is the base for everything else # -------------------------------------------------------------------------- @@ -742,9 +735,18 @@ sub command_line_setup () { { $opt_vardir= "$glob_mysql_test_dir/var"; } - $opt_vardir_trace= $opt_vardir; + elsif ( $mysql_version_id < 50000 ) + { + # --vardir was specified + # It's only supported in 4.1 as a symlink from var/ + # by setting up $opt_mem that will be created + $opt_mem= $opt_vardir; + $opt_vardir= undef; + } + + $path_vardir_trace= $opt_vardir; # Chop off any "c:", DBUG likes a unix path ex: c:/src/... => /src/... - $opt_vardir_trace=~ s/^\w://; + $path_vardir_trace=~ s/^\w://; # We make the path absolute, as the server will do a chdir() before usage unless ( $opt_vardir =~ m,^/, or @@ -765,12 +767,6 @@ sub command_line_setup () { # Do sanity checks of command line arguments # -------------------------------------------------------------------------- - if ( ! $opt_socket ) - { # FIXME set default before reading options? -# $opt_socket= '@MYSQL_UNIX_ADDR@'; - $opt_socket= "/tmp/mysql.sock"; # FIXME - } - # -------------------------------------------------------------------------- # Look at the command line options and set script flags # -------------------------------------------------------------------------- @@ -1120,11 +1116,12 @@ sub command_line_setup () { $path_timefile= "$opt_vardir/log/mysqltest-time"; $path_mysqltest_log= "$opt_vardir/log/mysqltest.log"; $path_current_test_log= "$opt_vardir/log/current_test"; + $path_ndb_testrun_log= "$opt_vardir/log/ndb_testrun.log"; $path_snapshot= "$opt_tmpdir/snapshot_$opt_master_myport/"; } -sub snapshot_setup () { +sub datadir_setup () { # Make a list of all data_dirs @data_dir_lst = ( @@ -1152,6 +1149,107 @@ sub snapshot_setup () { # ############################################################################## + +sub check_mysqld_features () { + # + # Execute "mysqld --no-defaults --help --verbose", that will + # print out version and a list of all features and settings + # + my $found_variable_list_start= 0; + my $spec_file= "$glob_mysql_test_dir/mysqld.spec.$$"; + if ( mtr_run($exe_mysqld, + ["--no-defaults", + "--verbose", + "--help"], + "", "$spec_file", "$spec_file", "") != 0 ) + { + mtr_error("Failed to get version and list of features from %s", + $exe_mysqld); + } + + my %mysqld_variables; + my $F= IO::File->new($spec_file) or + mtr_error("can't open file \"$spec_file\": $!"); + + while ( my $line= <$F> ) + { + # First look for version + if ( !$mysql_version_id ) + { + # Look for version + if ( $line =~ /^$exe_mysqld\s\sVer\s([0-9]*)\.([0-9]*)\.([0-9]*)/ ) + { + #print "Major: $1 Minor: $2 Build: $3\n"; + $mysql_version_id= $1*10000 + $2*100 + $3; + #print "mysql_version_id: $mysql_version_id\n"; + mtr_report("MySQL Version $1.$2.$3"); + } + } + else + { + if (!$found_variable_list_start) + { + # Look for start of variables list + if ( $line =~ /[\-]+\s[\-]+/ ) + { + $found_variable_list_start= 1; + } + } + else + { + # Put variables into hash + if ( $line =~ /^([\S]+)[ \t]+(.*)$/ ) + { + # print "$1=$2\n"; + $mysqld_variables{$1}= $2; + } + else + { + # The variable list is ended with a blank line + if ( $line =~ /^[\s]*$/ ) + { + last; + } + else + { + # Send out a warning, we should fix the variables that has no + # space between variable name and it's value + # or should it be fixed width column parsing? It does not + # look like that in function my_print_variables in my_getopt.c + mtr_warning("Could not parse variable list line : $line"); + } + } + } + } + } + unlink($spec_file); + mtr_error("Could not find version of MySQL") unless $mysql_version_id; + mtr_error("Could not find variabes list") unless $found_variable_list_start; + + check_ndbcluster_support(\%mysqld_variables); + check_ssl_support(\%mysqld_variables); + check_debug_support(\%mysqld_variables); + + if ( $mysql_version_id < 50000 ) + { + # Instance manager is not supported until 5.0 + $opt_skip_im= 1; + + } + + if ( $mysql_version_id < 50100 ) + { + # Slave cluster is not supported until 5.1 + $opt_skip_ndbcluster_slave= 1; + + } + + # Set default values from mysqld_variables + $opt_socket= %mysqld_variables->{'socket'}; + $default_mysqld_port = %mysqld_variables->{'port'}; +} + + sub executable_setup () { # @@ -1169,161 +1267,135 @@ sub executable_setup () { } } - if ( $opt_source_dist ) - { - if ( $glob_win32 ) - { - $path_client_bindir= mtr_path_exists("$glob_basedir/client_release", - "$glob_basedir/client_debug", - "$glob_basedir/bin", - # New CMake locations. - "$glob_basedir/client/release", - "$glob_basedir/client/debug"); - $exe_mysqld= mtr_exe_exists ("$path_client_bindir/mysqld-max-nt", - "$path_client_bindir/mysqld-max", - "$path_client_bindir/mysqld-nt", - "$path_client_bindir/mysqld", - "$path_client_bindir/mysqld-max", - "$path_client_bindir/mysqld-debug", - "$glob_basedir/sql/release/mysqld", - "$glob_basedir/sql/debug/mysqld"); - $path_language= mtr_path_exists("$glob_basedir/share/english/", - "$glob_basedir/sql/share/english/"); - $path_charsetsdir= mtr_path_exists("$glob_basedir/share/charsets", - "$glob_basedir/sql/share/charsets"); - $exe_my_print_defaults= - mtr_exe_exists("$path_client_bindir/my_print_defaults", - "$glob_basedir/extra/release/my_print_defaults", - "$glob_basedir/extra/debug/my_print_defaults"); - $exe_perror= - mtr_exe_exists("$path_client_bindir/perror", - "$glob_basedir/extra/release/perror", - "$glob_basedir/extra/debug/perror"); - } - else - { - $path_client_bindir= mtr_path_exists("$glob_basedir/client"); - $exe_mysqld= mtr_exe_exists ("$glob_basedir/sql/mysqld"); - $exe_mysqlslap= mtr_exe_exists ("$path_client_bindir/mysqlslap"); - $path_language= mtr_path_exists("$glob_basedir/sql/share/english/"); - $path_charsetsdir= mtr_path_exists("$glob_basedir/sql/share/charsets"); + # Look for the path where to find the client binaries + $path_client_bindir= mtr_path_exists("$glob_basedir/client", + "$glob_basedir/client_release", + "$glob_basedir/client_debug", + "$glob_basedir/client/release", + "$glob_basedir/client/debug", + "$glob_basedir/bin"); - $exe_im= mtr_exe_exists( - "$glob_basedir/server-tools/instance-manager/mysqlmanager"); - $exe_my_print_defaults= - mtr_exe_exists("$glob_basedir/extra/my_print_defaults"); - $exe_perror= - mtr_exe_exists("$glob_basedir/extra/perror"); - } - - if ( $glob_use_embedded_server ) - { - my $path_examples= "$glob_basedir/libmysqld/examples"; - $exe_mysqltest= mtr_exe_exists("$path_examples/mysqltest_embedded"); - $exe_mysql_client_test= - mtr_exe_exists("$path_examples/mysql_client_test_embedded", - "/usr/bin/false"); - } - else - { - $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mysqltest"); - $exe_mysql_client_test= - mtr_exe_exists("$glob_basedir/tests/mysql_client_test", - "$glob_basedir/tests/release/mysql_client_test", - "$glob_basedir/tests/debug/mysql_client_test", - "$path_client_bindir/mysql_client_test", - "/usr/bin/false"); - } - $exe_mysqlcheck= mtr_exe_exists("$path_client_bindir/mysqlcheck"); - $exe_mysqldump= mtr_exe_exists("$path_client_bindir/mysqldump"); - $exe_mysqlimport= mtr_exe_exists("$path_client_bindir/mysqlimport"); - $exe_mysqlshow= mtr_exe_exists("$path_client_bindir/mysqlshow"); - $exe_mysqlbinlog= mtr_exe_exists("$path_client_bindir/mysqlbinlog"); - $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mysqladmin"); - $exe_mysql= mtr_exe_exists("$path_client_bindir/mysql"); - $exe_mysql_fix_system_tables= - mtr_script_exists("$glob_basedir/scripts/mysql_fix_privilege_tables", - "/usr/bin/false"); - $path_ndb_tools_dir= mtr_path_exists("$glob_basedir/storage/ndb/tools"); - $path_ndb_examples_dir= mtr_path_exists("$glob_basedir/storage/ndb/ndbapi-examples"); - $exe_ndb_example= mtr_file_exists("$path_ndb_examples_dir/ndbapi_simple/ndbapi_simple"); - $exe_ndb_mgm= "$glob_basedir/storage/ndb/src/mgmclient/ndb_mgm"; - $exe_ndb_waiter= "$glob_basedir/storage/ndb/tools/ndb_waiter"; - $exe_ndbd= "$glob_basedir/storage/ndb/src/kernel/ndbd"; - $exe_ndb_mgmd= "$glob_basedir/storage/ndb/src/mgmsrv/ndb_mgmd"; - $lib_udf_example= - mtr_file_exists("$glob_basedir/sql/.libs/udf_example.so", - "$glob_basedir/sql/release/udf_example.dll", - "$glob_basedir/sql/debug/udf_example.dll"); - } - else - { - $path_client_bindir= mtr_path_exists("$glob_basedir/bin"); - $exe_mysqlcheck= mtr_exe_exists("$path_client_bindir/mysqlcheck"); - $exe_mysqldump= mtr_exe_exists("$path_client_bindir/mysqldump"); - $exe_mysqlimport= mtr_exe_exists("$path_client_bindir/mysqlimport"); - $exe_mysqlshow= mtr_exe_exists("$path_client_bindir/mysqlshow"); - $exe_mysqlbinlog= mtr_exe_exists("$path_client_bindir/mysqlbinlog"); - $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mysqladmin"); - $exe_mysql= mtr_exe_exists("$path_client_bindir/mysql"); - $exe_mysql_fix_system_tables= - mtr_script_exists("$path_client_bindir/mysql_fix_privilege_tables", - "$glob_basedir/scripts/mysql_fix_privilege_tables", - "/usr/bin/false"); - $exe_my_print_defaults= - mtr_exe_exists("$path_client_bindir/my_print_defaults"); - $exe_perror= - mtr_exe_exists("$path_client_bindir/perror"); - - $path_language= mtr_path_exists("$glob_basedir/share/mysql/english/", - "$glob_basedir/share/english/"); - $path_charsetsdir= mtr_path_exists("$glob_basedir/share/mysql/charsets", - "$glob_basedir/share/charsets"); - - if ( $glob_win32 ) - { - $exe_mysqld= mtr_exe_exists ("$glob_basedir/bin/mysqld-nt", - "$glob_basedir/bin/mysqld", - "$glob_basedir/bin/mysqld-debug",); - } - else - { - $exe_mysqld= mtr_exe_exists ("$glob_basedir/libexec/mysqld", - "$glob_basedir/bin/mysqld"); - $exe_mysqlslap= mtr_exe_exists("$path_client_bindir/mysqlslap"); - } - $exe_im= mtr_exe_exists("$glob_basedir/libexec/mysqlmanager", - "$glob_basedir/bin/mysqlmanager"); - if ( $glob_use_embedded_server ) - { - $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mysqltest_embedded"); - $exe_mysql_client_test= - mtr_exe_exists("$glob_basedir/tests/mysql_client_test_embedded", - "$path_client_bindir/mysql_client_test_embedded", - "/usr/bin/false"); - } - else - { - $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mysqltest"); - $exe_mysql_client_test= - mtr_exe_exists("$path_client_bindir/mysql_client_test", - "$glob_basedir/tests/release/mysql_client_test", - "$glob_basedir/tests/debug/mysql_client_test", - "/usr/bin/false"); # FIXME temporary - } - - $path_ndb_tools_dir= "$glob_basedir/bin"; - $path_ndb_examples_dir= "$glob_basedir/ndbapi-examples"; - $exe_ndb_mgm= "$glob_basedir/bin/ndb_mgm"; - $exe_ndb_waiter= "$glob_basedir/bin/ndb_waiter"; - $exe_ndbd= "$glob_basedir/bin/ndbd"; - $exe_ndb_mgmd= "$glob_basedir/bin/ndb_mgmd"; - } + # Look for the mysqld executable + $exe_mysqld= mtr_exe_exists ("$glob_basedir/sql/mysqld", + "$path_client_bindir/mysqld-max-nt", + "$path_client_bindir/mysqld-max", + "$path_client_bindir/mysqld-nt", + "$path_client_bindir/mysqld", + "$path_client_bindir/mysqld-debug", + "$path_client_bindir/mysqld-max", + "$glob_basedir/libexec/mysqld", + "$glob_basedir/sql/release/mysqld", + "$glob_basedir/sql/debug/mysqld"); $exe_master_mysqld= $exe_master_mysqld || $exe_mysqld; $exe_slave_mysqld= $exe_slave_mysqld || $exe_mysqld; - $file_ndb_testrun_log= "$opt_vardir/log/ndb_testrun.log"; + # Use the mysqld found above to find out what features are available + check_mysqld_features(); + + # Look for language files and charsetsdir, use same share + my $path_share= mtr_path_exists("$glob_basedir/share", + "$glob_basedir/sql/share", + "$glob_basedir/share/mysql", + "$glob_basedir/share"); + + $path_language= mtr_path_exists("$path_share/english"); + $path_charsetsdir= mtr_path_exists("$path_share/charsets"); + + # Look for my_print_defaults + $exe_my_print_defaults= + mtr_exe_exists("$path_client_bindir/my_print_defaults", + "$glob_basedir/extra/my_print_defaults", + "$glob_basedir/extra/release/my_print_defaults", + "$glob_basedir/extra/debug/my_print_defaults"); + + # Look for perror + $exe_perror= mtr_exe_exists("$glob_basedir/extra/perror", + "$path_client_bindir/perror", + "$glob_basedir/extra/release/perror", + "$glob_basedir/extra/debug/perror"); + + + if ( ! $opt_skip_im ) + { + # Look for instance manager binary - mysqlmanager + $exe_im= + mtr_exe_exists( + "$glob_basedir/server-tools/instance-manager/mysqlmanager", + "$glob_basedir/libexec/mysqlmanager"); + } + + # Look for the client binaries + $exe_mysqlcheck= mtr_exe_exists("$path_client_bindir/mysqlcheck"); + $exe_mysqldump= mtr_exe_exists("$path_client_bindir/mysqldump"); + $exe_mysqlimport= mtr_exe_exists("$path_client_bindir/mysqlimport"); + $exe_mysqlshow= mtr_exe_exists("$path_client_bindir/mysqlshow"); + $exe_mysqlbinlog= mtr_exe_exists("$path_client_bindir/mysqlbinlog"); + $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mysqladmin"); + $exe_mysql= mtr_exe_exists("$path_client_bindir/mysql"); + if ( $mysql_version_id >= 50100 ) + { + $exe_mysqlslap= mtr_exe_exists("$path_client_bindir/mysqlslap"); + } + + # Look for mysql_fix_system_table script + $exe_mysql_fix_system_tables= + mtr_script_exists("$glob_basedir/scripts/mysql_fix_privilege_tables", + "$path_client_bindir/mysql_fix_privilege_tables"); + + if ( ! $opt_skip_ndbcluster) + { + # Look for ndb tols and binaries + my $ndb_path= mtr_path_exists("$glob_basedir/ndb", + "$glob_basedir/storage/ndb"); + + $path_ndb_tools_dir= mtr_path_exists("$ndb_path/tools", + "$glob_basedir/bin"); + $exe_ndb_mgm= + mtr_exe_exists("$ndb_path/src/mgmclient/ndb_mgm", + "$glob_basedir/bin/ndb_mgm"); + $exe_ndb_mgmd= + mtr_exe_exists("$ndb_path/src/mgmsrv/ndb_mgmd", + "$glob_basedir/bin/ndb_mgmd"); + $exe_ndb_waiter= + mtr_exe_exists("$ndb_path/tools/ndb_waiter", + "$glob_basedir/bin/ndb_waiter"); + $exe_ndbd= + mtr_exe_exists("$ndb_path/src/kernel/ndbd", + "$glob_basedir/bin/ndbd"); + } + + # Look for the udf_example library + $lib_udf_example= + mtr_file_exists("$glob_basedir/sql/.libs/udf_example.so"); + + + # Look for mysqltest executable + if ( $glob_use_embedded_server ) + { + $exe_mysqltest= + mtr_exe_exists("$glob_basedir/libmysqld/examples/mysqltest_embedded", + "$path_client_bindir/mysqltest_embedded"); + } + else + { + $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mysqltest"); + + + } + + # Look for mysql_client_test executable + if ( $glob_use_embedded_server ) + { + $exe_mysql_client_test= + mtr_exe_exists("$glob_basedir/libmysqld/examples/mysql_client_test_embedded", + "$glob_basedir/tests/mysqltest_embedded"); + } + else + { + $exe_mysql_client_test= + mtr_exe_exists("$glob_basedir/tests/mysql_client_test"); + } } @@ -1422,8 +1494,7 @@ sub environment_setup () { $ENV{'SLAVE_MYPORT'}= $slave->[0]->{'port'}; $ENV{'SLAVE_MYPORT1'}= $slave->[1]->{'port'}; $ENV{'SLAVE_MYPORT2'}= $slave->[2]->{'port'}; -# $ENV{'MYSQL_TCP_PORT'}= '@MYSQL_TCP_PORT@'; # FIXME - $ENV{'MYSQL_TCP_PORT'}= 3306; + $ENV{'MYSQL_TCP_PORT'}= $default_mysqld_port; $ENV{MTR_BUILD_THREAD}= 0 unless $ENV{MTR_BUILD_THREAD}; # Set if not set @@ -1440,12 +1511,12 @@ sub environment_setup () { $ENV{'NDB_BACKUP_DIR'}= $clusters->[0]->{'data_dir'}; $ENV{'NDB_DATA_DIR'}= $clusters->[0]->{'data_dir'}; $ENV{'NDB_TOOLS_DIR'}= $path_ndb_tools_dir; - $ENV{'NDB_TOOLS_OUTPUT'}= $file_ndb_testrun_log; + $ENV{'NDB_TOOLS_OUTPUT'}= $path_ndb_testrun_log; $ENV{'NDB_CONNECTSTRING'}= $opt_ndbconnectstring; $ENV{'NDB_EXAMPLES_DIR'}= $path_ndb_examples_dir; $ENV{'MY_NDB_EXAMPLES_BINARY'}= $exe_ndb_example; - $ENV{'NDB_EXAMPLES_OUTPUT'}= $file_ndb_testrun_log; + $ENV{'NDB_EXAMPLES_OUTPUT'}= $path_ndb_testrun_log; # ---------------------------------------------------- # Setup env for IM @@ -1475,7 +1546,7 @@ sub environment_setup () { if ( $opt_debug ) { $cmdline_mysqlcheck .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqlcheck.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqlcheck.trace"; } $ENV{'MYSQL_CHECK'}= $cmdline_mysqlcheck; @@ -1488,9 +1559,9 @@ sub environment_setup () { if ( $opt_debug ) { $cmdline_mysqldump .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqldump-master.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqldump-master.trace"; $cmdline_mysqldumpslave .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqldump-slave.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqldump-slave.trace"; } $ENV{'MYSQL_DUMP'}= $cmdline_mysqldump; $ENV{'MYSQL_DUMP_SLAVE'}= $cmdline_mysqldumpslave; @@ -1499,7 +1570,7 @@ sub environment_setup () { # ---------------------------------------------------- # Setup env so childs can execute mysqlslap # ---------------------------------------------------- - unless ( $glob_win32 ) + if ( $exe_mysqlslap ) { my $cmdline_mysqlslap= "$exe_mysqlslap -uroot " . @@ -1510,7 +1581,7 @@ sub environment_setup () { if ( $opt_debug ) { $cmdline_mysqlslap .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqlslap.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqlslap.trace"; } $ENV{'MYSQL_SLAP'}= $cmdline_mysqlslap; } @@ -1526,7 +1597,7 @@ sub environment_setup () { if ( $opt_debug ) { $cmdline_mysqlimport .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqlimport.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqlimport.trace"; } $ENV{'MYSQL_IMPORT'}= $cmdline_mysqlimport; @@ -1542,7 +1613,7 @@ sub environment_setup () { if ( $opt_debug ) { $cmdline_mysqlshow .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqlshow.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqlshow.trace"; } $ENV{'MYSQL_SHOW'}= $cmdline_mysqlshow; @@ -1551,13 +1622,16 @@ sub environment_setup () { # ---------------------------------------------------- my $cmdline_mysqlbinlog= "$exe_mysqlbinlog" . - " --no-defaults --local-load=$opt_tmpdir" . - " --character-sets-dir=$path_charsetsdir"; + " --no-defaults --local-load=$opt_tmpdir"; + if ( $mysql_version_id >= 50000 ) + { + $cmdline_mysqlbinlog .=" --character-sets-dir=$path_charsetsdir"; + } if ( $opt_debug ) { $cmdline_mysqlbinlog .= - " --debug=d:t:A,$opt_vardir_trace/log/mysqlbinlog.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysqlbinlog.trace"; } $ENV{'MYSQL_BINLOG'}= $cmdline_mysqlbinlog; @@ -1578,13 +1652,16 @@ sub environment_setup () { my $cmdline_mysql_client_test= "$exe_mysql_client_test --no-defaults --testcase --user=root --silent " . "--port=$master->[0]->{'port'} " . - "--vardir=$opt_vardir " . "--socket=$master->[0]->{'path_sock'}"; + if ( $mysql_version_id >= 50000 ) + { + $cmdline_mysql_client_test .=" --vardir=$opt_vardir"; + } if ( $opt_debug ) { $cmdline_mysql_client_test .= - " --debug=d:t:A,$opt_vardir_trace/log/mysql_client_test.trace"; + " --debug=d:t:A,$path_vardir_trace/log/mysql_client_test.trace"; } if ( $glob_use_embedded_server ) @@ -1601,7 +1678,8 @@ sub environment_setup () { # Setup env so childs can execute mysql_fix_system_tables # ---------------------------------------------------- my $cmdline_mysql_fix_system_tables= - "$exe_mysql_fix_system_tables --no-defaults --host=localhost --user=root --password= " . + "$exe_mysql_fix_system_tables --no-defaults --host=localhost " . + "--user=root --password= " . "--basedir=$glob_basedir --bindir=$path_client_bindir --verbose " . "--port=$master->[0]->{'port'} " . "--socket=$master->[0]->{'path_sock'}"; @@ -1641,12 +1719,26 @@ sub environment_setup () { print "Using SLAVE_MYPORT = $ENV{SLAVE_MYPORT}\n"; print "Using SLAVE_MYPORT1 = $ENV{SLAVE_MYPORT1}\n"; print "Using SLAVE_MYPORT2 = $ENV{SLAVE_MYPORT2}\n"; - print "Using NDBCLUSTER_PORT = $ENV{NDBCLUSTER_PORT}\n"; - print "Using NDBCLUSTER_PORT_SLAVE = $ENV{NDBCLUSTER_PORT_SLAVE}\n"; - print "Using IM_PORT = $ENV{IM_PORT}\n"; - print "Using IM_MYSQLD1_PORT = $ENV{IM_MYSQLD1_PORT}\n"; - print "Using IM_MYSQLD2_PORT = $ENV{IM_MYSQLD2_PORT}\n"; + if ( ! $opt_skip_ndbcluster ) + { + print "Using NDBCLUSTER_PORT = $ENV{NDBCLUSTER_PORT}\n"; + if ( ! $opt_skip_ndbcluster_slave ) + { + print "Using NDBCLUSTER_PORT_SLAVE = $ENV{NDBCLUSTER_PORT_SLAVE}\n"; + } + } + if ( ! $opt_skip_im ) + { + print "Using IM_PORT = $ENV{IM_PORT}\n"; + print "Using IM_MYSQLD1_PORT = $ENV{IM_MYSQLD1_PORT}\n"; + print "Using IM_MYSQLD2_PORT = $ENV{IM_MYSQLD2_PORT}\n"; + } } + + # Create an environment variable to make it possible + # to detect that valgrind is being used from test cases + $ENV{'VALGRIND_TEST'}= $opt_valgrind; + } @@ -1700,6 +1792,8 @@ sub kill_running_server () { sub cleanup_stale_files () { + my $created_by_mem_file= "$glob_mysql_test_dir/var/created_by_mem"; + mtr_report("Removing Stale Files"); if ( $opt_vardir eq "$glob_mysql_test_dir/var" ) @@ -1707,15 +1801,26 @@ sub cleanup_stale_files () { # # Running with "var" in mysql-test dir # - if ( -l "$glob_mysql_test_dir/var" ) + if ( -l $opt_vardir) { - # Some users creates a soft link in mysql-test/var to another area - # - allow it - mtr_report("WARNING: Using the 'mysql-test/var' symlink"); - rmtree("$opt_vardir/log"); - rmtree("$opt_vardir/ndbcluster-$opt_ndbcluster_port"); - rmtree("$opt_vardir/run"); - rmtree("$opt_vardir/tmp"); + # var is a symlink + if (-f $created_by_mem_file) + { + # Remove the directory which the link points at + rmtree(readlink($opt_vardir)); + # Remove the entire "var" dir + rmtree("$opt_vardir/"); + } + else + { + # Some users creates a soft link in mysql-test/var to another area + # - allow it + mtr_report("WARNING: Using the 'mysql-test/var' symlink"); + rmtree("$opt_vardir/log"); + rmtree("$opt_vardir/ndbcluster-$opt_ndbcluster_port"); + rmtree("$opt_vardir/run"); + rmtree("$opt_vardir/tmp"); + } } else { @@ -1737,6 +1842,17 @@ sub cleanup_stale_files () { rmtree("$opt_vardir/"); } + if ( $opt_mem ) + { + # Runinng with var as a link to some "memory" location, normally tmpfs + rmtree($opt_mem); + mkpath($opt_mem); + mtr_verbose("Creating symlink from $opt_vardir to $opt_mem"); + symlink($opt_mem, $opt_vardir); + # Put a small file to recognize this dir was created by --mem + mtr_tofile($created_by_mem_file, $opt_mem); + } + mkpath("$opt_vardir/log"); mkpath("$opt_vardir/run"); mkpath("$opt_vardir/tmp"); @@ -1797,8 +1913,8 @@ sub check_running_as_root () { } - -sub check_ssl_support () { +sub check_ssl_support ($) { + my $mysqld_variables= shift; if ($opt_skip_ssl || $opt_extern) { @@ -1808,13 +1924,7 @@ sub check_ssl_support () { return; } - # check ssl support by testing using a switch - # that is only available in that case - if ( mtr_run($exe_mysqld, - ["--no-defaults", - "--ssl", - "--help"], - "", "/dev/null", "/dev/null", "") != 0 ) + if ( ! $mysqld_variables->{'ssl'} ) { if ( $opt_ssl) { @@ -1831,17 +1941,12 @@ sub check_ssl_support () { } -sub check_debug_support () { +sub check_debug_support ($) { + my $mysqld_variables= shift; - # check debug support by testing using a switch - # that is only available in that case - if ( mtr_run($exe_mysqld, - ["--no-defaults", - "--debug", - "--help"], - "", "/dev/null", "/dev/null", "") != 0 ) + if ( ! $mysqld_variables->{'debug'} ) { - # mtr_report("Binaries are not debug compiled"); + #mtr_report("Binaries are not debug compiled"); $debug_compiled_binaries= 0; if ( $opt_debug ) @@ -1860,7 +1965,8 @@ sub check_debug_support () { # ############################################################################## -sub check_ndbcluster_support () { +sub check_ndbcluster_support ($) { + my $mysqld_variables= shift; if ($opt_skip_ndbcluster) { @@ -1869,13 +1975,7 @@ sub check_ndbcluster_support () { return; } - # check ndbcluster support by runnning mysqld using a switch - # that is only available in that case - if ( mtr_run($exe_mysqld, - ["--no-defaults", - "--ndb-use-exact-count", - "--help"], - "", "/dev/null", "/dev/null", "") != 0 ) + if ( ! $mysqld_variables->{'ndb-connectstring'} ) { mtr_report("Skipping ndbcluster, mysqld not compiled with ndbcluster"); $opt_skip_ndbcluster= 1; @@ -1914,11 +2014,22 @@ sub ndbcluster_start_install ($) { if (!$opt_bench) { # Use a smaller configuration - $ndb_no_ord=32; - $ndb_con_op=5000; - $ndb_dmem="20M"; - $ndb_imem="1M"; - $ndb_pbmem="4M"; + if ( $mysql_version_id < 50100 ) + { + # 4.1 and 5.0 is using a "larger" --small configuration + $ndb_no_ord=128; + $ndb_con_op=10000; + $ndb_dmem="40M"; + $ndb_imem="12M"; + } + else + { + $ndb_no_ord=32; + $ndb_con_op=5000; + $ndb_dmem="20M"; + $ndb_imem="1M"; + $ndb_pbmem="4M"; + } } my $config_file_template= "ndb/ndb_config_${nodes}_node.ini"; @@ -1941,6 +2052,11 @@ sub ndbcluster_start_install ($) { s/CHOOSE_HOSTNAME_.*/$ndb_host/; s/CHOOSE_FILESYSTEM/$cluster->{'data_dir'}/; s/CHOOSE_PORT_MGM/$cluster->{'port'}/; + if ( $mysql_version_id < 50000 ) + { + my $base_port= $cluster->{'port'} + 1; + s/CHOOSE_PORT_TRANSPORTER/$base_port/; + } s/CHOOSE_DiskPageBufferMemory/$ndb_pbmem/; print OUT "$_ \n"; @@ -2062,7 +2178,10 @@ sub ndbd_start ($$$) { mtr_add_arg($args, "--no-defaults"); mtr_add_arg($args, "--core"); mtr_add_arg($args, "--ndb-connectstring=%s", "$cluster->{'connect_string'}"); - mtr_add_arg($args, "--character-sets-dir=%s", "$path_charsetsdir"); + if ( $mysql_version_id >= 50000) + { + mtr_add_arg($args, "--character-sets-dir=%s", "$path_charsetsdir"); + } mtr_add_arg($args, "--nodaemon"); mtr_add_arg($args, "$extra_args"); @@ -2163,8 +2282,6 @@ sub run_benchmarks ($) { chdir($glob_mysql_bench_dir) or mtr_error("Couldn't chdir to '$glob_mysql_bench_dir': $!"); - # FIXME write shorter.... - if ( ! $benchmark ) { mtr_add_arg($args, "--log"); @@ -2196,9 +2313,6 @@ sub run_benchmarks ($) { # ############################################################################## -# FIXME how to specify several suites to run? Comma separated list? - - sub run_suite () { my ($suite, $tests)= @_; @@ -2255,6 +2369,9 @@ sub run_suite () { ############################################################################## sub initialize_servers () { + + datadir_setup(); + if ( ! $glob_use_running_server ) { kill_running_server(); @@ -2293,7 +2410,8 @@ sub mysql_install_db () { my $cluster_started_ok= 1; # Assume it can be started if (ndbcluster_start_install($clusters->[0]) || - $max_slave_num && ndbcluster_start_install($clusters->[1])) + ($max_slave_num && !$opt_skip_ndbcluster_slave && + ndbcluster_start_install($clusters->[1]))) { mtr_warning("Failed to start install of cluster"); $cluster_started_ok= 0; @@ -2396,10 +2514,10 @@ sub install_db ($$) { if ( $opt_debug ) { mtr_add_arg($args, "--debug=d:t:i:A,%s/log/bootstrap_%s.trace", - $opt_vardir_trace, $type); + $path_vardir_trace, $type); } - if ( ! $opt_netware ) + if ( ! $glob_netware ) { mtr_add_arg($args, "--language=%s", $path_language); mtr_add_arg($args, "--character-sets-dir=%s", $path_charsetsdir); @@ -2492,9 +2610,15 @@ skip-innodb skip-ndbcluster EOF ; - + if ( $mysql_version_id < 50100 ) + { + print OUT "skip-bdb\n"; + } print OUT "nonguarded\n" if $instance->{'nonguarded'}; - print OUT "log-output=FILE\n" if $instance->{'old_log_format'}; + if ( $mysql_version_id >= 50100 ) + { + print OUT "log-output=FILE\n" if $instance->{'old_log_format'}; + } print OUT "\n"; } @@ -2553,21 +2677,24 @@ sub run_testcase_check_skip_test($) return 1; } - # If test needs cluster, check that master installed ok - if ( $tinfo->{'ndb_test'} and !$clusters->[0]->{'installed_ok'} ) + if ($tinfo->{'ndb_test'}) { - mtr_report_test_name($tinfo); - mtr_report_test_failed($tinfo); - return 1; - } + foreach my $cluster (@{$clusters}) + { + last if ($opt_skip_ndbcluster_slave and + $cluster->{'name'} eq 'Slave'); - # If test needs slave cluster, check that it installed ok - if ( $tinfo->{'ndb_test'} and $tinfo->{'slave_num'} and - !$clusters->[1]->{'installed_ok'} ) - { - mtr_report_test_name($tinfo); - mtr_report_test_failed($tinfo); - return 1; + # If test needs this cluster, check it was installed ok + if ( !$cluster->{'installed_ok'} ) + { + mtr_tofile($path_timefile, + "Test marked as failed because $cluster->{'name'} " . + "was not installed ok!"); + mtr_report_test_name($tinfo); + mtr_report_test_failed($tinfo); + return 1; + } + } } return 0; @@ -2579,28 +2706,40 @@ sub do_before_run_mysqltest($) my $tinfo= shift; my $tname= $tinfo->{'name'}; - # Remove old reject file - if ( $opt_suite eq "main" ) + # Remove old files produced by mysqltest + my $result_dir= "r"; + if ( ! $opt_suite eq "main" ) { - unlink("r/$tname.reject"); - } - else - { - unlink("suite/$opt_suite/r/$tname.reject"); + $result_dir= "suite/$opt_suite/r"; } + unlink("$result_dir/$tname.reject"); + unlink("$result_dir/$tname.progress"); + unlink("$result_dir/$tname.log"); + unlink("$result_dir/$tname.warnings"); - -# MASV cleanup... mtr_tonewfile($path_current_test_log,"$tname\n"); # Always tell where we are # output current test to ndbcluster log file to enable diagnostics - mtr_tofile($file_ndb_testrun_log,"CURRENT TEST $tname\n"); + mtr_tofile($path_ndb_testrun_log,"CURRENT TEST $tname\n"); mtr_tofile($master->[0]->{'path_myerr'},"CURRENT_TEST: $tname\n"); if ( $master->[1]->{'pid'} ) { mtr_tofile($master->[1]->{'path_myerr'},"CURRENT_TEST: $tname\n"); } + + if ( $mysql_version_id < 50000 ) + { + # Set envirnoment variable NDB_STATUS_OK to 1 + # if script decided to run mysqltest cluster _is_ installed ok + $ENV{'NDB_STATUS_OK'} = "1"; + } + elsif ( $mysql_version_id < 50100 ) + { + # Set envirnoment variable NDB_STATUS_OK to YES + # if script decided to run mysqltest cluster _is_ installed ok + $ENV{'NDB_STATUS_OK'} = "YES"; + } } sub do_after_run_mysqltest($) @@ -2608,12 +2747,13 @@ sub do_after_run_mysqltest($) my $tinfo= shift; my $tname= $tinfo->{'name'}; - #MASV cleanup - # Save info from this testcase run to mysqltest.log - my $testcase_log= mtr_fromfile($path_timefile) if -f $path_timefile; - mtr_tofile($path_mysqltest_log,"CURRENT TEST $tname\n"); - mtr_tofile($path_mysqltest_log, $testcase_log); - } + mtr_tofile($path_mysqltest_log,"CURRENT TEST $tname\n"); + + # Save info from this testcase run to mysqltest.log + mtr_appendfile_to_file($path_timefile, $path_mysqltest_log) + if -f $path_timefile; + +} ############################################################################## @@ -2824,24 +2964,17 @@ sub report_failure_and_restart ($) { ############################################################################## -# The embedded server needs the cleanup so we do some of the start work -# but stop before actually running mysqld or anything. -sub do_before_start_master ($$) { - my $tname= shift; - my $init_script= shift; +sub do_before_start_master ($) { + my ($tinfo)= @_; + + my $tname= $tinfo->{'name'}; + my $init_script= $tinfo->{'master_sh'}; # FIXME what about second master..... - # Remove stale binary logs except for 2 tests which need them FIXME here???? - if ( $tname ne "rpl_crash_binlog_ib_1b" and - $tname ne "rpl_crash_binlog_ib_2b" and - $tname ne "rpl_crash_binlog_ib_3b") + foreach my $bin ( glob("$opt_vardir/log/master*-bin*") ) { - # FIXME we really want separate dir for binlogs - foreach my $bin ( glob("$opt_vardir/log/master*-bin*") ) - { - unlink($bin); - } + unlink($bin); } # FIXME only remove the ones that are tied to this master @@ -2861,31 +2994,23 @@ sub do_before_start_master ($$) { # mtr_warning("$init_script exited with code $ret"); } } - # for gcov FIXME needed? If so we need more absolute paths - # chdir($glob_basedir); } -sub do_before_start_slave ($$) { - my $tname= shift; - my $init_script= shift; +sub do_before_start_slave ($) { + my ($tinfo)= @_; - # Remove stale binary logs and old master.info files - # except for too tests which need them - if ( $tname ne "rpl_crash_binlog_ib_1b" and - $tname ne "rpl_crash_binlog_ib_2b" and - $tname ne "rpl_crash_binlog_ib_3b" ) + my $tname= $tinfo->{'name'}; + my $init_script= $tinfo->{'master_sh'}; + + foreach my $bin ( glob("$opt_vardir/log/slave*-bin*") ) { - # FIXME we really want separate dir for binlogs - foreach my $bin ( glob("$opt_vardir/log/slave*-bin*") ) - { - unlink($bin); - } - # FIXME really master?! - unlink("$slave->[0]->{'path_myddir'}/master.info"); - unlink("$slave->[0]->{'path_myddir'}/relay-log.info"); + unlink($bin); } + unlink("$slave->[0]->{'path_myddir'}/master.info"); + unlink("$slave->[0]->{'path_myddir'}/relay-log.info"); + # Run slave initialization shell script if one exists if ( $init_script ) { @@ -2931,7 +3056,11 @@ sub mysqld_arguments ($$$$$) { mtr_add_arg($args, "%s--basedir=%s", $prefix, $path_my_basedir); mtr_add_arg($args, "%s--character-sets-dir=%s", $prefix, $path_charsetsdir); - mtr_add_arg($args, "%s--log-bin-trust-function-creators", $prefix); + if ( $mysql_version_id >= 50000 ) + { + mtr_add_arg($args, "%s--log-bin-trust-function-creators", $prefix); + } + mtr_add_arg($args, "%s--default-character-set=latin1", $prefix); mtr_add_arg($args, "%s--language=%s", $prefix, $path_language); mtr_add_arg($args, "%s--tmpdir=$opt_tmpdir", $prefix); @@ -2980,7 +3109,10 @@ sub mysqld_arguments ($$$$$) { mtr_add_arg($args, "%s--ndbcluster", $prefix); mtr_add_arg($args, "%s--ndb-connectstring=%s", $prefix, $cluster->{'connect_string'}); - mtr_add_arg($args, "%s--ndb-extra-logging", $prefix); + if ( $mysql_version_id >= 50100 ) + { + mtr_add_arg($args, "%s--ndb-extra-logging", $prefix); + } } } @@ -2998,7 +3130,7 @@ sub mysqld_arguments ($$$$$) { $opt_vardir, $sidx); # FIXME use own dir for binlogs mtr_add_arg($args, "%s--log-slave-updates", $prefix); } - # FIXME option duplicated for slave + mtr_add_arg($args, "%s--log=%s", $prefix, $slave->[$idx]->{'path_mylog'}); mtr_add_arg($args, "%s--master-retry-count=10", $prefix); @@ -3019,8 +3151,9 @@ sub mysqld_arguments ($$$$$) { # Directory where slaves find the dumps generated by "load data" # on the server. The path need to have constant length otherwise # test results will vary, thus a relative path is used. + my $slave_load_path= "../tmp"; mtr_add_arg($args, "%s--slave-load-tmpdir=%s", $prefix, - "../tmp"); + $slave_load_path); mtr_add_arg($args, "%s--socket=%s", $prefix, $slave->[$idx]->{'path_sock'}); mtr_add_arg($args, "%s--set-variable=slave_net_timeout=10", $prefix); @@ -3055,7 +3188,10 @@ sub mysqld_arguments ($$$$$) { mtr_add_arg($args, "%s--ndbcluster", $prefix); mtr_add_arg($args, "%s--ndb-connectstring=%s", $prefix, $clusters->[$slave->[$idx]->{'cluster'}]->{'connect_string'}); - mtr_add_arg($args, "%s--ndb-extra-logging", $prefix); + if ( $mysql_version_id >= 50100 ) + { + mtr_add_arg($args, "%s--ndb-extra-logging", $prefix); + } } } # end slave @@ -3064,12 +3200,12 @@ sub mysqld_arguments ($$$$$) { if ( $type eq 'master' ) { mtr_add_arg($args, "%s--debug=d:t:i:A,%s/log/master%s.trace", - $prefix, $opt_vardir_trace, $sidx); + $prefix, $path_vardir_trace, $sidx); } if ( $type eq 'slave' ) { mtr_add_arg($args, "%s--debug=d:t:i:A,%s/log/slave%s.trace", - $prefix, $opt_vardir_trace, $sidx); + $prefix, $path_vardir_trace, $sidx); } } @@ -3077,7 +3213,6 @@ sub mysqld_arguments ($$$$$) { mtr_add_arg($args, "%s--key_buffer_size=1M", $prefix); mtr_add_arg($args, "%s--sort_buffer=256K", $prefix); mtr_add_arg($args, "%s--max_heap_table_size=1M", $prefix); - mtr_add_arg($args, "%s--log-bin-trust-function-creators", $prefix); if ( $opt_ssl_supported ) { @@ -3255,10 +3390,13 @@ sub stop_all_servers () { print "Stopping All Servers\n"; - print "Shutting-down Instance Manager\n"; - unless (mtr_im_stop($instance_manager, "stop_all_servers")) + if ( ! $opt_skip_im ) { - mtr_error("Failed to stop Instance Manager.") + print "Shutting-down Instance Manager\n"; + unless (mtr_im_stop($instance_manager, "stop_all_servers")) + { + mtr_error("Failed to stop Instance Manager.") + } } my %admin_pids; # hash of admin processes that requests shutdown @@ -3384,6 +3522,11 @@ sub run_testcase_need_master_restart($) join(" ", @{$tinfo->{'master_opt'}}) . "' != '" . join(" ", @{$master->[0]->{'start_opts'}}) . "'" ); } + elsif( ! $master->[0]->{'pid'} ) + { + $do_restart= 1; + mtr_verbose("Restart because: master is not started"); + } return $do_restart; } @@ -3395,57 +3538,35 @@ sub run_testcase_need_slave_restart($) # We try to find out if we are to restart the slaves my $do_slave_restart= 0; # Assumes we don't have to - # FIXME only restart slave when necessary - $do_slave_restart= 1; + if ( $max_slave_num == 0) + { + mtr_verbose("No testcase use slaves, no slave restarts"); + } + else + { -# if ( ! $slave->[0]->{'pid'} ) -# { -# # mtr_verbose("Slave not started, no need to check slave restart"); -# } -# elsif ( $do_restart ) -# { -# $do_slave_restart= 1; # Always restart if master restart -# mtr_verbose("Restart slave because: Master restart"); -# } -# elsif ( $tinfo->{'slave_sh'} ) -# { -# $do_slave_restart= 1; # Always restart if script to run -# mtr_verbose("Restart slave because: Always restart if script to run"); -# } -# elsif ( ! $opt_skip_ndbcluster_slave and -# $tinfo->{'ndb_test'} == 0 and -# $clusters->[1]->{'pid'} != 0 ) -# { -# $do_slave_restart= 1; # Restart without slave cluster -# mtr_verbose("Restart slave because: Test does not need slave cluster"); -# } -# elsif ( ! $opt_with_ndbcluster_slave and -# $tinfo->{'ndb_test'} == 1 and -# $clusters->[1]->{'pid'} == 0 ) -# { -# $do_slave_restart= 1; # Restart with slave cluster -# mtr_verbose("Restart slave because: Test need slave cluster"); -# } -# elsif ( $tinfo->{'slave_restart'} ) -# { -# $do_slave_restart= 1; -# mtr_verbose("Restart slave because: slave_restart"); -# } -# elsif ( $slave->[0]->{'running_slave_is_special'} ) -# { -# $do_slave_restart= 1; -# mtr_verbose("Restart slave because: running_slave_is_special"); -# } -# # Check that running slave was started with same options -# # as the current test requires -# elsif (! mtr_same_opts($slave->[0]->{'start_opts'}, -# $tinfo->{'slave_opt'}) ) -# { -# $do_slave_restart= 1; -# mtr_verbose("Restart slave because: running with different options '" . -# join(" ", @{$tinfo->{'slave_opt'}}) . "' != '" . -# join(" ", @{$slave->[0]->{'start_opts'}}) . "'" ); -# } + # Check if any slave is currently started + my $any_slave_started= 0; + foreach my $mysqld (@{$slave}) + { + if ( $mysqld->{'pid'} ) + { + $any_slave_started= 1; + last; + } + } + + if ($any_slave_started) + { + mtr_verbose("Any slave is started, need to restart"); + $do_slave_restart= 1; + } + elsif ( $tinfo->{'slave_num'} ) + { + mtr_verbose("Test need slave, check for restart"); + $do_slave_restart= 1; + } + } return $do_slave_restart; @@ -3610,6 +3731,11 @@ sub run_testcase_start_servers($) { return; } + # ------------------------------------------------------- + # Init variables that can change between server starts + # ------------------------------------------------------- + $ENV{'TZ'}= $tinfo->{'timezone'}; + if ( $tinfo->{'component_id'} eq 'mysqld' ) { if ( ! $opt_skip_ndbcluster and @@ -3623,7 +3749,7 @@ sub run_testcase_start_servers($) { if ( !$master->[0]->{'pid'} ) { # Master mysqld is not started - do_before_start_master($tname,$tinfo->{'master_sh'}); + do_before_start_master($tinfo); mysqld_start($master->[0],$tinfo->{'master_opt'},[]); @@ -3633,16 +3759,21 @@ sub run_testcase_start_servers($) { { # Test needs cluster, start an extra mysqld connected to cluster - # First wait for first mysql server to have created ndb system tables ok - # FIXME This is a workaround so that only one mysqld creates the tables - if ( ! sleep_until_file_created( - "$master->[0]->{'path_myddir'}/cluster/apply_status.ndb", - $master->[0]->{'start_timeout'}, - $master->[0]->{'pid'})) + if ( $mysql_version_id >= 50100 ) { - mtr_report("Failed to create 'cluster/apply_status' table"); - report_failure_and_restart($tinfo); - return; + # First wait for first mysql server to have created ndb system + # tables ok FIXME This is a workaround so that only one mysqld + # create the tables + if ( ! sleep_until_file_created( + "$master->[0]->{'path_myddir'}/cluster/apply_status.ndb", + $master->[0]->{'start_timeout'}, + $master->[0]->{'pid'})) + { + mtr_report_test_name($tinfo); + mtr_report("Failed to create 'cluster/apply_status' table"); + report_failure_and_restart($tinfo); + return; + } } mtr_tofile($master->[1]->{'path_myerr'},"CURRENT_TEST: $tname\n"); @@ -3666,6 +3797,7 @@ sub run_testcase_start_servers($) { unless ( mtr_im_start($instance_manager, $tinfo->{im_opts}) ) { + mtr_report_test_name($tinfo); report_failure_and_restart($tinfo); mtr_report("Failed to start Instance Manager. " . "The test '$tname' is marked as failed."); @@ -3682,7 +3814,7 @@ sub run_testcase_start_servers($) { restore_slave_databases($tinfo->{'slave_num'}); - do_before_start_slave($tname,$tinfo->{'slave_sh'}); + do_before_start_slave($tinfo); if ( ! $opt_skip_ndbcluster_slave and !$clusters->[1]->{'pid'} and @@ -3856,11 +3988,6 @@ sub run_mysqltest ($) { mtr_add_arg($args, "--big-test"); } - if ( $opt_valgrind ) - { - mtr_add_arg($args, "--valgrind"); - } - if ( $opt_compress ) { mtr_add_arg($args, "--compress"); @@ -3874,7 +4001,7 @@ sub run_mysqltest ($) { if ( $opt_debug ) { mtr_add_arg($args, "--debug=d:t:A,%s/log/mysqltest.trace", - $opt_vardir_trace); + $path_vardir_trace); } if ( $opt_ssl_supported ) @@ -3971,11 +4098,6 @@ sub run_mysqltest ($) { } } - # ------------------------------------------------------- - # Init variables that change for each testcase - # ------------------------------------------------------- - $ENV{'TZ'}= $tinfo->{'timezone'}; - my $res = mtr_run_test($exe,$args,"","",$path_timefile,""); if ( $opt_check_testcases ) @@ -4205,9 +4327,7 @@ sub usage ($) { print STDERR <&1 @@ -360,18 +359,80 @@ select 3 from t1 ; # Missing delimiter # The comment will be "sucked into" the sleep command since # delimiter is missing until after "show status" ---system echo "sleep 4" > $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "# A comment" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "show status;" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +sleep 4 +# A comment +show status; +EOF --error 1 --exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 # # Missing delimiter until eof # The comment will be "sucked into" the sleep command since -# delimiter is missing ---system echo "sleep 7" > $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "# Another comment" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql +# delimiter is missing +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +sleep 7 +# Another comment +EOF +--error 1 +--exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 + +# +# Missing delimiter until "disable_query_log" +# +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +disconnect default + +# +# comment +# comment 3 +disable_query_log; +EOF +--error 1 +--exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 + +# +# Missing delimiter until "disable_query_log" +# +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +disconnect default + +# +# comment + +# comment 3 +disable_query_log; +EOF +--error 1 +--exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 + +# +# Missing delimiter until eof +# +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +disconnect default + +# +# comment +# comment2 + +# comment 3 +--disable_query_log +EOF +--error 1 +--exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 + +# +# Missing delimiter until eof +# +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +disconnect default # comment +# comment part2 + +# comment 3 +--disable_query_log +EOF --error 1 --exec $MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/mysqltest.sql 2>&1 @@ -388,6 +449,67 @@ select 3 from t1 ; --sleep 1 # Wait for insert delayed to be executed. --sleep 1 # Wait for insert delayed to be executed. +# ---------------------------------------------------------------------------- +# Test error +# ---------------------------------------------------------------------------- + +# Missing argument +--error 1 +--exec echo "error;" | $MYSQL_TEST 2>&1 +--error 1 +--exec echo "--error" | $MYSQL_TEST 2>&1 + +# First char must be uppercase 'S' or 'E' or [0-9] +--error 1 +--exec echo "--error s99999" | $MYSQL_TEST 2>&1 +--error 1 +--exec echo "--error e99999" | $MYSQL_TEST 2>&1 +--error 1 +--exec echo "--error 9eeeee" | $MYSQL_TEST 2>&1 +--error 1 +--exec echo "--error 1sssss" | $MYSQL_TEST 2>&1 + +# First char 'S' but too long +--error 1 +--exec echo "--error S999999" | $MYSQL_TEST 2>&1 + +# First char 'S' but lowercase char found +--error 1 +--exec echo "--error S99a99" | $MYSQL_TEST 2>&1 + +# First char 'S' but too short +--error 1 +--exec echo "--error S9999" | $MYSQL_TEST 2>&1 + +# First char 'E' but not found in error array +--error 1 +--exec echo "--error E9999" | $MYSQL_TEST 2>&1 + +# First char [0-9] but contains chars +--error 1 +--exec echo "--error 999e9" | $MYSQL_TEST 2>&1 +--error 1 +--exec echo "--error 9b" | $MYSQL_TEST 2>&1 + +# Multiple errorcodes separated by ',' +--error 1,1,1,1 +#--error 9,ER_PARSE_ERROR +#--error ER_PARSE_ERROR +#--error 9,ER_PARSE_ERROR,9,ER_PARSE_ERROR +#--error 9, ER_PARSE_ERROR, 9, ER_PARSE_ERROR +#--error 9,S00000,9,ER_PARSE_ERROR +#--error 9,S00000,9,ER_PARSE_ERROR,ER_PARSE_ERROR,ER_PARSE_ERROR,9,10,11,12 +--error 9,S00000,9 +--error 9,S00000,9,9,10,11,12 +--error 9 ,10 +--error 9 , 10 +--error 9 , 10 +--error 9 , 10 + +# Too many errorcodes specified +--error 1 +--exec echo "--error 1,2,3,4,5,6,7,8,9,10,11" | $MYSQL_TEST 2>&1 + # ---------------------------------------------------------------------------- # Test echo command @@ -610,6 +732,7 @@ echo $var3_var3; # Fix win paths --replace_result \\ / +# Source a nonexisting file --error 1 --exec echo "source non_existingFile;" | $MYSQL_TEST 2>&1 @@ -627,13 +750,16 @@ echo $var3_var3; # Test execution of source in a while loop +--write_file $MYSQLTEST_VARDIR/tmp/sourced.inc +echo here is the sourced script; +EOF --disable_query_log let $outer= 2; # Number of outer loops while ($outer) { eval SELECT '$outer = outer loop variable after while' AS ""; - --source include/sourced.inc + --source $MYSQLTEST_VARDIR/tmp/sourced.inc eval SELECT '$outer = outer loop variable before dec' AS ""; dec $outer; @@ -661,11 +787,12 @@ let $num= 9; while ($num) { SELECT 'In loop' AS ""; - --source include/sourced1.inc + --source $MYSQLTEST_VARDIR/tmp/sourced.inc dec $num; } --enable_abort_on_error --enable_query_log +--remove_file $MYSQLTEST_VARDIR/tmp/sourced.inc # ---------------------------------------------------------------------------- # Test sleep command @@ -817,10 +944,150 @@ while (!$i) } # Exceed max nesting level +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest_while.inc +let $1 = 10; +while ($1) +{ +while ($1) +{ +while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + while ($1) +{ + echo $1; + dec $1; +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +} +EOF # Fix win path ---replace_result \\ / +--replace_result \\ / $MYSQLTEST_VARDIR MYSQLTEST_VARDIR --error 1 ---exec echo "source include/mysqltest_while.inc;" | $MYSQL_TEST 2>&1 +--exec echo "source $MYSQLTEST_VARDIR/tmp/mysqltest_while.inc;" | $MYSQL_TEST 2>&1 +--remove_file $MYSQLTEST_VARDIR/tmp/mysqltest_while.inc --error 1 --exec echo "while \$i;" | $MYSQL_TEST 2>&1 --error 1 @@ -925,12 +1192,6 @@ select "a" as col1, "c" as col2; --error 1 --exec echo "connect (con2,);" | $MYSQL_TEST 2>&1 --error 1 ---exec echo "connect (con2,localhost);" | $MYSQL_TEST 2>&1 ---error 1 ---exec echo "connect (con2, localhost, root);" | $MYSQL_TEST 2>&1 ---error 1 ---exec echo "connect (con2, localhost, root,);" | $MYSQL_TEST 2>&1 ---error 1 --exec echo "connect (con2,localhost,root,,illegal_db);" | $MYSQL_TEST 2>&1 --error 1 --exec echo "connect (con1,localhost,root,,,illegal_port,);" | $MYSQL_TEST 2>&1 @@ -938,13 +1199,15 @@ select "a" as col1, "c" as col2; --exec echo "connect (con1,localhost,root,,,,,SMTP POP);" | $MYSQL_TEST 2>&1 # Repeat connect/disconnect ---system echo "let \$i=100;" > $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "while (\$i)" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "{" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo " connect (test_con1,localhost,root,,); " >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo " disconnect test_con1; " >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo " dec \$i; " >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql ---system echo "}" >> $MYSQLTEST_VARDIR/tmp/mysqltest.sql +--write_file $MYSQLTEST_VARDIR/tmp/mysqltest.sql +let $i=100; +while ($i) +{ + connect (test_con1,localhost,root,,); + disconnect test_con1; + dec $i; +} +EOF --exec echo "source $MYSQLTEST_VARDIR/tmp/mysqltest.sql; echo OK;" | $MYSQL_TEST 2>&1 # Repeat connect/disconnect, exceed max number of connections @@ -1149,8 +1412,6 @@ query sleep; --error 1065 query ; ---echo End of 5.0 tests - # test for replace_regex --replace_regex /at/b/ select "at" as col1, "c" as col2; @@ -1189,4 +1450,103 @@ insert into t1 values (2,4); select * from t1; drop table t1; ---echo End of 5.1 tests +# ---------------------------------------------------------------------------- +# test for remove_file +# ---------------------------------------------------------------------------- + +--error 1 +--exec echo "remove_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +remove_file non_existing_file; + +# ---------------------------------------------------------------------------- +# test for write_file +# ---------------------------------------------------------------------------- +--error 1 +--exec echo "write_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "write_file filename ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "write_file filename \";" | $MYSQL_TEST 2>&1 + +write_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +Content for test_file1 +EOF +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; + +write_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp END_DELIMITER; +Content for test_file1 contains EOF +END_DELIMITER +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; + +# ---------------------------------------------------------------------------- +# test for file_exist +# ---------------------------------------------------------------------------- +--error 1 +--exec echo "file_exists ;" | $MYSQL_TEST 2>&1 + +--error 0,1 +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +--error 1 +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +write_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +Content for test_file1 +EOF +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +--error 1 +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; + + +# ---------------------------------------------------------------------------- +# test for copy_file +# ---------------------------------------------------------------------------- +--write_file $MYSQLTEST_VARDIR/tmp/file1.tmp +file1 +EOF + +copy_file $MYSQLTEST_VARDIR/tmp/file1.tmp $MYSQLTEST_VARDIR/tmp/file2.tmp; +file_exists $MYSQLTEST_VARDIR/tmp/file2.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/file2.tmp; + +--error 1 +--exec echo "copy_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "copy_file from_file;" | $MYSQL_TEST 2>&1 + +# ---------------------------------------------------------------------------- +# test for perl +# ---------------------------------------------------------------------------- +--perl +print "hello\n"; +EOF + +--perl EOF +print "hello\n"; +EOF + +--perl DELIMITER +print "hello\n"; +DELIMITER + +--error 1 +--exec echo "perl TOO_LONG_DELIMITER ;" | $MYSQL_TEST 2>&1 + +perl; +print "hello\n"; +EOF + +perl; + # Print "hello" + print "hello\n"; +EOF + + +--echo End of tests diff --git a/mysql-test/t/partition.test b/mysql-test/t/partition.test index 928d855f9f4..4cdf7a31915 100644 --- a/mysql-test/t/partition.test +++ b/mysql-test/t/partition.test @@ -4,6 +4,7 @@ # Taken fromm the select test # -- source include/have_partition.inc +-- source include/have_innodb.inc # # This test is disabled on Windows due to BUG#19107 # @@ -1332,37 +1333,51 @@ eval SET @inx_dir = 'INDEX DIRECTORY = ''$MYSQLTEST_VARDIR/master-data/tmpinx''' let $inx_directory = `select @inx_dir`; --enable_query_log ---replace_result $MYSQLTEST_VARDIR "hello" +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval create table t1 (a int) engine myisam partition by range (a) subpartition by hash (a) (partition p0 VALUES LESS THAN (1) $data_directory $inx_directory (SUBPARTITION subpart00, SUBPARTITION subpart01)); ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/test/t1.* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/test/t1#* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/tmpdata/t1#* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/tmpinx/t1#* || true ---replace_result $MYSQLTEST_VARDIR "hello" +--echo Checking if file exists before alter +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1.frm +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1.par +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p0#SP#subpart00.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p0#SP#subpart00.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p0#SP#subpart01.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p0#SP#subpart01.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p0#SP#subpart00.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p0#SP#subpart01.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p0#SP#subpart00.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p0#SP#subpart01.MYI +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval ALTER TABLE t1 REORGANIZE PARTITION p0 INTO (partition p1 VALUES LESS THAN (1) $data_directory $inx_directory (SUBPARTITION subpart10, SUBPARTITION subpart11), partition p2 VALUES LESS THAN (2) $data_directory $inx_directory (SUBPARTITION subpart20, SUBPARTITION subpart21)); ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/test/t1.* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/test/t1#* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/tmpdata/t1#* || true ---replace_result $MYSQLTEST_VARDIR "hello" ---exec ls $MYSQLTEST_VARDIR/master-data/tmpinx/t1#* || true +--echo Checking if file exists after alter +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1.frm +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1.par +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p1#SP#subpart10.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p1#SP#subpart10.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p1#SP#subpart11.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p1#SP#subpart11.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p2#SP#subpart20.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p2#SP#subpart20.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p2#SP#subpart21.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/test/t1#P#p2#SP#subpart21.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p1#SP#subpart10.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p1#SP#subpart11.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p2#SP#subpart20.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpdata/t1#P#p2#SP#subpart21.MYD +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p1#SP#subpart10.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p1#SP#subpart11.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p2#SP#subpart20.MYI +--file_exists $MYSQLTEST_VARDIR/master-data/tmpinx/t1#P#p2#SP#subpart21.MYI drop table t1; --exec rmdir $MYSQLTEST_VARDIR/master-data/tmpdata || true diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index ac79dbc3434..31034099515 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -354,14 +354,14 @@ create table t1 (a int, b int); insert into t1 (a, b) values (1,1), (1,2), (2,1), (2,2); prepare stmt from "explain select * from t1 where t1.a=2 and t1.a=t1.b and t1.b > 1 + ?"; ---replace_column 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - set @v=5; -execute stmt using @v; --replace_column 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - +execute stmt using @v; set @v=0; -execute stmt using @v; --replace_column 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - +execute stmt using @v; set @v=5; +--replace_column 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - execute stmt using @v; drop table t1; deallocate prepare stmt; diff --git a/mysql-test/t/ps_1general.test b/mysql-test/t/ps_1general.test index c0b81796731..4c0f411c758 100644 --- a/mysql-test/t/ps_1general.test +++ b/mysql-test/t/ps_1general.test @@ -316,8 +316,8 @@ prepare stmt4 from ' show table status from test like ''t9%'' '; --replace_column 8 # 12 # 13 # 14 # # Bug#4288 execute stmt4; ---replace_column 2 # prepare stmt4 from ' show status like ''Threads_running'' '; +--replace_column 2 # execute stmt4; prepare stmt4 from ' show variables like ''sql_mode'' '; execute stmt4; diff --git a/mysql-test/t/ps_grant.test b/mysql-test/t/ps_grant.test index 4c48b4d151f..b25facdb418 100644 --- a/mysql-test/t/ps_grant.test +++ b/mysql-test/t/ps_grant.test @@ -35,7 +35,7 @@ use mysqltest; --source include/ps_create.inc --source include/ps_renew.inc --enable_query_log -eval use $DB; +use test; grant usage on mysqltest.* to second_user@localhost identified by 'looser' ; grant select on mysqltest.t9 to second_user@localhost diff --git a/mysql-test/t/query_cache.test b/mysql-test/t/query_cache.test index 2c94fe63c04..67c4c4cb20b 100644 --- a/mysql-test/t/query_cache.test +++ b/mysql-test/t/query_cache.test @@ -699,7 +699,7 @@ select a from t1; flush query cache; drop table t1, t2; -set GLOBAL query_cache_size=1355776 +set GLOBAL query_cache_size=1355776; # diff --git a/mysql-test/t/rpl_truncate_7ndb.test b/mysql-test/t/rpl_truncate_7ndb.test index b0d4bdfc763..1d69eee5dd0 100644 --- a/mysql-test/t/rpl_truncate_7ndb.test +++ b/mysql-test/t/rpl_truncate_7ndb.test @@ -39,6 +39,7 @@ SELECT * FROM t1 ORDER BY a,b; --echo **** On Master **** connection master; DROP TABLE t1; +let SERVER_VERSION=`select version()`; --replace_regex /\/\* xid=[0-9]+ \*\//\/* xid= *\// /table_id: [0-9]+/table_id: #/ --replace_result $SERVER_VERSION SERVER_VERSION SHOW BINLOG EVENTS; diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index e829a71c45a..2c85c6ae643 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -319,7 +319,7 @@ begin declare x int; end| ---error 1332 +--error 1332 create procedure p() begin declare c condition for 1064; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index b3563e21bdd..14c5bb00cee 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -2940,11 +2940,11 @@ begin end| --disable_parsing --replace_regex /table_id: [0-9]+/table_id: #/ -show binlog events; -show storage engines; -show master status; -show slave hosts; -show slave status; +show binlog events| +show storage engines| +show master status| +show slave hosts| +show slave status| --enable_parsing call bug4902()| diff --git a/mysql-test/t/sp_notembedded.test b/mysql-test/t/sp_notembedded.test index 28abf448089..6335ad55606 100644 --- a/mysql-test/t/sp_notembedded.test +++ b/mysql-test/t/sp_notembedded.test @@ -19,11 +19,11 @@ begin show grants for 'root'@'localhost'; end| --disable_parsing -show binlog events; -show storage engines; -show master status; -show slave hosts; -show slave status; +show binlog events| +show storage engines| +show master status| +show slave hosts| +show slave status| --enable_parsing call bug4902()| @@ -110,7 +110,7 @@ flush status| flush query cache| delete from t1| drop procedure bug3583| -drop table t1; +drop table t1| # # BUG#6807: Stored procedure crash if CREATE PROCEDURE ... KILL QUERY diff --git a/mysql-test/t/subselect.test b/mysql-test/t/subselect.test index 7811301a9bc..d357b983703 100644 --- a/mysql-test/t/subselect.test +++ b/mysql-test/t/subselect.test @@ -1728,7 +1728,7 @@ select (select a from t1) = (1,2); select (1,2,3) = (select * from t1); -- error 1241 select (select * from t1) = (1,2,3); -drop table t1 +drop table t1; # # Item_int_with_ref check (BUG#10020) diff --git a/mysql-test/t/wait_timeout.test b/mysql-test/t/wait_timeout.test index dbd792e48d8..bdff72cdc76 100644 --- a/mysql-test/t/wait_timeout.test +++ b/mysql-test/t/wait_timeout.test @@ -55,7 +55,7 @@ select 2; select 3; # Disconnect so that we will not be confused by a future abort from this # connection. -disconnect default +disconnect default; # # Do the same test as above on a TCP connection