1 | /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. |
2 | Copyright (c) 2009, 2017, MariaDB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
16 | |
17 | /* |
18 | mysqltest |
19 | |
20 | Tool used for executing a .test file |
21 | |
22 | See the "MySQL Test framework manual" for more information |
23 | http://dev.mysql.com/doc/mysqltest/en/index.html |
24 | |
25 | Please keep the test framework tools identical in all versions! |
26 | |
27 | Written by: |
28 | Sasha Pachev <sasha@mysql.com> |
29 | Matt Wagner <matt@mysql.com> |
30 | Monty |
31 | Jani |
32 | Holyfoot |
33 | And many others |
34 | */ |
35 | |
36 | #define MTEST_VERSION "3.5" |
37 | |
38 | #include "client_priv.h" |
39 | #include <mysql_version.h> |
40 | #include <mysqld_error.h> |
41 | #include <sql_common.h> |
42 | #include <m_ctype.h> |
43 | #include <my_dir.h> |
44 | #include <hash.h> |
45 | #include <stdarg.h> |
46 | #include <violite.h> |
47 | #define PCRE_STATIC 1 /* Important on Windows */ |
48 | #include "pcreposix.h" /* pcreposix regex library */ |
49 | #ifdef HAVE_SYS_WAIT_H |
50 | #include <sys/wait.h> |
51 | #endif |
52 | #ifdef _WIN32 |
53 | #include <direct.h> |
54 | #endif |
55 | #include <signal.h> |
56 | #include <my_stacktrace.h> |
57 | |
58 | #include <welcome_copyright_notice.h> // ORACLE_WELCOME_COPYRIGHT_NOTICE |
59 | |
60 | #ifdef _WIN32 |
61 | #include <crtdbg.h> |
62 | #define SIGNAL_FMT "exception 0x%x" |
63 | #else |
64 | #define SIGNAL_FMT "signal %d" |
65 | #endif |
66 | |
67 | #include <my_context.h> |
68 | static my_bool non_blocking_api_enabled= 0; |
69 | #if !defined(EMBEDDED_LIBRARY) && !defined(MY_CONTEXT_DISABLE) |
70 | #define WRAP_NONBLOCK_ENABLED non_blocking_api_enabled |
71 | #include "../tests/nonblock-wrappers.h" |
72 | #endif |
73 | |
74 | /* Use cygwin for --exec and --system before 5.0 */ |
75 | #if MYSQL_VERSION_ID < 50000 |
76 | #define USE_CYGWIN |
77 | #endif |
78 | |
79 | #define MAX_VAR_NAME_LENGTH 256 |
80 | #define MAX_COLUMNS 256 |
81 | #define MAX_EMBEDDED_SERVER_ARGS 64 |
82 | #define MAX_DELIMITER_LENGTH 16 |
83 | #define DEFAULT_MAX_CONN 64 |
84 | |
85 | #define DIE_BUFF_SIZE 256*1024 |
86 | |
87 | /* Flags controlling send and reap */ |
88 | #define QUERY_SEND_FLAG 1 |
89 | #define QUERY_REAP_FLAG 2 |
90 | |
91 | #define QUERY_PRINT_ORIGINAL_FLAG 4 |
92 | |
93 | #ifndef HAVE_SETENV |
94 | static int setenv(const char *name, const char *value, int overwrite); |
95 | #endif |
96 | |
97 | C_MODE_START |
98 | static sig_handler signal_handler(int sig); |
99 | static my_bool get_one_option(int optid, const struct my_option *, |
100 | char *argument); |
101 | C_MODE_END |
102 | |
103 | enum { |
104 | OPT_LOG_DIR=OPT_MAX_CLIENT_OPTION, OPT_RESULT_FORMAT_VERSION |
105 | }; |
106 | |
107 | static int record= 0, opt_sleep= -1; |
108 | static char *opt_db= 0, *opt_pass= 0; |
109 | const char *opt_user= 0, *opt_host= 0, *unix_sock= 0, *opt_basedir= "./" ; |
110 | static char *shared_memory_base_name=0; |
111 | const char *opt_logdir= "" ; |
112 | const char *opt_prologue= 0, *opt_charsets_dir; |
113 | static int opt_port= 0; |
114 | static int opt_max_connect_retries; |
115 | static int opt_result_format_version; |
116 | static int opt_max_connections= DEFAULT_MAX_CONN; |
117 | static int error_count= 0; |
118 | static my_bool opt_compress= 0, silent= 0, verbose= 0; |
119 | static my_bool debug_info_flag= 0, debug_check_flag= 0; |
120 | static my_bool tty_password= 0; |
121 | static my_bool opt_mark_progress= 0; |
122 | static my_bool ps_protocol= 0, ps_protocol_enabled= 0; |
123 | static my_bool sp_protocol= 0, sp_protocol_enabled= 0; |
124 | static my_bool view_protocol= 0, view_protocol_enabled= 0; |
125 | static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; |
126 | static my_bool parsing_disabled= 0; |
127 | static my_bool display_result_vertically= FALSE, display_result_lower= FALSE, |
128 | display_metadata= FALSE, display_result_sorted= FALSE; |
129 | static my_bool disable_query_log= 0, disable_result_log= 0; |
130 | static my_bool disable_connect_log= 0; |
131 | static my_bool disable_warnings= 0, disable_column_names= 0; |
132 | static my_bool prepare_warnings_enabled= 0; |
133 | static my_bool disable_info= 1; |
134 | static my_bool abort_on_error= 1, opt_continue_on_error= 0; |
135 | static my_bool server_initialized= 0; |
136 | static my_bool is_windows= 0; |
137 | static char **default_argv; |
138 | static const char *load_default_groups[]= |
139 | { "mysqltest" , "client" , "client-server" , "client-mariadb" , 0 }; |
140 | static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer; |
141 | |
142 | /* Info on properties that can be set with --enable_X and --disable_X */ |
143 | |
144 | struct property { |
145 | my_bool *var; /* Actual variable */ |
146 | my_bool set; /* Has been set for ONE command */ |
147 | my_bool old; /* If set, thus is the old value */ |
148 | my_bool reverse; /* Varible is true if disabled */ |
149 | const char *env_name; /* Env. variable name */ |
150 | }; |
151 | |
152 | static struct property prop_list[] = { |
153 | { &abort_on_error, 0, 1, 0, "$ENABLED_ABORT_ON_ERROR" }, |
154 | { &disable_connect_log, 0, 1, 1, "$ENABLED_CONNECT_LOG" }, |
155 | { &disable_info, 0, 1, 1, "$ENABLED_INFO" }, |
156 | { &display_metadata, 0, 0, 0, "$ENABLED_METADATA" }, |
157 | { &ps_protocol_enabled, 0, 0, 0, "$ENABLED_PS_PROTOCOL" }, |
158 | { &disable_query_log, 0, 0, 1, "$ENABLED_QUERY_LOG" }, |
159 | { &disable_result_log, 0, 0, 1, "$ENABLED_RESULT_LOG" }, |
160 | { &disable_warnings, 0, 0, 1, "$ENABLED_WARNINGS" } |
161 | }; |
162 | |
163 | static my_bool once_property= FALSE; |
164 | |
165 | enum enum_prop { |
166 | P_ABORT= 0, |
167 | P_CONNECT, |
168 | P_INFO, |
169 | P_META, |
170 | P_PS, |
171 | P_QUERY, |
172 | P_RESULT, |
173 | P_WARN, |
174 | P_MAX |
175 | }; |
176 | |
177 | static uint start_lineno= 0; /* Start line of current command */ |
178 | static uint my_end_arg= 0; |
179 | |
180 | /* Number of lines of the result to include in failure report */ |
181 | static uint opt_tail_lines= 0; |
182 | |
183 | static uint opt_connect_timeout= 0; |
184 | static uint opt_wait_for_pos_timeout= 0; |
185 | |
186 | static char delimiter[MAX_DELIMITER_LENGTH]= ";" ; |
187 | static size_t delimiter_length= 1; |
188 | |
189 | static char TMPDIR[FN_REFLEN]; |
190 | static char global_subst_from[200]; |
191 | static char global_subst_to[200]; |
192 | static char *global_subst= NULL; |
193 | static MEM_ROOT require_file_root; |
194 | static const my_bool my_true= 1; |
195 | static const my_bool my_false= 0; |
196 | |
197 | /* Block stack */ |
198 | enum block_cmd { |
199 | cmd_none, |
200 | cmd_if, |
201 | cmd_while |
202 | }; |
203 | |
204 | struct st_block |
205 | { |
206 | int line; /* Start line of block */ |
207 | my_bool ok; /* Should block be executed */ |
208 | enum block_cmd cmd; /* Command owning the block */ |
209 | char delim[MAX_DELIMITER_LENGTH]; /* Delimiter before block */ |
210 | }; |
211 | |
212 | static struct st_block block_stack[32]; |
213 | static struct st_block *cur_block, *block_stack_end; |
214 | |
215 | /* Open file stack */ |
216 | struct st_test_file |
217 | { |
218 | FILE* file; |
219 | char *file_name; |
220 | uint lineno; /* Current line in file */ |
221 | }; |
222 | |
223 | static struct st_test_file file_stack[16]; |
224 | static struct st_test_file* cur_file; |
225 | static struct st_test_file* file_stack_end; |
226 | |
227 | static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ |
228 | |
229 | static const char *embedded_server_groups[]= |
230 | { |
231 | "server" , |
232 | "embedded" , |
233 | "mysqltest_SERVER" , |
234 | NullS |
235 | }; |
236 | |
237 | static int embedded_server_arg_count=0; |
238 | static char *embedded_server_args[MAX_EMBEDDED_SERVER_ARGS]; |
239 | |
240 | /* |
241 | Timer related variables |
242 | See the timer_output() definition for details |
243 | */ |
244 | static char *timer_file = NULL; |
245 | static ulonglong timer_start; |
246 | static void timer_output(void); |
247 | static ulonglong timer_now(void); |
248 | |
249 | |
250 | static ulong connection_retry_sleep= 100000; /* Microseconds */ |
251 | |
252 | static const char *opt_plugin_dir; |
253 | static const char *opt_suite_dir, *opt_overlay_dir; |
254 | static size_t suite_dir_len, overlay_dir_len; |
255 | |
256 | /* Precompiled re's */ |
257 | static regex_t ps_re; /* the query can be run using PS protocol */ |
258 | static regex_t sp_re; /* the query can be run as a SP */ |
259 | static regex_t view_re; /* the query can be run as a view*/ |
260 | |
261 | static void init_re(void); |
262 | static int match_re(regex_t *, char *); |
263 | static void free_re(void); |
264 | |
265 | static char *get_string(char **to_ptr, char **from_ptr, |
266 | struct st_command *command); |
267 | static int replace(DYNAMIC_STRING *ds_str, |
268 | const char *search_str, size_t search_len, |
269 | const char *replace_str, size_t replace_len); |
270 | |
271 | static uint opt_protocol=0; |
272 | |
273 | DYNAMIC_ARRAY q_lines; |
274 | |
275 | #include "sslopt-vars.h" |
276 | |
277 | struct Parser |
278 | { |
279 | int read_lines,current_line; |
280 | } parser; |
281 | |
282 | struct MasterPos |
283 | { |
284 | char file[FN_REFLEN]; |
285 | ulong pos; |
286 | } master_pos; |
287 | |
288 | /* if set, all results are concated and compared against this file */ |
289 | const char *result_file_name= 0; |
290 | |
291 | typedef struct |
292 | { |
293 | char *name; |
294 | size_t name_len; |
295 | char *str_val; |
296 | size_t str_val_len; |
297 | int int_val; |
298 | size_t alloced_len; |
299 | bool int_dirty; /* do not update string if int is updated until first read */ |
300 | bool is_int; |
301 | bool alloced; |
302 | } VAR; |
303 | |
304 | /*Perl/shell-like variable registers */ |
305 | VAR var_reg[10]; |
306 | |
307 | HASH var_hash; |
308 | |
309 | struct st_connection |
310 | { |
311 | MYSQL *mysql; |
312 | /* Used when creating views and sp, to avoid implicit commit */ |
313 | MYSQL* util_mysql; |
314 | char *name; |
315 | size_t name_len; |
316 | MYSQL_STMT* stmt; |
317 | /* Set after send to disallow other queries before reap */ |
318 | my_bool pending; |
319 | |
320 | #ifdef EMBEDDED_LIBRARY |
321 | pthread_t tid; |
322 | const char *cur_query; |
323 | int cur_query_len; |
324 | int command, result; |
325 | pthread_mutex_t query_mutex; |
326 | pthread_cond_t query_cond; |
327 | pthread_mutex_t result_mutex; |
328 | pthread_cond_t result_cond; |
329 | int query_done; |
330 | my_bool has_thread; |
331 | #endif /*EMBEDDED_LIBRARY*/ |
332 | }; |
333 | |
334 | struct st_connection *connections= NULL; |
335 | struct st_connection* cur_con= NULL, *next_con, *connections_end; |
336 | |
337 | /* |
338 | List of commands in mysqltest |
339 | Must match the "command_names" array |
340 | Add new commands before Q_UNKNOWN! |
341 | */ |
342 | enum enum_commands { |
343 | Q_CONNECTION=1, Q_QUERY, |
344 | Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, |
345 | Q_INC, Q_DEC, |
346 | Q_SOURCE, Q_DISCONNECT, |
347 | Q_LET, Q_ECHO, |
348 | Q_WHILE, Q_END_BLOCK, |
349 | Q_SYSTEM, Q_RESULT, |
350 | Q_REQUIRE, Q_SAVE_MASTER_POS, |
351 | Q_SYNC_WITH_MASTER, |
352 | Q_SYNC_SLAVE_WITH_MASTER, |
353 | Q_ERROR, |
354 | Q_SEND, Q_REAP, |
355 | Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN, |
356 | Q_PING, Q_EVAL, |
357 | Q_EVALP, |
358 | Q_EVAL_RESULT, |
359 | Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG, |
360 | Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG, |
361 | Q_ENABLE_CONNECT_LOG, Q_DISABLE_CONNECT_LOG, |
362 | Q_WAIT_FOR_SLAVE_TO_STOP, |
363 | Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS, |
364 | Q_ENABLE_INFO, Q_DISABLE_INFO, |
365 | Q_ENABLE_METADATA, Q_DISABLE_METADATA, |
366 | Q_ENABLE_COLUMN_NAMES, Q_DISABLE_COLUMN_NAMES, |
367 | Q_EXEC, Q_DELIMITER, |
368 | Q_DISABLE_ABORT_ON_ERROR, Q_ENABLE_ABORT_ON_ERROR, |
369 | Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, |
370 | Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_SORTED_RESULT, |
371 | Q_LOWERCASE, |
372 | Q_START_TIMER, Q_END_TIMER, |
373 | Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, |
374 | Q_ENABLE_NON_BLOCKING_API, Q_DISABLE_NON_BLOCKING_API, |
375 | Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, |
376 | Q_IF, |
377 | Q_DISABLE_PARSING, Q_ENABLE_PARSING, |
378 | Q_REPLACE_REGEX, Q_REMOVE_FILE, Q_FILE_EXIST, |
379 | Q_WRITE_FILE, Q_COPY_FILE, Q_PERL, Q_DIE, Q_EXIT, Q_SKIP, |
380 | Q_CHMOD_FILE, Q_APPEND_FILE, Q_CAT_FILE, Q_DIFF_FILES, |
381 | Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, |
382 | Q_LIST_FILES, Q_LIST_FILES_WRITE_FILE, Q_LIST_FILES_APPEND_FILE, |
383 | Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, |
384 | Q_RESULT_FORMAT_VERSION, |
385 | Q_MOVE_FILE, Q_REMOVE_FILES_WILDCARD, Q_SEND_EVAL, |
386 | Q_ENABLE_PREPARE_WARNINGS, Q_DISABLE_PREPARE_WARNINGS, |
387 | Q_UNKNOWN, /* Unknown command. */ |
388 | , /* Comments, ignored. */ |
389 | Q_COMMENT_WITH_COMMAND, |
390 | Q_EMPTY_LINE |
391 | }; |
392 | |
393 | |
394 | const char *command_names[]= |
395 | { |
396 | "connection" , |
397 | "query" , |
398 | "connect" , |
399 | "sleep" , |
400 | "real_sleep" , |
401 | "inc" , |
402 | "dec" , |
403 | "source" , |
404 | "disconnect" , |
405 | "let" , |
406 | "echo" , |
407 | "while" , |
408 | "end" , |
409 | "system" , |
410 | "result" , |
411 | "require" , |
412 | "save_master_pos" , |
413 | "sync_with_master" , |
414 | "sync_slave_with_master" , |
415 | "error" , |
416 | "send" , |
417 | "reap" , |
418 | "dirty_close" , |
419 | "replace_result" , |
420 | "replace_column" , |
421 | "ping" , |
422 | "eval" , |
423 | "evalp" , |
424 | "eval_result" , |
425 | /* Enable/disable that the _query_ is logged to result file */ |
426 | "enable_query_log" , |
427 | "disable_query_log" , |
428 | /* Enable/disable that the _result_ from a query is logged to result file */ |
429 | "enable_result_log" , |
430 | "disable_result_log" , |
431 | "enable_connect_log" , |
432 | "disable_connect_log" , |
433 | "wait_for_slave_to_stop" , |
434 | "enable_warnings" , |
435 | "disable_warnings" , |
436 | "enable_info" , |
437 | "disable_info" , |
438 | "enable_metadata" , |
439 | "disable_metadata" , |
440 | "enable_column_names" , |
441 | "disable_column_names" , |
442 | "exec" , |
443 | "delimiter" , |
444 | "disable_abort_on_error" , |
445 | "enable_abort_on_error" , |
446 | "vertical_results" , |
447 | "horizontal_results" , |
448 | "query_vertical" , |
449 | "query_horizontal" , |
450 | "sorted_result" , |
451 | "lowercase_result" , |
452 | "start_timer" , |
453 | "end_timer" , |
454 | "character_set" , |
455 | "disable_ps_protocol" , |
456 | "enable_ps_protocol" , |
457 | "enable_non_blocking_api" , |
458 | "disable_non_blocking_api" , |
459 | "disable_reconnect" , |
460 | "enable_reconnect" , |
461 | "if" , |
462 | "disable_parsing" , |
463 | "enable_parsing" , |
464 | "replace_regex" , |
465 | "remove_file" , |
466 | "file_exists" , |
467 | "write_file" , |
468 | "copy_file" , |
469 | "perl" , |
470 | "die" , |
471 | |
472 | /* Don't execute any more commands, compare result */ |
473 | "exit" , |
474 | "skip" , |
475 | "chmod" , |
476 | "append_file" , |
477 | "cat_file" , |
478 | "diff_files" , |
479 | "send_quit" , |
480 | "change_user" , |
481 | "mkdir" , |
482 | "rmdir" , |
483 | "list_files" , |
484 | "list_files_write_file" , |
485 | "list_files_append_file" , |
486 | "send_shutdown" , |
487 | "shutdown_server" , |
488 | "result_format" , |
489 | "move_file" , |
490 | "remove_files_wildcard" , |
491 | "send_eval" , |
492 | "enable_prepare_warnings" , |
493 | "disable_prepare_warnings" , |
494 | |
495 | 0 |
496 | }; |
497 | |
498 | |
499 | /* |
500 | The list of error codes to --error are stored in an internal array of |
501 | structs. This struct can hold numeric SQL error codes, error names or |
502 | SQLSTATE codes as strings. The element next to the last active element |
503 | in the list is set to type ERR_EMPTY. When an SQL statement returns an |
504 | error, we use this list to check if this is an expected error. |
505 | */ |
506 | enum match_err_type |
507 | { |
508 | ERR_EMPTY= 0, |
509 | ERR_ERRNO, |
510 | ERR_SQLSTATE |
511 | }; |
512 | |
513 | struct st_match_err |
514 | { |
515 | enum match_err_type type; |
516 | union |
517 | { |
518 | uint errnum; |
519 | char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ |
520 | } code; |
521 | }; |
522 | |
523 | struct st_expected_errors |
524 | { |
525 | struct st_match_err err[12]; |
526 | uint count; |
527 | }; |
528 | static struct st_expected_errors saved_expected_errors; |
529 | |
530 | struct st_command |
531 | { |
532 | char *query, *query_buf,*first_argument,*last_argument,*end; |
533 | DYNAMIC_STRING content; |
534 | DYNAMIC_STRING eval_query; |
535 | int first_word_len, query_len; |
536 | my_bool abort_on_error, used_replace; |
537 | struct st_expected_errors expected_errors; |
538 | char *require_file; |
539 | enum enum_commands type; |
540 | }; |
541 | |
542 | TYPELIB command_typelib= {array_elements(command_names),"" , |
543 | command_names, 0}; |
544 | |
545 | DYNAMIC_STRING ds_res; |
546 | /* Points to ds_warning in run_query, so it can be freed */ |
547 | DYNAMIC_STRING *ds_warn= 0; |
548 | struct st_command *curr_command= 0; |
549 | |
550 | char builtin_echo[FN_REFLEN]; |
551 | |
552 | struct st_replace_regex |
553 | { |
554 | DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */ |
555 | |
556 | /* |
557 | Temporary storage areas for substitutions. To reduce unnessary copying |
558 | and memory freeing/allocation, we pre-allocate two buffers, and alternate |
559 | their use, one for input/one for output, the roles changing on the next |
560 | st_regex substition. At the end of substitutions buf points to the |
561 | one containing the final result. |
562 | */ |
563 | char* buf; |
564 | char* even_buf; |
565 | char* odd_buf; |
566 | int even_buf_len; |
567 | int odd_buf_len; |
568 | }; |
569 | |
570 | struct st_replace_regex *glob_replace_regex= 0; |
571 | |
572 | struct st_replace; |
573 | struct st_replace *glob_replace= 0; |
574 | void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, |
575 | const char *from); |
576 | |
577 | ATTRIBUTE_NORETURN |
578 | static void cleanup_and_exit(int exit_code); |
579 | |
580 | ATTRIBUTE_NORETURN |
581 | void really_die(const char *msg); |
582 | void report_or_die(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); |
583 | ATTRIBUTE_NORETURN ATTRIBUTE_FORMAT(printf, 1, 2) |
584 | void die(const char *fmt, ...); |
585 | static void make_error_message(char *buf, size_t len, const char *fmt, va_list args); |
586 | ATTRIBUTE_NORETURN ATTRIBUTE_FORMAT(printf, 1, 2) |
587 | void abort_not_supported_test(const char *fmt, ...); |
588 | void verbose_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); |
589 | void log_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); |
590 | |
591 | VAR* var_from_env(const char *, const char *); |
592 | VAR* var_init(VAR* v, const char *name, size_t name_len, const char *val, size_t val_len); |
593 | VAR* var_get(const char *var_name, const char** var_name_end, |
594 | my_bool raw, my_bool ignore_not_existing); |
595 | void eval_expr(VAR* v, const char *p, const char** p_end, |
596 | bool open_end=false, bool do_eval=true); |
597 | my_bool match_delimiter(int c, const char *delim, size_t length); |
598 | void dump_result_to_reject_file(char *buf, int size); |
599 | void dump_warning_messages(); |
600 | |
601 | void do_eval(DYNAMIC_STRING *query_eval, const char *query, |
602 | const char *query_end, my_bool pass_through_escape_chars); |
603 | void str_to_file(const char *fname, char *str, size_t size); |
604 | void str_to_file2(const char *fname, char *str, size_t size, my_bool append); |
605 | |
606 | void fix_win_paths(char *val, size_t len); |
607 | const char *get_errname_from_code (uint error_code); |
608 | int multi_reg_replace(struct st_replace_regex* r,char* val); |
609 | |
610 | #ifdef _WIN32 |
611 | void free_tmp_sh_file(); |
612 | void free_win_path_patterns(); |
613 | #endif |
614 | |
615 | |
616 | /* For replace_column */ |
617 | static char *replace_column[MAX_COLUMNS]; |
618 | static uint max_replace_column= 0; |
619 | void do_get_replace_column(struct st_command*); |
620 | void free_replace_column(); |
621 | |
622 | /* For replace */ |
623 | void do_get_replace(struct st_command *command); |
624 | void free_replace(); |
625 | |
626 | /* For replace_regex */ |
627 | void do_get_replace_regex(struct st_command *command); |
628 | void free_replace_regex(); |
629 | |
630 | /* Used by sleep */ |
631 | void check_eol_junk_line(const char *eol); |
632 | |
633 | void free_all_replace(){ |
634 | free_replace(); |
635 | free_replace_regex(); |
636 | free_replace_column(); |
637 | } |
638 | |
639 | void var_set_int(const char* name, int value); |
640 | |
641 | |
642 | class LogFile { |
643 | FILE* m_file; |
644 | char m_file_name[FN_REFLEN]; |
645 | size_t m_bytes_written; |
646 | public: |
647 | LogFile() : m_file(NULL), m_bytes_written(0) { |
648 | bzero(m_file_name, sizeof(m_file_name)); |
649 | } |
650 | |
651 | ~LogFile() { |
652 | close(); |
653 | } |
654 | |
655 | const char* file_name() const { return m_file_name; } |
656 | size_t bytes_written() const { return m_bytes_written; } |
657 | |
658 | void open(const char* dir, const char* name, const char* ext) |
659 | { |
660 | DBUG_ENTER("LogFile::open" ); |
661 | DBUG_PRINT("enter" , ("dir: '%s', name: '%s'" , dir, name)); |
662 | if (!name) |
663 | { |
664 | m_file= stdout; |
665 | DBUG_VOID_RETURN; |
666 | } |
667 | |
668 | fn_format(m_file_name, name, dir, ext, |
669 | *dir ? MY_REPLACE_DIR | MY_REPLACE_EXT : |
670 | MY_REPLACE_EXT); |
671 | |
672 | DBUG_PRINT("info" , ("file_name: %s" , m_file_name)); |
673 | |
674 | if ((m_file= fopen(m_file_name, "wb+" )) == NULL) |
675 | die("Failed to open log file %s, errno: %d" , m_file_name, errno); |
676 | |
677 | DBUG_VOID_RETURN; |
678 | } |
679 | |
680 | void close() |
681 | { |
682 | if (m_file) { |
683 | if (m_file != stdout) |
684 | fclose(m_file); |
685 | else |
686 | fflush(m_file); |
687 | } |
688 | m_file= NULL; |
689 | } |
690 | |
691 | void flush() |
692 | { |
693 | if (m_file && m_file != stdout) |
694 | { |
695 | if (fflush(m_file)) |
696 | die("Failed to flush '%s', errno: %d" , m_file_name, errno); |
697 | } |
698 | } |
699 | |
700 | void write(DYNAMIC_STRING* ds) |
701 | { |
702 | DBUG_ENTER("LogFile::write" ); |
703 | DBUG_PRINT("enter" , ("length: %u" , (uint) ds->length)); |
704 | |
705 | DBUG_ASSERT(m_file); |
706 | |
707 | if (ds->length == 0) |
708 | DBUG_VOID_RETURN; |
709 | DBUG_ASSERT(ds->str); |
710 | |
711 | #ifdef EXTRA_DEBUG |
712 | DBUG_PRINT("extra" , ("str: %*s" , (int) ds->length, ds->str)); |
713 | #endif |
714 | |
715 | if (fwrite(ds->str, 1, ds->length, m_file) != ds->length) |
716 | die("Failed to write %lu bytes to '%s', errno: %d" , |
717 | (unsigned long)ds->length, m_file_name, errno); |
718 | m_bytes_written+= ds->length; |
719 | DBUG_VOID_RETURN; |
720 | } |
721 | |
722 | void show_tail(uint lines) { |
723 | DBUG_ENTER("LogFile::show_tail" ); |
724 | |
725 | if (!m_file || m_file == stdout) |
726 | DBUG_VOID_RETURN; |
727 | |
728 | if (lines == 0) |
729 | DBUG_VOID_RETURN; |
730 | lines++; |
731 | |
732 | int show_offset= 0; |
733 | char buf[256+1]; /* + zero termination for DBUG_PRINT */ |
734 | size_t bytes; |
735 | bool found_bof= false; |
736 | |
737 | /* Search backward in file until "lines" newline has been found */ |
738 | while (lines && !found_bof) |
739 | { |
740 | show_offset-= sizeof(buf)-1; |
741 | while(fseek(m_file, show_offset, SEEK_END) != 0 && show_offset < 0) |
742 | { |
743 | found_bof= true; |
744 | // Seeking before start of file |
745 | show_offset++; |
746 | } |
747 | |
748 | if ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) <= 0) |
749 | { |
750 | // ferror=0 will happen here if no queries executed yet |
751 | if (ferror(m_file)) |
752 | fprintf(stderr, |
753 | "Failed to read from '%s', errno: %d, feof:%d, ferror:%d\n" , |
754 | m_file_name, errno, feof(m_file), ferror(m_file)); |
755 | DBUG_VOID_RETURN; |
756 | } |
757 | |
758 | DBUG_PRINT("info" , ("Read %zu bytes from file, buf: %.*s" , |
759 | bytes, (int)bytes, buf)); |
760 | |
761 | char* show_from= buf + bytes; |
762 | while(show_from > buf && lines > 0 ) |
763 | { |
764 | show_from--; |
765 | if (*show_from == '\n') |
766 | lines--; |
767 | } |
768 | if (show_from != buf) |
769 | { |
770 | // The last new line was found in this buf, adjust offset |
771 | show_offset+= (int)(show_from - buf) + 1; |
772 | DBUG_PRINT("info" , ("adjusted offset to %d" , show_offset)); |
773 | } |
774 | DBUG_PRINT("info" , ("show_offset: %d" , show_offset)); |
775 | } |
776 | |
777 | fprintf(stderr, "\nThe result from queries just before the failure was:\n" ); |
778 | |
779 | DBUG_PRINT("info" , ("show_offset: %d" , show_offset)); |
780 | if (!lines) |
781 | { |
782 | fprintf(stderr, "< snip >\n" ); |
783 | |
784 | if (fseek(m_file, show_offset, SEEK_END) != 0) |
785 | { |
786 | fprintf(stderr, "Failed to seek to position %d in '%s', errno: %d" , |
787 | show_offset, m_file_name, errno); |
788 | DBUG_VOID_RETURN; |
789 | } |
790 | |
791 | } |
792 | else { |
793 | DBUG_PRINT("info" , ("Showing the whole file" )); |
794 | if (fseek(m_file, 0L, SEEK_SET) != 0) |
795 | { |
796 | fprintf(stderr, "Failed to seek to pos 0 in '%s', errno: %d" , |
797 | m_file_name, errno); |
798 | DBUG_VOID_RETURN; |
799 | } |
800 | } |
801 | |
802 | while ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) > 0) |
803 | if (bytes != fwrite(buf, 1, bytes, stderr)) |
804 | die("Failed to write to '%s', errno: %d" , |
805 | m_file_name, errno); |
806 | |
807 | if (!lines) |
808 | { |
809 | fprintf(stderr, |
810 | "\nMore results from queries before failure can be found in %s\n" , |
811 | m_file_name); |
812 | } |
813 | fflush(stderr); |
814 | |
815 | DBUG_VOID_RETURN; |
816 | } |
817 | }; |
818 | |
819 | LogFile log_file; |
820 | LogFile progress_file; |
821 | |
822 | void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len); |
823 | void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); |
824 | void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val); |
825 | void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING* ds_input, |
826 | bool ); |
827 | |
828 | static int match_expected_error(struct st_command *command, |
829 | unsigned int err_errno, |
830 | const char *err_sqlstate); |
831 | void handle_error(struct st_command*, |
832 | unsigned int err_errno, const char *err_error, |
833 | const char *err_sqlstate, DYNAMIC_STRING *ds); |
834 | void handle_no_error(struct st_command*); |
835 | void revert_properties(); |
836 | |
837 | static void handle_no_active_connection(struct st_command* command, |
838 | struct st_connection *cn, DYNAMIC_STRING *ds); |
839 | |
840 | |
841 | /* Wrapper for fgets.Strips \r off newlines on Windows. |
842 | Should be used with together with my_popen(). |
843 | */ |
844 | static char *my_fgets(char * s, int n, FILE * stream, int *len) |
845 | { |
846 | char *buf = fgets(s, n, stream); |
847 | if (!buf) |
848 | { |
849 | *len= 0; |
850 | return buf; |
851 | } |
852 | |
853 | *len = (int)strlen(buf); |
854 | #ifdef _WIN32 |
855 | /* Strip '\r' off newlines. */ |
856 | if (*len > 1 && buf[*len - 2] == '\r' && buf[*len - 1] == '\n') |
857 | { |
858 | buf[*len - 2]= '\n'; |
859 | buf[*len - 1]= 0; |
860 | (*len)--; |
861 | } |
862 | #endif |
863 | return buf; |
864 | } |
865 | |
866 | /* |
867 | Wrapper for popen(). |
868 | On Windows, uses binary mode to workaround |
869 | C runtime bug mentioned in MDEV-9409 |
870 | */ |
871 | static FILE* my_popen(const char *cmd, const char *mode) |
872 | { |
873 | FILE *f= popen(cmd, mode); |
874 | #ifdef _WIN32 |
875 | if (f) |
876 | _setmode(fileno(f), O_BINARY); |
877 | #endif |
878 | return f; |
879 | } |
880 | |
881 | #ifdef EMBEDDED_LIBRARY |
882 | |
883 | #define EMB_SEND_QUERY 1 |
884 | #define EMB_READ_QUERY_RESULT 2 |
885 | #define EMB_END_CONNECTION 3 |
886 | #define EMB_PREPARE_STMT 4 |
887 | #define EMB_EXECUTE_STMT 5 |
888 | #define EMB_CLOSE_STMT 6 |
889 | |
890 | /* workaround for MySQL BUG#57491 */ |
891 | #undef MY_WME |
892 | #define MY_WME 0 |
893 | |
894 | /* attributes of the query thread */ |
895 | pthread_attr_t cn_thd_attrib; |
896 | |
897 | |
898 | /* |
899 | This procedure represents the connection and actually |
900 | runs queries when in the EMBEDDED-SERVER mode. |
901 | The run_query_normal() just sends request for running |
902 | mysql_send_query and mysql_read_query_result() here. |
903 | */ |
904 | |
905 | pthread_handler_t connection_thread(void *arg) |
906 | { |
907 | struct st_connection *cn= (struct st_connection*)arg; |
908 | |
909 | mysql_thread_init(); |
910 | while (cn->command != EMB_END_CONNECTION) |
911 | { |
912 | if (!cn->command) |
913 | { |
914 | pthread_mutex_lock(&cn->query_mutex); |
915 | while (!cn->command) |
916 | pthread_cond_wait(&cn->query_cond, &cn->query_mutex); |
917 | pthread_mutex_unlock(&cn->query_mutex); |
918 | } |
919 | switch (cn->command) |
920 | { |
921 | case EMB_END_CONNECTION: |
922 | goto end_thread; |
923 | case EMB_SEND_QUERY: |
924 | cn->result= mysql_send_query(cn->mysql, |
925 | cn->cur_query, cn->cur_query_len); |
926 | break; |
927 | case EMB_READ_QUERY_RESULT: |
928 | cn->result= mysql_read_query_result(cn->mysql); |
929 | break; |
930 | case EMB_PREPARE_STMT: |
931 | cn->result= mysql_stmt_prepare(cn->stmt, |
932 | cn->cur_query, cn->cur_query_len); |
933 | break; |
934 | case EMB_EXECUTE_STMT: |
935 | cn->result= mysql_stmt_execute(cn->stmt); |
936 | break; |
937 | case EMB_CLOSE_STMT: |
938 | cn->result= mysql_stmt_close(cn->stmt); |
939 | break; |
940 | default: |
941 | DBUG_ASSERT(0); |
942 | } |
943 | cn->command= 0; |
944 | pthread_mutex_lock(&cn->result_mutex); |
945 | cn->query_done= 1; |
946 | pthread_cond_signal(&cn->result_cond); |
947 | pthread_mutex_unlock(&cn->result_mutex); |
948 | } |
949 | |
950 | end_thread: |
951 | cn->query_done= 1; |
952 | mysql_thread_end(); |
953 | pthread_exit(0); |
954 | return 0; |
955 | } |
956 | |
957 | static void wait_query_thread_done(struct st_connection *con) |
958 | { |
959 | DBUG_ASSERT(con->has_thread); |
960 | if (!con->query_done) |
961 | { |
962 | pthread_mutex_lock(&con->result_mutex); |
963 | while (!con->query_done) |
964 | pthread_cond_wait(&con->result_cond, &con->result_mutex); |
965 | pthread_mutex_unlock(&con->result_mutex); |
966 | } |
967 | } |
968 | |
969 | |
970 | static void signal_connection_thd(struct st_connection *cn, int command) |
971 | { |
972 | DBUG_ASSERT(cn->has_thread); |
973 | cn->query_done= 0; |
974 | cn->command= command; |
975 | pthread_mutex_lock(&cn->query_mutex); |
976 | pthread_cond_signal(&cn->query_cond); |
977 | pthread_mutex_unlock(&cn->query_mutex); |
978 | } |
979 | |
980 | |
981 | /* |
982 | Sometimes we try to execute queries when the connection is closed. |
983 | It's done to make sure it was closed completely. |
984 | So that if our connection is closed (cn->has_thread == 0), we just return |
985 | the mysql_send_query() result which is an error in this case. |
986 | */ |
987 | |
988 | static int do_send_query(struct st_connection *cn, const char *q, int q_len) |
989 | { |
990 | if (!cn->has_thread) |
991 | return mysql_send_query(cn->mysql, q, q_len); |
992 | cn->cur_query= q; |
993 | cn->cur_query_len= q_len; |
994 | signal_connection_thd(cn, EMB_SEND_QUERY); |
995 | return 0; |
996 | } |
997 | |
998 | static int do_read_query_result(struct st_connection *cn) |
999 | { |
1000 | DBUG_ASSERT(cn->has_thread); |
1001 | wait_query_thread_done(cn); |
1002 | if (cn->result) |
1003 | goto exit_func; |
1004 | |
1005 | signal_connection_thd(cn, EMB_READ_QUERY_RESULT); |
1006 | wait_query_thread_done(cn); |
1007 | |
1008 | exit_func: |
1009 | return cn->result; |
1010 | } |
1011 | |
1012 | |
1013 | static int do_stmt_prepare(struct st_connection *cn, const char *q, int q_len) |
1014 | { |
1015 | /* The cn->stmt is already set. */ |
1016 | if (!cn->has_thread) |
1017 | return mysql_stmt_prepare(cn->stmt, q, q_len); |
1018 | cn->cur_query= q; |
1019 | cn->cur_query_len= q_len; |
1020 | signal_connection_thd(cn, EMB_PREPARE_STMT); |
1021 | wait_query_thread_done(cn); |
1022 | return cn->result; |
1023 | } |
1024 | |
1025 | |
1026 | static int do_stmt_execute(struct st_connection *cn) |
1027 | { |
1028 | /* The cn->stmt is already set. */ |
1029 | if (!cn->has_thread) |
1030 | return mysql_stmt_execute(cn->stmt); |
1031 | signal_connection_thd(cn, EMB_EXECUTE_STMT); |
1032 | wait_query_thread_done(cn); |
1033 | return cn->result; |
1034 | } |
1035 | |
1036 | |
1037 | static int do_stmt_close(struct st_connection *cn) |
1038 | { |
1039 | /* The cn->stmt is already set. */ |
1040 | if (!cn->has_thread) |
1041 | return mysql_stmt_close(cn->stmt); |
1042 | signal_connection_thd(cn, EMB_CLOSE_STMT); |
1043 | wait_query_thread_done(cn); |
1044 | return cn->result; |
1045 | } |
1046 | |
1047 | |
1048 | static void emb_close_connection(struct st_connection *cn) |
1049 | { |
1050 | if (!cn->has_thread) |
1051 | return; |
1052 | wait_query_thread_done(cn); |
1053 | signal_connection_thd(cn, EMB_END_CONNECTION); |
1054 | pthread_join(cn->tid, NULL); |
1055 | cn->has_thread= FALSE; |
1056 | pthread_mutex_destroy(&cn->query_mutex); |
1057 | pthread_cond_destroy(&cn->query_cond); |
1058 | pthread_mutex_destroy(&cn->result_mutex); |
1059 | pthread_cond_destroy(&cn->result_cond); |
1060 | } |
1061 | |
1062 | |
1063 | static void init_connection_thd(struct st_connection *cn) |
1064 | { |
1065 | cn->query_done= 1; |
1066 | cn->command= 0; |
1067 | if (pthread_mutex_init(&cn->query_mutex, NULL) || |
1068 | pthread_cond_init(&cn->query_cond, NULL) || |
1069 | pthread_mutex_init(&cn->result_mutex, NULL) || |
1070 | pthread_cond_init(&cn->result_cond, NULL) || |
1071 | pthread_create(&cn->tid, &cn_thd_attrib, connection_thread, (void*)cn)) |
1072 | die("Error in the thread library" ); |
1073 | cn->has_thread=TRUE; |
1074 | } |
1075 | |
1076 | #else /* ! EMBEDDED_LIBRARY*/ |
1077 | |
1078 | #define init_connection_thd(X) do { } while(0) |
1079 | #define do_send_query(cn,q,q_len) mysql_send_query(cn->mysql, q, (ulong)q_len) |
1080 | #define do_read_query_result(cn) mysql_read_query_result(cn->mysql) |
1081 | #define do_stmt_prepare(cn, q, q_len) mysql_stmt_prepare(cn->stmt, q, (ulong)q_len) |
1082 | #define do_stmt_execute(cn) mysql_stmt_execute(cn->stmt) |
1083 | #define do_stmt_close(cn) mysql_stmt_close(cn->stmt) |
1084 | |
1085 | #endif /*EMBEDDED_LIBRARY*/ |
1086 | |
1087 | void do_eval(DYNAMIC_STRING *query_eval, const char *query, |
1088 | const char *query_end, my_bool pass_through_escape_chars) |
1089 | { |
1090 | const char *p; |
1091 | char c, next_c; |
1092 | int escaped = 0; |
1093 | VAR *v; |
1094 | DBUG_ENTER("do_eval" ); |
1095 | |
1096 | for (p= query; (c= *p) && p < query_end; ++p) |
1097 | { |
1098 | switch(c) { |
1099 | case '$': |
1100 | if (escaped) |
1101 | { |
1102 | escaped= 0; |
1103 | dynstr_append_mem(query_eval, p, 1); |
1104 | } |
1105 | else |
1106 | { |
1107 | if (!(v= var_get(p, &p, 0, 0))) |
1108 | { |
1109 | report_or_die( "Bad variable in eval" ); |
1110 | DBUG_VOID_RETURN; |
1111 | } |
1112 | dynstr_append_mem(query_eval, v->str_val, v->str_val_len); |
1113 | } |
1114 | break; |
1115 | case '\\': |
1116 | next_c= *(p+1); |
1117 | if (escaped) |
1118 | { |
1119 | escaped= 0; |
1120 | dynstr_append_mem(query_eval, p, 1); |
1121 | } |
1122 | else if (next_c == '\\' || next_c == '$' || next_c == '"') |
1123 | { |
1124 | /* Set escaped only if next char is \, " or $ */ |
1125 | escaped= 1; |
1126 | |
1127 | if (pass_through_escape_chars) |
1128 | { |
1129 | /* The escape char should be added to the output string. */ |
1130 | dynstr_append_mem(query_eval, p, 1); |
1131 | } |
1132 | } |
1133 | else |
1134 | dynstr_append_mem(query_eval, p, 1); |
1135 | break; |
1136 | default: |
1137 | escaped= 0; |
1138 | dynstr_append_mem(query_eval, p, 1); |
1139 | break; |
1140 | } |
1141 | } |
1142 | fix_win_paths(query_eval->str, query_eval->length); |
1143 | DBUG_VOID_RETURN; |
1144 | } |
1145 | |
1146 | |
1147 | /* |
1148 | Run query and dump the result to stderr in vertical format |
1149 | |
1150 | NOTE! This function should be safe to call when an error |
1151 | has occurred and thus any further errors will be ignored (although logged) |
1152 | |
1153 | SYNOPSIS |
1154 | show_query |
1155 | mysql - connection to use |
1156 | query - query to run |
1157 | |
1158 | */ |
1159 | |
1160 | static void show_query(MYSQL* mysql, const char* query) |
1161 | { |
1162 | MYSQL_RES* res; |
1163 | DBUG_ENTER("show_query" ); |
1164 | |
1165 | if (!mysql) |
1166 | DBUG_VOID_RETURN; |
1167 | |
1168 | if (mysql_query(mysql, query)) |
1169 | { |
1170 | log_msg("Error running query '%s': %d %s" , |
1171 | query, mysql_errno(mysql), mysql_error(mysql)); |
1172 | DBUG_VOID_RETURN; |
1173 | } |
1174 | |
1175 | if ((res= mysql_store_result(mysql)) == NULL) |
1176 | { |
1177 | /* No result set returned */ |
1178 | DBUG_VOID_RETURN; |
1179 | } |
1180 | |
1181 | { |
1182 | MYSQL_ROW row; |
1183 | unsigned int i; |
1184 | unsigned int row_num= 0; |
1185 | unsigned int num_fields= mysql_num_fields(res); |
1186 | MYSQL_FIELD *fields= mysql_fetch_fields(res); |
1187 | |
1188 | fprintf(stderr, "=== %s ===\n" , query); |
1189 | while ((row= mysql_fetch_row(res))) |
1190 | { |
1191 | unsigned long *lengths= mysql_fetch_lengths(res); |
1192 | row_num++; |
1193 | |
1194 | fprintf(stderr, "---- %d. ----\n" , row_num); |
1195 | for(i= 0; i < num_fields; i++) |
1196 | { |
1197 | fprintf(stderr, "%s\t%.*s\n" , |
1198 | fields[i].name, |
1199 | (int)lengths[i], row[i] ? row[i] : "NULL" ); |
1200 | } |
1201 | } |
1202 | for (i= 0; i < strlen(query)+8; i++) |
1203 | fprintf(stderr, "=" ); |
1204 | fprintf(stderr, "\n\n" ); |
1205 | } |
1206 | mysql_free_result(res); |
1207 | |
1208 | DBUG_VOID_RETURN; |
1209 | } |
1210 | |
1211 | |
1212 | /* |
1213 | Show any warnings just before the error. Since the last error |
1214 | is added to the warning stack, only print @@warning_count-1 warnings. |
1215 | |
1216 | NOTE! This function should be safe to call when an error |
1217 | has occurred and this any further errors will be ignored(although logged) |
1218 | |
1219 | SYNOPSIS |
1220 | show_warnings_before_error |
1221 | mysql - connection to use |
1222 | |
1223 | */ |
1224 | |
1225 | static void show_warnings_before_error(MYSQL* mysql) |
1226 | { |
1227 | MYSQL_RES* res; |
1228 | const char* query= "SHOW WARNINGS" ; |
1229 | DBUG_ENTER("show_warnings_before_error" ); |
1230 | |
1231 | if (!mysql) |
1232 | DBUG_VOID_RETURN; |
1233 | |
1234 | if (mysql_query(mysql, query)) |
1235 | { |
1236 | log_msg("Error running query '%s': %d %s" , |
1237 | query, mysql_errno(mysql), mysql_error(mysql)); |
1238 | DBUG_VOID_RETURN; |
1239 | } |
1240 | |
1241 | if ((res= mysql_store_result(mysql)) == NULL) |
1242 | { |
1243 | /* No result set returned */ |
1244 | DBUG_VOID_RETURN; |
1245 | } |
1246 | |
1247 | if (mysql_num_rows(res) <= 1) |
1248 | { |
1249 | /* Don't display the last row, it's "last error" */ |
1250 | } |
1251 | else |
1252 | { |
1253 | MYSQL_ROW row; |
1254 | unsigned int row_num= 0; |
1255 | unsigned int num_fields= mysql_num_fields(res); |
1256 | |
1257 | fprintf(stderr, "\nWarnings from just before the error:\n" ); |
1258 | while ((row= mysql_fetch_row(res))) |
1259 | { |
1260 | unsigned int i; |
1261 | unsigned long *lengths= mysql_fetch_lengths(res); |
1262 | |
1263 | if (++row_num >= mysql_num_rows(res)) |
1264 | { |
1265 | /* Don't display the last row, it's "last error" */ |
1266 | break; |
1267 | } |
1268 | |
1269 | for(i= 0; i < num_fields; i++) |
1270 | { |
1271 | fprintf(stderr, "%.*s " , (int)lengths[i], |
1272 | row[i] ? row[i] : "NULL" ); |
1273 | } |
1274 | fprintf(stderr, "\n" ); |
1275 | } |
1276 | } |
1277 | mysql_free_result(res); |
1278 | |
1279 | DBUG_VOID_RETURN; |
1280 | } |
1281 | |
1282 | |
1283 | enum arg_type |
1284 | { |
1285 | ARG_STRING, |
1286 | ARG_REST |
1287 | }; |
1288 | |
1289 | struct command_arg { |
1290 | const char *argname; /* Name of argument */ |
1291 | enum arg_type type; /* Type of argument */ |
1292 | my_bool required; /* Argument required */ |
1293 | DYNAMIC_STRING *ds; /* Storage for argument */ |
1294 | const char *description; /* Description of the argument */ |
1295 | }; |
1296 | |
1297 | |
1298 | void check_command_args(struct st_command *command, |
1299 | const char *arguments, |
1300 | const struct command_arg *args, |
1301 | int num_args, const char delimiter_arg) |
1302 | { |
1303 | int i; |
1304 | const char *ptr= arguments; |
1305 | const char *start; |
1306 | DBUG_ENTER("check_command_args" ); |
1307 | DBUG_PRINT("enter" , ("num_args: %d" , num_args)); |
1308 | |
1309 | for (i= 0; i < num_args; i++) |
1310 | { |
1311 | const struct command_arg *arg= &args[i]; |
1312 | char delimiter; |
1313 | |
1314 | switch (arg->type) { |
1315 | /* A string */ |
1316 | case ARG_STRING: |
1317 | /* Skip leading spaces */ |
1318 | while (*ptr && *ptr == ' ') |
1319 | ptr++; |
1320 | start= ptr; |
1321 | delimiter = delimiter_arg; |
1322 | /* If start of arg is ' ` or " search to matching quote end instead */ |
1323 | if (*ptr && strchr ("'`\"" , *ptr)) |
1324 | { |
1325 | delimiter= *ptr; |
1326 | start= ++ptr; |
1327 | } |
1328 | /* Find end of arg, terminated by "delimiter" */ |
1329 | while (*ptr && *ptr != delimiter) |
1330 | ptr++; |
1331 | if (ptr > start) |
1332 | { |
1333 | init_dynamic_string(arg->ds, 0, ptr-start, 32); |
1334 | do_eval(arg->ds, start, ptr, FALSE); |
1335 | } |
1336 | else |
1337 | { |
1338 | /* Empty string */ |
1339 | init_dynamic_string(arg->ds, "" , 0, 0); |
1340 | } |
1341 | /* Find real end of arg, terminated by "delimiter_arg" */ |
1342 | /* This will do nothing if arg was not closed by quotes */ |
1343 | while (*ptr && *ptr != delimiter_arg) |
1344 | ptr++; |
1345 | |
1346 | command->last_argument= (char*)ptr; |
1347 | |
1348 | /* Step past the delimiter */ |
1349 | if (*ptr && *ptr == delimiter_arg) |
1350 | ptr++; |
1351 | DBUG_PRINT("info" , ("val: %s" , arg->ds->str)); |
1352 | break; |
1353 | |
1354 | /* Rest of line */ |
1355 | case ARG_REST: |
1356 | start= ptr; |
1357 | init_dynamic_string(arg->ds, 0, command->query_len, 256); |
1358 | do_eval(arg->ds, start, command->end, FALSE); |
1359 | command->last_argument= command->end; |
1360 | DBUG_PRINT("info" , ("val: %s" , arg->ds->str)); |
1361 | break; |
1362 | |
1363 | default: |
1364 | DBUG_ASSERT("Unknown argument type" ); |
1365 | break; |
1366 | } |
1367 | |
1368 | /* Check required arg */ |
1369 | if (arg->ds->length == 0 && arg->required) |
1370 | die("Missing required argument '%s' to command '%.*s'" , arg->argname, |
1371 | command->first_word_len, command->query); |
1372 | |
1373 | } |
1374 | /* Check for too many arguments passed */ |
1375 | ptr= command->last_argument; |
1376 | while(ptr <= command->end && *ptr != '#') |
1377 | { |
1378 | if (*ptr && *ptr != ' ') |
1379 | die("Extra argument '%s' passed to '%.*s'" , |
1380 | ptr, command->first_word_len, command->query); |
1381 | ptr++; |
1382 | } |
1383 | DBUG_VOID_RETURN; |
1384 | } |
1385 | |
1386 | void handle_command_error(struct st_command *command, uint error, |
1387 | int sys_errno) |
1388 | { |
1389 | DBUG_ENTER("handle_command_error" ); |
1390 | DBUG_PRINT("enter" , ("error: %d" , error)); |
1391 | var_set_int("$sys_errno" ,sys_errno); |
1392 | var_set_int("$errno" ,error); |
1393 | if (error != 0) |
1394 | { |
1395 | int i; |
1396 | |
1397 | if (command->abort_on_error) |
1398 | { |
1399 | report_or_die("command \"%.*s\" failed with error: %u my_errno: %d " |
1400 | "errno: %d" , |
1401 | command->first_word_len, command->query, error, my_errno, |
1402 | sys_errno); |
1403 | DBUG_VOID_RETURN; |
1404 | } |
1405 | |
1406 | i= match_expected_error(command, error, NULL); |
1407 | |
1408 | if (i >= 0) |
1409 | { |
1410 | DBUG_PRINT("info" , ("command \"%.*s\" failed with expected error: %u, errno: %d" , |
1411 | command->first_word_len, command->query, error, |
1412 | sys_errno)); |
1413 | revert_properties(); |
1414 | DBUG_VOID_RETURN; |
1415 | } |
1416 | if (command->expected_errors.count > 0) |
1417 | report_or_die("command \"%.*s\" failed with wrong error: %u " |
1418 | "my_errno: %d errno: %d" , |
1419 | command->first_word_len, command->query, error, my_errno, |
1420 | sys_errno); |
1421 | } |
1422 | else if (command->expected_errors.err[0].type == ERR_ERRNO && |
1423 | command->expected_errors.err[0].code.errnum != 0) |
1424 | { |
1425 | /* Error code we wanted was != 0, i.e. not an expected success */ |
1426 | report_or_die("command \"%.*s\" succeeded - should have failed with " |
1427 | "errno %d..." , |
1428 | command->first_word_len, command->query, |
1429 | command->expected_errors.err[0].code.errnum); |
1430 | } |
1431 | revert_properties(); |
1432 | DBUG_VOID_RETURN; |
1433 | } |
1434 | |
1435 | |
1436 | void close_connections() |
1437 | { |
1438 | DBUG_ENTER("close_connections" ); |
1439 | for (--next_con; next_con >= connections; --next_con) |
1440 | { |
1441 | if (next_con->stmt) |
1442 | do_stmt_close(next_con); |
1443 | #ifdef EMBEDDED_LIBRARY |
1444 | emb_close_connection(next_con); |
1445 | #endif |
1446 | next_con->stmt= 0; |
1447 | mysql_close(next_con->mysql); |
1448 | next_con->mysql= 0; |
1449 | if (next_con->util_mysql) |
1450 | mysql_close(next_con->util_mysql); |
1451 | my_free(next_con->name); |
1452 | } |
1453 | my_free(connections); |
1454 | DBUG_VOID_RETURN; |
1455 | } |
1456 | |
1457 | |
1458 | void close_statements() |
1459 | { |
1460 | struct st_connection *con; |
1461 | DBUG_ENTER("close_statements" ); |
1462 | for (con= connections; con < next_con; con++) |
1463 | { |
1464 | if (con->stmt) |
1465 | do_stmt_close(con); |
1466 | con->stmt= 0; |
1467 | } |
1468 | DBUG_VOID_RETURN; |
1469 | } |
1470 | |
1471 | |
1472 | void close_files() |
1473 | { |
1474 | DBUG_ENTER("close_files" ); |
1475 | for (; cur_file >= file_stack; cur_file--) |
1476 | { |
1477 | if (cur_file->file && cur_file->file != stdin) |
1478 | { |
1479 | DBUG_PRINT("info" , ("closing file: %s" , cur_file->file_name)); |
1480 | fclose(cur_file->file); |
1481 | } |
1482 | my_free(cur_file->file_name); |
1483 | cur_file->file_name= 0; |
1484 | } |
1485 | DBUG_VOID_RETURN; |
1486 | } |
1487 | |
1488 | |
1489 | void free_used_memory() |
1490 | { |
1491 | uint i; |
1492 | DBUG_ENTER("free_used_memory" ); |
1493 | |
1494 | if (connections) |
1495 | close_connections(); |
1496 | close_files(); |
1497 | my_hash_free(&var_hash); |
1498 | |
1499 | for (i= 0 ; i < q_lines.elements ; i++) |
1500 | { |
1501 | struct st_command **q= dynamic_element(&q_lines, i, struct st_command**); |
1502 | my_free((*q)->query_buf); |
1503 | if ((*q)->eval_query.str) |
1504 | dynstr_free(&(*q)->eval_query); |
1505 | if ((*q)->content.str) |
1506 | dynstr_free(&(*q)->content); |
1507 | my_free((*q)); |
1508 | } |
1509 | for (i= 0; i < 10; i++) |
1510 | { |
1511 | if (var_reg[i].alloced_len) |
1512 | my_free(var_reg[i].str_val); |
1513 | } |
1514 | while (embedded_server_arg_count > 1) |
1515 | my_free(embedded_server_args[--embedded_server_arg_count]); |
1516 | delete_dynamic(&q_lines); |
1517 | dynstr_free(&ds_res); |
1518 | if (ds_warn) |
1519 | dynstr_free(ds_warn); |
1520 | free_all_replace(); |
1521 | my_free(opt_pass); |
1522 | free_defaults(default_argv); |
1523 | free_root(&require_file_root, MYF(0)); |
1524 | free_re(); |
1525 | #ifdef _WIN32 |
1526 | free_tmp_sh_file(); |
1527 | free_win_path_patterns(); |
1528 | #endif |
1529 | DBUG_VOID_RETURN; |
1530 | } |
1531 | |
1532 | |
1533 | static void cleanup_and_exit(int exit_code) |
1534 | { |
1535 | free_used_memory(); |
1536 | |
1537 | /* Only call mysql_server_end if mysql_server_init has been called */ |
1538 | if (server_initialized) |
1539 | mysql_server_end(); |
1540 | |
1541 | /* |
1542 | mysqltest is fundamentally written in a way that makes impossible |
1543 | to free all memory before exit (consider memory allocated |
1544 | for frame local DYNAMIC_STRING's and die() invoked down the stack. |
1545 | |
1546 | We close stderr here to stop unavoidable safemalloc reports |
1547 | from polluting the output. |
1548 | */ |
1549 | fclose(stderr); |
1550 | |
1551 | my_end(my_end_arg); |
1552 | |
1553 | if (!silent) { |
1554 | switch (exit_code) { |
1555 | case 1: |
1556 | printf("not ok\n" ); |
1557 | break; |
1558 | case 0: |
1559 | printf("ok\n" ); |
1560 | break; |
1561 | case 62: |
1562 | printf("skipped\n" ); |
1563 | break; |
1564 | default: |
1565 | printf("unknown exit code: %d\n" , exit_code); |
1566 | DBUG_ASSERT(0); |
1567 | } |
1568 | } |
1569 | |
1570 | exit(exit_code); |
1571 | } |
1572 | |
1573 | size_t print_file_stack(char *s, const char *end) |
1574 | { |
1575 | char *start= s; |
1576 | struct st_test_file* err_file= cur_file; |
1577 | if (err_file == file_stack) |
1578 | return 0; |
1579 | |
1580 | for (;;) |
1581 | { |
1582 | err_file--; |
1583 | s+= my_snprintf(s, end - s, "included from %s at line %d:\n" , |
1584 | err_file->file_name, err_file->lineno); |
1585 | if (err_file == file_stack) |
1586 | break; |
1587 | } |
1588 | return s - start; |
1589 | } |
1590 | |
1591 | |
1592 | static void make_error_message(char *buf, size_t len, const char *fmt, va_list args) |
1593 | { |
1594 | char *s= buf, *end= buf + len; |
1595 | s+= my_snprintf(s, end - s, "mysqltest: " ); |
1596 | if (cur_file && cur_file != file_stack) |
1597 | { |
1598 | s+= my_snprintf(s, end - s, "In included file \"%s\": \n" , |
1599 | cur_file->file_name); |
1600 | s+= print_file_stack(s, end); |
1601 | } |
1602 | |
1603 | if (start_lineno > 0) |
1604 | s+= my_snprintf(s, end -s, "At line %u: " , start_lineno); |
1605 | if (!fmt) |
1606 | fmt= "unknown error" ; |
1607 | |
1608 | s+= my_vsnprintf(s, end - s, fmt, args); |
1609 | s+= my_snprintf(s, end -s, "\n" ); |
1610 | } |
1611 | |
1612 | void die(const char *fmt, ...) |
1613 | { |
1614 | char buff[DIE_BUFF_SIZE]; |
1615 | va_list args; |
1616 | va_start(args, fmt); |
1617 | make_error_message(buff, sizeof(buff), fmt, args); |
1618 | really_die(buff); |
1619 | } |
1620 | |
1621 | void really_die(const char *msg) |
1622 | { |
1623 | static int dying= 0; |
1624 | fflush(stdout); |
1625 | fprintf(stderr, "%s" , msg); |
1626 | fflush(stderr); |
1627 | |
1628 | /* |
1629 | Protect against dying twice |
1630 | first time 'die' is called, try to write log files |
1631 | second time, just exit |
1632 | */ |
1633 | if (dying) |
1634 | cleanup_and_exit(1); |
1635 | dying= 1; |
1636 | |
1637 | log_file.show_tail(opt_tail_lines); |
1638 | |
1639 | /* |
1640 | Help debugging by displaying any warnings that might have |
1641 | been produced prior to the error |
1642 | */ |
1643 | if (cur_con && !cur_con->pending) |
1644 | show_warnings_before_error(cur_con->mysql); |
1645 | |
1646 | cleanup_and_exit(1); |
1647 | } |
1648 | |
1649 | void report_or_die(const char *fmt, ...) |
1650 | { |
1651 | va_list args; |
1652 | DBUG_ENTER("report_or_die" ); |
1653 | |
1654 | char buff[DIE_BUFF_SIZE]; |
1655 | |
1656 | va_start(args, fmt); |
1657 | make_error_message(buff, sizeof(buff), fmt, args); |
1658 | va_end(args); |
1659 | |
1660 | if (opt_continue_on_error) |
1661 | { |
1662 | /* Just log the error and continue */ |
1663 | replace_dynstr_append(&ds_res, buff); |
1664 | error_count++; |
1665 | DBUG_VOID_RETURN; |
1666 | } |
1667 | |
1668 | really_die(buff); |
1669 | } |
1670 | |
1671 | |
1672 | void abort_not_supported_test(const char *fmt, ...) |
1673 | { |
1674 | va_list args; |
1675 | DBUG_ENTER("abort_not_supported_test" ); |
1676 | |
1677 | /* Print include filestack */ |
1678 | fflush(stdout); |
1679 | fprintf(stderr, "The test '%s' is not supported by this installation\n" , |
1680 | file_stack->file_name); |
1681 | fprintf(stderr, "Detected in file %s at line %d\n" , |
1682 | cur_file->file_name, cur_file->lineno); |
1683 | |
1684 | char buff[DIE_BUFF_SIZE]; |
1685 | print_file_stack(buff, buff + sizeof(buff)); |
1686 | fprintf(stderr, "%s" , buff); |
1687 | |
1688 | /* Print error message */ |
1689 | va_start(args, fmt); |
1690 | if (fmt) |
1691 | { |
1692 | fprintf(stderr, "reason: " ); |
1693 | vfprintf(stderr, fmt, args); |
1694 | fprintf(stderr, "\n" ); |
1695 | fflush(stderr); |
1696 | } |
1697 | va_end(args); |
1698 | |
1699 | cleanup_and_exit(62); |
1700 | } |
1701 | |
1702 | |
1703 | void abort_not_in_this_version() |
1704 | { |
1705 | die("Not available in this version of mysqltest" ); |
1706 | } |
1707 | |
1708 | |
1709 | void verbose_msg(const char *fmt, ...) |
1710 | { |
1711 | va_list args; |
1712 | DBUG_ENTER("verbose_msg" ); |
1713 | DBUG_PRINT("enter" , ("format: %s" , fmt)); |
1714 | |
1715 | if (!verbose) |
1716 | DBUG_VOID_RETURN; |
1717 | |
1718 | fflush(stdout); |
1719 | va_start(args, fmt); |
1720 | fprintf(stderr, "mysqltest: " ); |
1721 | if (cur_file && cur_file != file_stack) |
1722 | fprintf(stderr, "In included file \"%s\": " , |
1723 | cur_file->file_name); |
1724 | if (start_lineno != 0) |
1725 | fprintf(stderr, "At line %u: " , start_lineno); |
1726 | vfprintf(stderr, fmt, args); |
1727 | fprintf(stderr, "\n" ); |
1728 | va_end(args); |
1729 | fflush(stderr); |
1730 | |
1731 | DBUG_VOID_RETURN; |
1732 | } |
1733 | |
1734 | |
1735 | void log_msg(const char *fmt, ...) |
1736 | { |
1737 | va_list args; |
1738 | char buff[1024]; |
1739 | size_t len; |
1740 | DBUG_ENTER("log_msg" ); |
1741 | |
1742 | va_start(args, fmt); |
1743 | len= my_vsnprintf(buff, sizeof(buff)-1, fmt, args); |
1744 | va_end(args); |
1745 | |
1746 | dynstr_append_mem(&ds_res, buff, len); |
1747 | dynstr_append(&ds_res, "\n" ); |
1748 | |
1749 | DBUG_VOID_RETURN; |
1750 | } |
1751 | |
1752 | |
1753 | /* |
1754 | Read a file and append it to ds |
1755 | |
1756 | SYNOPSIS |
1757 | cat_file |
1758 | ds - pointer to dynamic string where to add the files content |
1759 | filename - name of the file to read |
1760 | |
1761 | */ |
1762 | |
1763 | int cat_file(DYNAMIC_STRING* ds, const char* filename) |
1764 | { |
1765 | int fd; |
1766 | size_t len; |
1767 | char *buff; |
1768 | |
1769 | if ((fd= my_open(filename, O_RDONLY, MYF(0))) < 0) |
1770 | return 1; |
1771 | |
1772 | len= (size_t) my_seek(fd, 0, SEEK_END, MYF(0)); |
1773 | my_seek(fd, 0, SEEK_SET, MYF(0)); |
1774 | if (len == (size_t)MY_FILEPOS_ERROR || |
1775 | !(buff= (char*)my_malloc(len + 1, MYF(0)))) |
1776 | { |
1777 | my_close(fd, MYF(0)); |
1778 | return 1; |
1779 | } |
1780 | len= my_read(fd, (uchar*)buff, len, MYF(0)); |
1781 | my_close(fd, MYF(0)); |
1782 | |
1783 | { |
1784 | char *p= buff, *start= buff,*end=buff+len; |
1785 | while (p < end) |
1786 | { |
1787 | /* Convert cr/lf to lf */ |
1788 | if (*p == '\r' && p+1 < end && *(p+1)== '\n') |
1789 | { |
1790 | /* Add fake newline instead of cr and output the line */ |
1791 | *p= '\n'; |
1792 | p++; /* Step past the "fake" newline */ |
1793 | *p= 0; |
1794 | replace_dynstr_append_mem(ds, start, p-start); |
1795 | p++; /* Step past the "fake" newline */ |
1796 | start= p; |
1797 | } |
1798 | else |
1799 | p++; |
1800 | } |
1801 | /* Output any chars that migh be left */ |
1802 | *p= 0; |
1803 | replace_dynstr_append_mem(ds, start, p-start); |
1804 | } |
1805 | my_free(buff); |
1806 | return 0; |
1807 | } |
1808 | |
1809 | |
1810 | /* |
1811 | Run the specified command with popen |
1812 | |
1813 | SYNOPSIS |
1814 | run_command |
1815 | cmd - command to execute(should be properly quoted |
1816 | ds_res- pointer to dynamic string where to store the result |
1817 | |
1818 | */ |
1819 | |
1820 | static int run_command(char* cmd, |
1821 | DYNAMIC_STRING *ds_res) |
1822 | { |
1823 | char buf[512]= {0}; |
1824 | FILE *res_file; |
1825 | int error; |
1826 | DBUG_ENTER("run_command" ); |
1827 | DBUG_PRINT("enter" , ("cmd: %s" , cmd)); |
1828 | |
1829 | if (!(res_file= my_popen(cmd, "r" ))) |
1830 | { |
1831 | report_or_die("popen(\"%s\", \"r\") failed" , cmd); |
1832 | DBUG_RETURN(-1); |
1833 | } |
1834 | |
1835 | int len; |
1836 | while (my_fgets(buf, sizeof(buf), res_file, &len)) |
1837 | { |
1838 | DBUG_PRINT("info" , ("buf: %s" , buf)); |
1839 | if(ds_res) |
1840 | { |
1841 | /* Save the output of this command in the supplied string */ |
1842 | dynstr_append_mem(ds_res, buf,len); |
1843 | } |
1844 | else |
1845 | { |
1846 | /* Print it directly on screen */ |
1847 | fprintf(stdout, "%s" , buf); |
1848 | } |
1849 | } |
1850 | |
1851 | error= pclose(res_file); |
1852 | DBUG_RETURN(WEXITSTATUS(error)); |
1853 | } |
1854 | |
1855 | |
1856 | /* |
1857 | Run the specified tool with variable number of arguments |
1858 | |
1859 | SYNOPSIS |
1860 | run_tool |
1861 | tool_path - the name of the tool to run |
1862 | ds_res - pointer to dynamic string where to store the result |
1863 | ... - variable number of arguments that will be properly |
1864 | quoted and appended after the tool's name |
1865 | |
1866 | */ |
1867 | |
1868 | static int run_tool(const char *tool_path, DYNAMIC_STRING *ds_res, ...) |
1869 | { |
1870 | int ret; |
1871 | const char* arg; |
1872 | va_list args; |
1873 | DYNAMIC_STRING ds_cmdline; |
1874 | |
1875 | DBUG_ENTER("run_tool" ); |
1876 | DBUG_PRINT("enter" , ("tool_path: %s" , tool_path)); |
1877 | |
1878 | if (init_dynamic_string(&ds_cmdline, IF_WIN("\"" , "" ), FN_REFLEN, FN_REFLEN)) |
1879 | die("Out of memory" ); |
1880 | |
1881 | dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS); |
1882 | dynstr_append(&ds_cmdline, " " ); |
1883 | |
1884 | va_start(args, ds_res); |
1885 | |
1886 | while ((arg= va_arg(args, char *))) |
1887 | { |
1888 | /* Options should be os quoted */ |
1889 | if (strncmp(arg, "--" , 2) == 0) |
1890 | dynstr_append_os_quoted(&ds_cmdline, arg, NullS); |
1891 | else |
1892 | dynstr_append(&ds_cmdline, arg); |
1893 | dynstr_append(&ds_cmdline, " " ); |
1894 | } |
1895 | |
1896 | va_end(args); |
1897 | |
1898 | #ifdef _WIN32 |
1899 | dynstr_append(&ds_cmdline, "\"" ); |
1900 | #endif |
1901 | |
1902 | DBUG_PRINT("info" , ("Running: %s" , ds_cmdline.str)); |
1903 | ret= run_command(ds_cmdline.str, ds_res); |
1904 | DBUG_PRINT("exit" , ("ret: %d" , ret)); |
1905 | dynstr_free(&ds_cmdline); |
1906 | DBUG_RETURN(ret); |
1907 | } |
1908 | |
1909 | |
1910 | /* |
1911 | Test if diff is present. This is needed on Windows systems |
1912 | as the OS returns 1 whether diff is successful or if it is |
1913 | not present. |
1914 | |
1915 | We run diff -v and look for output in stdout. |
1916 | We don't redirect stderr to stdout to make for a simplified check |
1917 | Windows will output '"diff"' is not recognized... to stderr if it is |
1918 | not present. |
1919 | */ |
1920 | |
1921 | #ifdef _WIN32 |
1922 | |
1923 | static int diff_check(const char *diff_name) |
1924 | { |
1925 | FILE *res_file; |
1926 | char buf[128]; |
1927 | int have_diff= 0; |
1928 | |
1929 | my_snprintf(buf, sizeof(buf), "%s -v" , diff_name); |
1930 | |
1931 | if (!(res_file= my_popen(buf, "r" ))) |
1932 | die("popen(\"%s\", \"r\") failed" , buf); |
1933 | |
1934 | /* |
1935 | if diff is not present, nothing will be in stdout to increment |
1936 | have_diff |
1937 | */ |
1938 | int len; |
1939 | if (my_fgets(buf, sizeof(buf), res_file, &len)) |
1940 | have_diff= 1; |
1941 | |
1942 | pclose(res_file); |
1943 | |
1944 | return have_diff; |
1945 | } |
1946 | |
1947 | #endif |
1948 | |
1949 | |
1950 | /* |
1951 | Show the diff of two files using the systems builtin diff |
1952 | command. If no such diff command exist, just dump the content |
1953 | of the two files and inform about how to get "diff" |
1954 | |
1955 | SYNOPSIS |
1956 | show_diff |
1957 | ds - pointer to dynamic string where to add the diff(may be NULL) |
1958 | filename1 - name of first file |
1959 | filename2 - name of second file |
1960 | |
1961 | */ |
1962 | |
1963 | void show_diff(DYNAMIC_STRING* ds, |
1964 | const char* filename1, const char* filename2) |
1965 | { |
1966 | DYNAMIC_STRING ds_tmp; |
1967 | const char *diff_name = 0; |
1968 | |
1969 | if (init_dynamic_string(&ds_tmp, "" , 256, 256)) |
1970 | die("Out of memory" ); |
1971 | |
1972 | /* determine if we have diff on Windows |
1973 | needs special processing due to return values |
1974 | on that OS |
1975 | This test is only done on Windows since it's only needed there |
1976 | in order to correctly detect non-availibility of 'diff', and |
1977 | the way it's implemented does not work with default 'diff' on Solaris. |
1978 | */ |
1979 | #ifdef _WIN32 |
1980 | if (diff_check("diff" )) |
1981 | diff_name = "diff" ; |
1982 | else if (diff_check("mtrdiff" )) |
1983 | diff_name = "mtrdiff" ; |
1984 | else |
1985 | diff_name = 0; |
1986 | #else |
1987 | diff_name = "diff" ; /* Otherwise always assume it's called diff */ |
1988 | #endif |
1989 | |
1990 | if (diff_name) |
1991 | { |
1992 | /* First try with unified diff */ |
1993 | if (run_tool(diff_name, |
1994 | &ds_tmp, /* Get output from diff in ds_tmp */ |
1995 | "-u" , |
1996 | filename1, |
1997 | filename2, |
1998 | "2>&1" , |
1999 | NULL) > 1) /* Most "diff" tools return >1 if error */ |
2000 | { |
2001 | dynstr_set(&ds_tmp, "" ); |
2002 | |
2003 | /* Fallback to context diff with "diff -c" */ |
2004 | if (run_tool(diff_name, |
2005 | &ds_tmp, /* Get output from diff in ds_tmp */ |
2006 | "-c" , |
2007 | filename1, |
2008 | filename2, |
2009 | "2>&1" , |
2010 | NULL) > 1) /* Most "diff" tools return >1 if error */ |
2011 | { |
2012 | dynstr_set(&ds_tmp, "" ); |
2013 | |
2014 | /* Fallback to simple diff with "diff" */ |
2015 | if (run_tool(diff_name, |
2016 | &ds_tmp, /* Get output from diff in ds_tmp */ |
2017 | filename1, |
2018 | filename2, |
2019 | "2>&1" , |
2020 | NULL) > 1) /* Most "diff" tools return >1 if error */ |
2021 | { |
2022 | diff_name= 0; |
2023 | } |
2024 | } |
2025 | } |
2026 | } |
2027 | |
2028 | if (! diff_name) |
2029 | { |
2030 | /* |
2031 | Fallback to dump both files to result file and inform |
2032 | about installing "diff" |
2033 | */ |
2034 | dynstr_append(&ds_tmp, "\n" ); |
2035 | dynstr_append(&ds_tmp, |
2036 | "\n" |
2037 | "The two files differ but it was not possible to execute 'diff' in\n" |
2038 | "order to show only the difference. Instead the whole content of the\n" |
2039 | "two files was shown for you to diff manually.\n\n" |
2040 | "To get a better report you should install 'diff' on your system, which you\n" |
2041 | "for example can get from http://www.gnu.org/software/diffutils/diffutils.html\n" |
2042 | #ifdef _WIN32 |
2043 | "or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n" |
2044 | #endif |
2045 | "\n" ); |
2046 | |
2047 | dynstr_append(&ds_tmp, " --- " ); |
2048 | dynstr_append(&ds_tmp, filename1); |
2049 | dynstr_append(&ds_tmp, " >>>\n" ); |
2050 | cat_file(&ds_tmp, filename1); |
2051 | dynstr_append(&ds_tmp, "<<<\n --- " ); |
2052 | dynstr_append(&ds_tmp, filename1); |
2053 | dynstr_append(&ds_tmp, " >>>\n" ); |
2054 | cat_file(&ds_tmp, filename2); |
2055 | dynstr_append(&ds_tmp, "<<<<\n" ); |
2056 | } |
2057 | |
2058 | if (ds) |
2059 | { |
2060 | /* Add the diff to output */ |
2061 | dynstr_append_mem(ds, ds_tmp.str, ds_tmp.length); |
2062 | } |
2063 | else |
2064 | { |
2065 | /* Print diff directly to stdout */ |
2066 | fprintf(stderr, "%s\n" , ds_tmp.str); |
2067 | } |
2068 | |
2069 | dynstr_free(&ds_tmp); |
2070 | |
2071 | } |
2072 | |
2073 | |
2074 | enum compare_files_result_enum { |
2075 | RESULT_OK= 0, |
2076 | RESULT_CONTENT_MISMATCH= 1, |
2077 | RESULT_LENGTH_MISMATCH= 2 |
2078 | }; |
2079 | |
2080 | /* |
2081 | Compare two files, given a fd to the first file and |
2082 | name of the second file |
2083 | |
2084 | SYNOPSIS |
2085 | compare_files2 |
2086 | fd - Open file descriptor of the first file |
2087 | filename2 - Name of second file |
2088 | |
2089 | RETURN VALUES |
2090 | According to the values in "compare_files_result_enum" |
2091 | |
2092 | */ |
2093 | |
2094 | int compare_files2(File fd1, const char* filename2) |
2095 | { |
2096 | int error= RESULT_OK; |
2097 | File fd2; |
2098 | size_t fd1_length, fd2_length; |
2099 | DYNAMIC_STRING fd1_result, fd2_result; |
2100 | |
2101 | if ((fd2= my_open(filename2, O_RDONLY, MYF(0))) < 0) |
2102 | { |
2103 | my_close(fd1, MYF(0)); |
2104 | die("Failed to open second file: '%s'" , filename2); |
2105 | } |
2106 | |
2107 | fd1_length= (size_t) my_seek(fd1, 0, SEEK_END, MYF(0)); |
2108 | fd2_length= (size_t) my_seek(fd2, 0, SEEK_END, MYF(0)); |
2109 | |
2110 | if (init_dynamic_string(&fd1_result, 0, fd1_length, 0) || |
2111 | init_dynamic_string(&fd2_result, 0, fd2_length, 0)) |
2112 | die("Out of memory when allocating data for result" ); |
2113 | |
2114 | fd1_result.length= fd1_length; |
2115 | fd2_result.length= fd2_length; |
2116 | |
2117 | (void) my_seek(fd1, 0, SEEK_SET, MYF(0)); |
2118 | (void) my_seek(fd2, 0, SEEK_SET, MYF(0)); |
2119 | if (my_read(fd1, (uchar*) fd1_result.str, fd1_length, MYF(MY_WME | MY_NABP))) |
2120 | die("Error when reading data from result file" ); |
2121 | if (my_read(fd2, (uchar*) fd2_result.str, fd2_length, MYF(MY_WME | MY_NABP))) |
2122 | die("Error when reading data from result file" ); |
2123 | |
2124 | if (global_subst && |
2125 | (fd1_length != fd2_length || |
2126 | memcmp(fd1_result.str, fd2_result.str, fd1_length))) |
2127 | { |
2128 | /** |
2129 | @todo MARIA_HACK |
2130 | This serves for when a test is run with --default-storage-engine=X |
2131 | where X is not MyISAM: tests using SHOW CREATE TABLE will always fail |
2132 | because SHOW CREATE TABLE prints X instead of MyISAM. With |
2133 | --global-subst=X,MyISAM , such trivial differences are eliminated and |
2134 | test may be reported as passing. |
2135 | --global-subst is only a quick way to run a lot of existing tests |
2136 | with Maria and find bugs; it is not good enough for reaching the main |
2137 | trees when Maria is merged into them. |
2138 | --global-subst should be removed. |
2139 | */ |
2140 | size_t global_subst_from_len= strlen(global_subst_from); |
2141 | size_t global_subst_to_len= strlen(global_subst_to); |
2142 | while (replace(&fd1_result, |
2143 | global_subst_from, global_subst_from_len, |
2144 | global_subst_to, global_subst_to_len) == 0) |
2145 | /* do nothing */ ; |
2146 | /* let's compare again to see if it is ok now */ |
2147 | } |
2148 | |
2149 | if (fd1_result.length != fd2_result.length) |
2150 | error= RESULT_LENGTH_MISMATCH; |
2151 | else if ((memcmp(fd1_result.str, fd2_result.str, fd1_result.length))) |
2152 | error= RESULT_CONTENT_MISMATCH; |
2153 | |
2154 | my_close(fd2, MYF(0)); |
2155 | dynstr_free(&fd1_result); |
2156 | dynstr_free(&fd2_result); |
2157 | |
2158 | return error; |
2159 | } |
2160 | |
2161 | |
2162 | /* |
2163 | Compare two files, given their filenames |
2164 | |
2165 | SYNOPSIS |
2166 | compare_files |
2167 | filename1 - Name of first file |
2168 | filename2 - Name of second file |
2169 | |
2170 | RETURN VALUES |
2171 | See 'compare_files2' |
2172 | |
2173 | */ |
2174 | |
2175 | int compare_files(const char* filename1, const char* filename2) |
2176 | { |
2177 | File fd; |
2178 | int error; |
2179 | |
2180 | if ((fd= my_open(filename1, O_RDONLY, MYF(0))) < 0) |
2181 | die("Failed to open first file: '%s'" , filename1); |
2182 | |
2183 | error= compare_files2(fd, filename2); |
2184 | |
2185 | my_close(fd, MYF(0)); |
2186 | |
2187 | return error; |
2188 | } |
2189 | |
2190 | |
2191 | /* |
2192 | Compare content of the string in ds to content of file fname |
2193 | |
2194 | SYNOPSIS |
2195 | dyn_string_cmp |
2196 | ds - Dynamic string containing the string o be compared |
2197 | fname - Name of file to compare with |
2198 | |
2199 | RETURN VALUES |
2200 | See 'compare_files2' |
2201 | */ |
2202 | |
2203 | int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) |
2204 | { |
2205 | int error; |
2206 | File fd; |
2207 | char temp_file_path[FN_REFLEN]; |
2208 | |
2209 | DBUG_ENTER("dyn_string_cmp" ); |
2210 | DBUG_PRINT("enter" , ("fname: %s" , fname)); |
2211 | |
2212 | if ((fd= create_temp_file(temp_file_path, TMPDIR, "tmp" , O_SHARE, |
2213 | MYF(MY_WME))) < 0) |
2214 | die("Failed to create temporary file for ds" ); |
2215 | |
2216 | /* Write ds to temporary file and set file pos to beginning*/ |
2217 | if (my_write(fd, (uchar *) ds->str, ds->length, |
2218 | MYF(MY_FNABP | MY_WME)) || |
2219 | my_seek(fd, 0, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) |
2220 | { |
2221 | my_close(fd, MYF(0)); |
2222 | /* Remove the temporary file */ |
2223 | my_delete(temp_file_path, MYF(MY_WME)); |
2224 | die("Failed to write file '%s'" , temp_file_path); |
2225 | } |
2226 | |
2227 | error= compare_files2(fd, fname); |
2228 | |
2229 | my_close(fd, MYF(0)); |
2230 | /* Remove the temporary file */ |
2231 | my_delete(temp_file_path, MYF(MY_WME)); |
2232 | |
2233 | DBUG_RETURN(error); |
2234 | } |
2235 | |
2236 | |
2237 | /* |
2238 | Check the content of log against result file |
2239 | |
2240 | SYNOPSIS |
2241 | check_result |
2242 | |
2243 | RETURN VALUES |
2244 | error - the function will not return |
2245 | |
2246 | */ |
2247 | |
2248 | void check_result() |
2249 | { |
2250 | const char *mess= 0; |
2251 | |
2252 | DBUG_ENTER("check_result" ); |
2253 | DBUG_ASSERT(result_file_name); |
2254 | DBUG_PRINT("enter" , ("result_file_name: %s" , result_file_name)); |
2255 | |
2256 | switch (compare_files(log_file.file_name(), result_file_name)) { |
2257 | case RESULT_OK: |
2258 | if (!error_count) |
2259 | break; /* ok */ |
2260 | mess= "Got errors while running test" ; |
2261 | /* Fallthrough */ |
2262 | case RESULT_LENGTH_MISMATCH: |
2263 | if (!mess) |
2264 | mess= "Result length mismatch\n" ; |
2265 | /* Fallthrough */ |
2266 | case RESULT_CONTENT_MISMATCH: |
2267 | { |
2268 | /* |
2269 | Result mismatched, dump results to .reject file |
2270 | and then show the diff |
2271 | */ |
2272 | char reject_file[FN_REFLEN]; |
2273 | size_t reject_length; |
2274 | |
2275 | if (!mess) |
2276 | mess= "Result content mismatch\n" ; |
2277 | |
2278 | dirname_part(reject_file, result_file_name, &reject_length); |
2279 | |
2280 | if (access(reject_file, W_OK) == 0) |
2281 | { |
2282 | /* Result file directory is writable, save reject file there */ |
2283 | fn_format(reject_file, result_file_name, "" , |
2284 | ".reject" , MY_REPLACE_EXT); |
2285 | } |
2286 | else |
2287 | { |
2288 | /* Put reject file in opt_logdir */ |
2289 | fn_format(reject_file, result_file_name, opt_logdir, |
2290 | ".reject" , MY_REPLACE_DIR | MY_REPLACE_EXT); |
2291 | } |
2292 | |
2293 | if (my_copy(log_file.file_name(), reject_file, MYF(0)) != 0) |
2294 | die("Failed to copy '%s' to '%s', errno: %d" , |
2295 | log_file.file_name(), reject_file, errno); |
2296 | |
2297 | show_diff(NULL, result_file_name, reject_file); |
2298 | die("%s" , mess); |
2299 | break; |
2300 | } |
2301 | default: /* impossible */ |
2302 | die("Unknown error code from dyn_string_cmp()" ); |
2303 | } |
2304 | |
2305 | DBUG_VOID_RETURN; |
2306 | } |
2307 | |
2308 | |
2309 | /* |
2310 | Check the content of ds against a require file |
2311 | If match fails, abort the test with special error code |
2312 | indicating that test is not supported |
2313 | |
2314 | SYNOPSIS |
2315 | check_require |
2316 | ds - content to be checked |
2317 | fname - name of file to check against |
2318 | |
2319 | RETURN VALUES |
2320 | error - the function will not return |
2321 | |
2322 | */ |
2323 | |
2324 | void check_require(DYNAMIC_STRING* ds, const char *fname) |
2325 | { |
2326 | DBUG_ENTER("check_require" ); |
2327 | |
2328 | if (dyn_string_cmp(ds, fname)) |
2329 | { |
2330 | char reason[FN_REFLEN]; |
2331 | fn_format(reason, fname, "" , "" , MY_REPLACE_EXT | MY_REPLACE_DIR); |
2332 | abort_not_supported_test("Test requires: '%s'" , reason); |
2333 | } |
2334 | DBUG_VOID_RETURN; |
2335 | } |
2336 | |
2337 | |
2338 | /* |
2339 | Remove surrounding chars from string |
2340 | |
2341 | Return 1 if first character is found but not last |
2342 | */ |
2343 | static int strip_surrounding(char* str, char c1, char c2) |
2344 | { |
2345 | char* ptr= str; |
2346 | |
2347 | /* Check if the first non space character is c1 */ |
2348 | while(*ptr && my_isspace(charset_info, *ptr)) |
2349 | ptr++; |
2350 | if (*ptr == c1) |
2351 | { |
2352 | /* Replace it with a space */ |
2353 | *ptr= ' '; |
2354 | |
2355 | /* Last non space charecter should be c2 */ |
2356 | ptr= strend(str)-1; |
2357 | while(*ptr && my_isspace(charset_info, *ptr)) |
2358 | ptr--; |
2359 | if (*ptr == c2) |
2360 | { |
2361 | /* Replace it with \0 */ |
2362 | *ptr= 0; |
2363 | } |
2364 | else |
2365 | { |
2366 | /* Mismatch detected */ |
2367 | return 1; |
2368 | } |
2369 | } |
2370 | return 0; |
2371 | } |
2372 | |
2373 | |
2374 | static void strip_parentheses(struct st_command *command) |
2375 | { |
2376 | if (strip_surrounding(command->first_argument, '(', ')')) |
2377 | die("%.*s - argument list started with '%c' must be ended with '%c'" , |
2378 | command->first_word_len, command->query, '(', ')'); |
2379 | } |
2380 | |
2381 | |
2382 | C_MODE_START |
2383 | |
2384 | static uchar *get_var_key(const uchar* var, size_t *len, |
2385 | my_bool __attribute__((unused)) t) |
2386 | { |
2387 | char* key; |
2388 | key = ((VAR*)var)->name; |
2389 | *len = ((VAR*)var)->name_len; |
2390 | return (uchar*)key; |
2391 | } |
2392 | |
2393 | |
2394 | static void var_free(void *v) |
2395 | { |
2396 | VAR *var= (VAR*) v; |
2397 | my_free(var->str_val); |
2398 | if (var->alloced) |
2399 | my_free(var); |
2400 | } |
2401 | |
2402 | C_MODE_END |
2403 | |
2404 | void var_check_int(VAR *v) |
2405 | { |
2406 | char *endptr; |
2407 | char *str= v->str_val; |
2408 | |
2409 | /* Initially assume not a number */ |
2410 | v->int_val= 0; |
2411 | v->is_int= false; |
2412 | v->int_dirty= false; |
2413 | if (!str) return; |
2414 | |
2415 | v->int_val = (int) strtol(str, &endptr, 10); |
2416 | /* It is an int if strtol consumed something up to end/space/tab */ |
2417 | if (endptr > str && (!*endptr || *endptr == ' ' || *endptr == '\t')) |
2418 | v->is_int= true; |
2419 | } |
2420 | |
2421 | |
2422 | VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val, size_t val_len) |
2423 | { |
2424 | size_t val_alloc_len; |
2425 | VAR *tmp_var; |
2426 | if (!name_len && name) |
2427 | name_len = strlen(name); |
2428 | if (!val_len && val) |
2429 | val_len = strlen(val) ; |
2430 | if (!val) |
2431 | val_len= 0; |
2432 | val_alloc_len = val_len + 16; /* room to grow */ |
2433 | if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(sizeof(*tmp_var) |
2434 | + name_len+2, MYF(MY_WME)))) |
2435 | die("Out of memory" ); |
2436 | |
2437 | if (name != NULL) |
2438 | { |
2439 | tmp_var->name= reinterpret_cast<char*>(tmp_var) + sizeof(*tmp_var); |
2440 | memcpy(tmp_var->name, name, name_len); |
2441 | tmp_var->name[name_len]= 0; |
2442 | } |
2443 | else |
2444 | tmp_var->name= NULL; |
2445 | |
2446 | tmp_var->alloced = (v == 0); |
2447 | |
2448 | if (!(tmp_var->str_val = (char*)my_malloc(val_alloc_len+1, MYF(MY_WME)))) |
2449 | die("Out of memory" ); |
2450 | |
2451 | if (val) |
2452 | memcpy(tmp_var->str_val, val, val_len); |
2453 | tmp_var->str_val[val_len]= 0; |
2454 | |
2455 | var_check_int(tmp_var); |
2456 | tmp_var->name_len = name_len; |
2457 | tmp_var->str_val_len = val_len; |
2458 | tmp_var->alloced_len = val_alloc_len; |
2459 | return tmp_var; |
2460 | } |
2461 | |
2462 | |
2463 | VAR* var_from_env(const char *name, const char *def_val) |
2464 | { |
2465 | const char *tmp; |
2466 | VAR *v; |
2467 | if (!(tmp = getenv(name))) |
2468 | tmp = def_val; |
2469 | |
2470 | v = var_init(0, name, strlen(name), tmp, strlen(tmp)); |
2471 | my_hash_insert(&var_hash, (uchar*)v); |
2472 | return v; |
2473 | } |
2474 | |
2475 | |
2476 | VAR* var_get(const char *var_name, const char **var_name_end, my_bool raw, |
2477 | my_bool ignore_not_existing) |
2478 | { |
2479 | int digit; |
2480 | VAR *v; |
2481 | DBUG_ENTER("var_get" ); |
2482 | DBUG_PRINT("enter" , ("var_name: %s" ,var_name)); |
2483 | |
2484 | if (*var_name != '$') |
2485 | goto err; |
2486 | digit = *++var_name - '0'; |
2487 | if (digit < 0 || digit >= 10) |
2488 | { |
2489 | const char *save_var_name = var_name, *end; |
2490 | uint length; |
2491 | end = (var_name_end) ? *var_name_end : 0; |
2492 | while (my_isvar(charset_info,*var_name) && var_name != end) |
2493 | var_name++; |
2494 | if (var_name == save_var_name) |
2495 | { |
2496 | if (ignore_not_existing) |
2497 | DBUG_RETURN(0); |
2498 | die("Empty variable" ); |
2499 | } |
2500 | length= (uint) (var_name - save_var_name); |
2501 | if (length >= MAX_VAR_NAME_LENGTH) |
2502 | die("Too long variable name: %s" , save_var_name); |
2503 | |
2504 | if (!(v = (VAR*) my_hash_search(&var_hash, (const uchar*) save_var_name, |
2505 | length))) |
2506 | { |
2507 | char buff[MAX_VAR_NAME_LENGTH+1]; |
2508 | strmake(buff, save_var_name, length); |
2509 | v= var_from_env(buff, "" ); |
2510 | } |
2511 | var_name--; /* Point at last character */ |
2512 | } |
2513 | else |
2514 | v = var_reg + digit; |
2515 | |
2516 | if (!raw && v->int_dirty) |
2517 | { |
2518 | sprintf(v->str_val, "%d" , v->int_val); |
2519 | v->int_dirty= false; |
2520 | v->str_val_len = strlen(v->str_val); |
2521 | } |
2522 | if (var_name_end) |
2523 | *var_name_end = var_name ; |
2524 | DBUG_RETURN(v); |
2525 | err: |
2526 | if (var_name_end) |
2527 | *var_name_end = 0; |
2528 | die("Unsupported variable name: %s" , var_name); |
2529 | DBUG_RETURN(0); |
2530 | } |
2531 | |
2532 | |
2533 | VAR *var_obtain(const char *name, int len) |
2534 | { |
2535 | VAR* v; |
2536 | if ((v = (VAR*)my_hash_search(&var_hash, (const uchar *) name, len))) |
2537 | return v; |
2538 | v = var_init(0, name, len, "" , 0); |
2539 | my_hash_insert(&var_hash, (uchar*)v); |
2540 | return v; |
2541 | } |
2542 | |
2543 | |
2544 | /* |
2545 | - if variable starts with a $ it is regarded as a local test variable |
2546 | - if not it is treated as a environment variable, and the corresponding |
2547 | environment variable will be updated |
2548 | */ |
2549 | |
2550 | void var_set(const char *var_name, const char *var_name_end, |
2551 | const char *var_val, const char *var_val_end) |
2552 | { |
2553 | int digit, env_var= 0; |
2554 | VAR *v; |
2555 | DBUG_ENTER("var_set" ); |
2556 | DBUG_PRINT("enter" , ("var_name: '%.*s' = '%.*s' (length: %d)" , |
2557 | (int) (var_name_end - var_name), var_name, |
2558 | (int) (var_val_end - var_val), var_val, |
2559 | (int) (var_val_end - var_val))); |
2560 | |
2561 | if (*var_name != '$') |
2562 | env_var= 1; |
2563 | else |
2564 | var_name++; |
2565 | |
2566 | digit= *var_name - '0'; |
2567 | if (!(digit < 10 && digit >= 0)) |
2568 | { |
2569 | v= var_obtain(var_name, (uint) (var_name_end - var_name)); |
2570 | } |
2571 | else |
2572 | v= var_reg + digit; |
2573 | |
2574 | eval_expr(v, var_val, (const char**) &var_val_end); |
2575 | |
2576 | if (env_var) |
2577 | { |
2578 | if (v->int_dirty) |
2579 | { |
2580 | sprintf(v->str_val, "%d" , v->int_val); |
2581 | v->int_dirty=false; |
2582 | v->str_val_len= strlen(v->str_val); |
2583 | } |
2584 | /* setenv() expects \0-terminated strings */ |
2585 | DBUG_ASSERT(v->name[v->name_len] == 0); |
2586 | setenv(v->name, v->str_val, 1); |
2587 | } |
2588 | DBUG_VOID_RETURN; |
2589 | } |
2590 | |
2591 | |
2592 | void var_set_string(const char* name, const char* value) |
2593 | { |
2594 | var_set(name, name + strlen(name), value, value + strlen(value)); |
2595 | } |
2596 | |
2597 | |
2598 | void var_set_int(const char* name, int value) |
2599 | { |
2600 | char buf[21]; |
2601 | my_snprintf(buf, sizeof(buf), "%d" , value); |
2602 | var_set_string(name, buf); |
2603 | } |
2604 | |
2605 | |
2606 | /* |
2607 | Store an integer (typically the returncode of the last SQL) |
2608 | statement in the mysqltest builtin variable $mysql_errno |
2609 | */ |
2610 | |
2611 | void var_set_errno(int sql_errno) |
2612 | { |
2613 | var_set_int("$mysql_errno" , sql_errno); |
2614 | var_set_string("$mysql_errname" , get_errname_from_code(sql_errno)); |
2615 | } |
2616 | |
2617 | /* Functions to handle --disable and --enable properties */ |
2618 | |
2619 | void set_once_property(enum_prop prop, my_bool val) |
2620 | { |
2621 | property &pr= prop_list[prop]; |
2622 | pr.set= 1; |
2623 | pr.old= *pr.var; |
2624 | *pr.var= val; |
2625 | var_set_int(pr.env_name, (val != pr.reverse)); |
2626 | once_property= TRUE; |
2627 | } |
2628 | |
2629 | void set_property(st_command *command, enum_prop prop, my_bool val) |
2630 | { |
2631 | char* p= command->first_argument; |
2632 | if (p && !strcmp (p, "ONCE" )) |
2633 | { |
2634 | command->last_argument= p + 4; |
2635 | set_once_property(prop, val); |
2636 | return; |
2637 | } |
2638 | property &pr= prop_list[prop]; |
2639 | *pr.var= val; |
2640 | pr.set= 0; |
2641 | var_set_int(pr.env_name, (val != pr.reverse)); |
2642 | } |
2643 | |
2644 | void revert_properties() |
2645 | { |
2646 | if (! once_property) |
2647 | return; |
2648 | for (int i= 0; i < (int) P_MAX; i++) |
2649 | { |
2650 | property &pr= prop_list[i]; |
2651 | if (pr.set) |
2652 | { |
2653 | *pr.var= pr.old; |
2654 | pr.set= 0; |
2655 | var_set_int(pr.env_name, (pr.old != pr.reverse)); |
2656 | } |
2657 | } |
2658 | once_property=FALSE; |
2659 | } |
2660 | |
2661 | |
2662 | /* |
2663 | Set variable from the result of a query |
2664 | |
2665 | SYNOPSIS |
2666 | var_query_set() |
2667 | var variable to set from query |
2668 | query start of query string to execute |
2669 | query_end end of the query string to execute |
2670 | |
2671 | |
2672 | DESCRIPTION |
2673 | let @<var_name> = `<query>` |
2674 | |
2675 | Execute the query and assign the first row of result to var as |
2676 | a tab separated strings |
2677 | |
2678 | Also assign each column of the result set to |
2679 | variable "$<var_name>_<column_name>" |
2680 | Thus the tab separated output can be read from $<var_name> and |
2681 | and each individual column can be read as $<var_name>_<col_name> |
2682 | |
2683 | */ |
2684 | |
2685 | void var_query_set(VAR *var, const char *query, const char** query_end) |
2686 | { |
2687 | char *end = (char*)((query_end && *query_end) ? |
2688 | *query_end : query + strlen(query)); |
2689 | MYSQL_RES *UNINIT_VAR(res); |
2690 | MYSQL_ROW row; |
2691 | MYSQL* mysql = cur_con->mysql; |
2692 | DYNAMIC_STRING ds_query; |
2693 | DBUG_ENTER("var_query_set" ); |
2694 | |
2695 | if (!mysql) |
2696 | { |
2697 | struct st_command command; |
2698 | DBUG_ASSERT(query_end); |
2699 | memset(&command, 0, sizeof(command)); |
2700 | command.query= (char*)query; |
2701 | command.first_word_len= (int)(*query_end - query); |
2702 | command.first_argument= command.query + command.first_word_len; |
2703 | command.end= (char*)*query_end; |
2704 | command.abort_on_error= 1; /* avoid uninitialized variables */ |
2705 | handle_no_active_connection(&command, cur_con, &ds_res); |
2706 | DBUG_VOID_RETURN; |
2707 | } |
2708 | |
2709 | /* Only white space or ) allowed past ending ` */ |
2710 | while (end > query && *end != '`') |
2711 | { |
2712 | if (*end && (*end != ' ' && *end != '\t' && *end != '\n' && *end != ')')) |
2713 | die("Spurious text after `query` expression" ); |
2714 | --end; |
2715 | } |
2716 | |
2717 | if (query == end) |
2718 | die("Syntax error in query, missing '`'" ); |
2719 | ++query; |
2720 | |
2721 | /* Eval the query, thus replacing all environment variables */ |
2722 | init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); |
2723 | do_eval(&ds_query, query, end, FALSE); |
2724 | |
2725 | if (mysql_real_query(mysql, ds_query.str, (ulong)ds_query.length) || |
2726 | !(res= mysql_store_result(mysql))) |
2727 | { |
2728 | handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), |
2729 | mysql_sqlstate(mysql), &ds_res); |
2730 | /* If error was acceptable, return empty string */ |
2731 | dynstr_free(&ds_query); |
2732 | eval_expr(var, "" , 0); |
2733 | DBUG_VOID_RETURN; |
2734 | } |
2735 | |
2736 | dynstr_free(&ds_query); |
2737 | |
2738 | if ((row= mysql_fetch_row(res)) && row[0]) |
2739 | { |
2740 | /* |
2741 | Concatenate all fields in the first row with tab in between |
2742 | and assign that string to the $variable |
2743 | */ |
2744 | DYNAMIC_STRING result; |
2745 | uint i; |
2746 | ulong *lengths; |
2747 | |
2748 | init_dynamic_string(&result, "" , 512, 512); |
2749 | lengths= mysql_fetch_lengths(res); |
2750 | for (i= 0; i < mysql_num_fields(res); i++) |
2751 | { |
2752 | if (row[i]) |
2753 | { |
2754 | /* Add column to tab separated string */ |
2755 | char *val= row[i]; |
2756 | size_t len= lengths[i]; |
2757 | |
2758 | if (glob_replace_regex) |
2759 | { |
2760 | /* Regex replace */ |
2761 | if (!multi_reg_replace(glob_replace_regex, (char*)val)) |
2762 | { |
2763 | val= glob_replace_regex->buf; |
2764 | len= strlen(val); |
2765 | } |
2766 | } |
2767 | |
2768 | if (glob_replace) |
2769 | replace_strings_append(glob_replace, &result, val); |
2770 | else |
2771 | dynstr_append_mem(&result, val, len); |
2772 | } |
2773 | dynstr_append_mem(&result, "\t" , 1); |
2774 | } |
2775 | end= result.str + result.length-1; |
2776 | /* Evaluation should not recurse via backtick */ |
2777 | eval_expr(var, result.str, (const char**) &end, false, false); |
2778 | dynstr_free(&result); |
2779 | } |
2780 | else |
2781 | eval_expr(var, "" , 0); |
2782 | |
2783 | mysql_free_result(res); |
2784 | DBUG_VOID_RETURN; |
2785 | } |
2786 | |
2787 | |
2788 | static void |
2789 | set_result_format_version(ulong new_version) |
2790 | { |
2791 | switch (new_version){ |
2792 | case 1: |
2793 | /* The first format */ |
2794 | break; |
2795 | case 2: |
2796 | /* New format that also writes comments and empty lines |
2797 | from test file to result */ |
2798 | break; |
2799 | default: |
2800 | die("Version format %lu has not yet been implemented" , new_version); |
2801 | break; |
2802 | } |
2803 | opt_result_format_version= new_version; |
2804 | } |
2805 | |
2806 | |
2807 | /* |
2808 | Set the result format version to use when generating |
2809 | the .result file |
2810 | */ |
2811 | |
2812 | static void |
2813 | do_result_format_version(struct st_command *command) |
2814 | { |
2815 | long version; |
2816 | static DYNAMIC_STRING ds_version; |
2817 | const struct command_arg result_format_args[] = { |
2818 | {"version" , ARG_STRING, TRUE, &ds_version, "Version to use" } |
2819 | }; |
2820 | |
2821 | DBUG_ENTER("do_result_format_version" ); |
2822 | |
2823 | check_command_args(command, command->first_argument, |
2824 | result_format_args, |
2825 | sizeof(result_format_args)/sizeof(struct command_arg), |
2826 | ','); |
2827 | |
2828 | /* Convert version number to int */ |
2829 | if (!str2int(ds_version.str, 10, (long) 0, (long) INT_MAX, &version)) |
2830 | die("Invalid version number: '%s'" , ds_version.str); |
2831 | |
2832 | set_result_format_version(version); |
2833 | |
2834 | dynstr_append(&ds_res, "result_format: " ); |
2835 | dynstr_append_mem(&ds_res, ds_version.str, ds_version.length); |
2836 | dynstr_append(&ds_res, "\n" ); |
2837 | dynstr_free(&ds_version); |
2838 | } |
2839 | |
2840 | |
2841 | /* |
2842 | Set variable from the result of a field in a query |
2843 | |
2844 | This function is useful when checking for a certain value |
2845 | in the output from a query that can't be restricted to only |
2846 | return some values. A very good example of that is most SHOW |
2847 | commands. |
2848 | |
2849 | SYNOPSIS |
2850 | var_set_query_get_value() |
2851 | |
2852 | DESCRIPTION |
2853 | let $variable= query_get_value(<query to run>,<column name>,<row no>); |
2854 | |
2855 | <query to run> - The query that should be sent to the server |
2856 | <column name> - Name of the column that holds the field be compared |
2857 | against the expected value |
2858 | <row no> - Number of the row that holds the field to be |
2859 | compared against the expected value |
2860 | |
2861 | */ |
2862 | |
2863 | void var_set_query_get_value(struct st_command *command, VAR *var) |
2864 | { |
2865 | long row_no; |
2866 | int col_no= -1; |
2867 | MYSQL_RES* UNINIT_VAR(res); |
2868 | MYSQL* mysql= cur_con->mysql; |
2869 | |
2870 | static DYNAMIC_STRING ds_query; |
2871 | static DYNAMIC_STRING ds_col; |
2872 | static DYNAMIC_STRING ds_row; |
2873 | const struct command_arg query_get_value_args[] = { |
2874 | {"query" , ARG_STRING, TRUE, &ds_query, "Query to run" }, |
2875 | {"column name" , ARG_STRING, TRUE, &ds_col, "Name of column" }, |
2876 | {"row number" , ARG_STRING, TRUE, &ds_row, "Number for row" } |
2877 | }; |
2878 | |
2879 | DBUG_ENTER("var_set_query_get_value" ); |
2880 | |
2881 | if (!mysql) |
2882 | { |
2883 | handle_no_active_connection(command, cur_con, &ds_res); |
2884 | DBUG_VOID_RETURN; |
2885 | } |
2886 | |
2887 | strip_parentheses(command); |
2888 | DBUG_PRINT("info" , ("query: %s" , command->query)); |
2889 | check_command_args(command, command->first_argument, query_get_value_args, |
2890 | sizeof(query_get_value_args)/sizeof(struct command_arg), |
2891 | ','); |
2892 | |
2893 | DBUG_PRINT("info" , ("query: %s" , ds_query.str)); |
2894 | DBUG_PRINT("info" , ("col: %s" , ds_col.str)); |
2895 | |
2896 | /* Convert row number to int */ |
2897 | if (!str2int(ds_row.str, 10, (long) 0, (long) INT_MAX, &row_no)) |
2898 | die("Invalid row number: '%s'" , ds_row.str); |
2899 | DBUG_PRINT("info" , ("row: %s, row_no: %ld" , ds_row.str, row_no)); |
2900 | dynstr_free(&ds_row); |
2901 | |
2902 | /* Remove any surrounding "'s from the query - if there is any */ |
2903 | if (strip_surrounding(ds_query.str, '"', '"')) |
2904 | die("Mismatched \"'s around query '%s'" , ds_query.str); |
2905 | |
2906 | /* Run the query */ |
2907 | if (mysql_real_query(mysql, ds_query.str, (ulong)ds_query.length)) |
2908 | { |
2909 | handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), |
2910 | mysql_sqlstate(mysql), &ds_res); |
2911 | /* If error was acceptable, return empty string */ |
2912 | dynstr_free(&ds_query); |
2913 | dynstr_free(&ds_col); |
2914 | eval_expr(var, "" , 0); |
2915 | DBUG_VOID_RETURN; |
2916 | } |
2917 | |
2918 | if (!(res= mysql_store_result(mysql))) |
2919 | { |
2920 | report_or_die("Query '%s' didn't return a result set" , ds_query.str); |
2921 | dynstr_free(&ds_query); |
2922 | dynstr_free(&ds_col); |
2923 | eval_expr(var, "" , 0); |
2924 | DBUG_VOID_RETURN; |
2925 | } |
2926 | |
2927 | { |
2928 | /* Find column number from the given column name */ |
2929 | uint i; |
2930 | uint num_fields= mysql_num_fields(res); |
2931 | MYSQL_FIELD *fields= mysql_fetch_fields(res); |
2932 | |
2933 | for (i= 0; i < num_fields; i++) |
2934 | { |
2935 | if (strcmp(fields[i].name, ds_col.str) == 0 && |
2936 | strlen(fields[i].name) == ds_col.length) |
2937 | { |
2938 | col_no= i; |
2939 | break; |
2940 | } |
2941 | } |
2942 | if (col_no == -1) |
2943 | { |
2944 | mysql_free_result(res); |
2945 | report_or_die("Could not find column '%s' in the result of '%s'" , |
2946 | ds_col.str, ds_query.str); |
2947 | dynstr_free(&ds_query); |
2948 | dynstr_free(&ds_col); |
2949 | DBUG_VOID_RETURN; |
2950 | } |
2951 | DBUG_PRINT("info" , ("Found column %d with name '%s'" , |
2952 | i, fields[i].name)); |
2953 | } |
2954 | dynstr_free(&ds_col); |
2955 | |
2956 | { |
2957 | /* Get the value */ |
2958 | MYSQL_ROW row; |
2959 | long rows= 0; |
2960 | const char* value= "No such row" ; |
2961 | |
2962 | while ((row= mysql_fetch_row(res))) |
2963 | { |
2964 | if (++rows == row_no) |
2965 | { |
2966 | |
2967 | DBUG_PRINT("info" , ("At row %ld, column %d is '%s'" , |
2968 | row_no, col_no, row[col_no])); |
2969 | /* Found the row to get */ |
2970 | if (row[col_no]) |
2971 | value= row[col_no]; |
2972 | else |
2973 | value= "NULL" ; |
2974 | |
2975 | break; |
2976 | } |
2977 | } |
2978 | eval_expr(var, value, 0, false, false); |
2979 | } |
2980 | dynstr_free(&ds_query); |
2981 | mysql_free_result(res); |
2982 | |
2983 | DBUG_VOID_RETURN; |
2984 | } |
2985 | |
2986 | |
2987 | void var_copy(VAR *dest, VAR *src) |
2988 | { |
2989 | dest->int_val= src->int_val; |
2990 | dest->is_int= src->is_int; |
2991 | dest->int_dirty= src->int_dirty; |
2992 | |
2993 | /* Alloc/realloc data for str_val in dest */ |
2994 | if (dest->alloced_len < src->alloced_len && |
2995 | !(dest->str_val= dest->str_val |
2996 | ? (char*)my_realloc(dest->str_val, src->alloced_len, MYF(MY_WME)) |
2997 | : (char*)my_malloc(src->alloced_len, MYF(MY_WME)))) |
2998 | die("Out of memory" ); |
2999 | else |
3000 | dest->alloced_len= src->alloced_len; |
3001 | |
3002 | /* Copy str_val data to dest */ |
3003 | dest->str_val_len= src->str_val_len; |
3004 | if (src->str_val_len) |
3005 | memcpy(dest->str_val, src->str_val, src->str_val_len); |
3006 | } |
3007 | |
3008 | |
3009 | void eval_expr(VAR *v, const char *p, const char **p_end, |
3010 | bool open_end, bool do_eval) |
3011 | { |
3012 | |
3013 | DBUG_ENTER("eval_expr" ); |
3014 | DBUG_PRINT("enter" , ("p: '%s'" , p)); |
3015 | |
3016 | /* Skip to treat as pure string if no evaluation */ |
3017 | if (! do_eval) |
3018 | goto NO_EVAL; |
3019 | |
3020 | if (*p == '$') |
3021 | { |
3022 | VAR *vp; |
3023 | const char* expected_end= *p_end; // Remember var end |
3024 | if ((vp= var_get(p, p_end, 0, 0))) |
3025 | var_copy(v, vp); |
3026 | |
3027 | /* Apparently it is not safe to assume null-terminated string */ |
3028 | v->str_val[v->str_val_len]= 0; |
3029 | |
3030 | /* Make sure there was just a $variable and nothing else */ |
3031 | const char* end= *p_end + 1; |
3032 | if (end < expected_end && !open_end) |
3033 | die("Found junk '%.*s' after $variable in expression" , |
3034 | (int)(expected_end - end - 1), end); |
3035 | |
3036 | DBUG_VOID_RETURN; |
3037 | } |
3038 | |
3039 | if (*p == '`') |
3040 | { |
3041 | var_query_set(v, p, p_end); |
3042 | DBUG_VOID_RETURN; |
3043 | } |
3044 | |
3045 | { |
3046 | /* Check if this is a "let $var= query_get_value()" */ |
3047 | const char* get_value_str= "query_get_value" ; |
3048 | const size_t len= strlen(get_value_str); |
3049 | if (strncmp(p, get_value_str, len)==0) |
3050 | { |
3051 | struct st_command command; |
3052 | memset(&command, 0, sizeof(command)); |
3053 | command.query= (char*)p; |
3054 | command.first_word_len= (int)len; |
3055 | command.first_argument= command.query + len; |
3056 | command.end= (char*)*p_end; |
3057 | command.abort_on_error= 1; /* avoid uninitialized variables */ |
3058 | var_set_query_get_value(&command, v); |
3059 | DBUG_VOID_RETURN; |
3060 | } |
3061 | } |
3062 | |
3063 | NO_EVAL: |
3064 | { |
3065 | size_t new_val_len = (p_end && *p_end) ? |
3066 | (size_t)(*p_end - p) : strlen(p); |
3067 | if (new_val_len + 1 >= v->alloced_len) |
3068 | { |
3069 | static size_t MIN_VAR_ALLOC= 32; |
3070 | v->alloced_len = (new_val_len < MIN_VAR_ALLOC - 1) ? |
3071 | MIN_VAR_ALLOC : new_val_len + 1; |
3072 | if (!(v->str_val = |
3073 | v->str_val ? |
3074 | (char*)my_realloc(v->str_val, v->alloced_len+1, MYF(MY_WME)) : |
3075 | (char*)my_malloc(v->alloced_len+1, MYF(MY_WME)))) |
3076 | die("Out of memory" ); |
3077 | } |
3078 | v->str_val_len = new_val_len; |
3079 | memcpy(v->str_val, p, new_val_len); |
3080 | v->str_val[new_val_len] = 0; |
3081 | var_check_int(v); |
3082 | } |
3083 | DBUG_VOID_RETURN; |
3084 | } |
3085 | |
3086 | |
3087 | bool open_and_set_current(const char *name) |
3088 | { |
3089 | FILE *opened= fopen(name, "rb" ); |
3090 | |
3091 | if (!opened) |
3092 | return false; |
3093 | |
3094 | cur_file++; |
3095 | cur_file->file= opened; |
3096 | cur_file->file_name= my_strdup(name, MYF(MY_FAE)); |
3097 | cur_file->lineno=1; |
3098 | return true; |
3099 | } |
3100 | |
3101 | |
3102 | void open_file(const char *name) |
3103 | { |
3104 | char buff[FN_REFLEN]; |
3105 | size_t length; |
3106 | char *curname= cur_file->file_name; |
3107 | DBUG_ENTER("open_file" ); |
3108 | DBUG_PRINT("enter" , ("name: %s" , name)); |
3109 | |
3110 | if (cur_file == file_stack_end) |
3111 | die("Source directives are nesting too deep" ); |
3112 | |
3113 | if (test_if_hard_path(name)) |
3114 | { |
3115 | if (open_and_set_current(name)) |
3116 | DBUG_VOID_RETURN; |
3117 | } |
3118 | else |
3119 | { |
3120 | /* |
3121 | if overlay-dir is specified, and the file is located somewhere |
3122 | under overlay-dir or under suite-dir, the search works as follows: |
3123 | |
3124 | 0.let suffix be current file dirname relative to siute-dir or overlay-dir |
3125 | 1.try in overlay-dir/suffix |
3126 | 2.try in suite-dir/suffix |
3127 | 3.try in overlay-dir |
3128 | 4.try in suite-dir |
3129 | 5.try in basedir |
3130 | |
3131 | consider an example: 'rty' overlay of the 'qwe' suite, |
3132 | file qwe/include/some.inc contains the line |
3133 | --source thing.inc |
3134 | we look for it in this order: |
3135 | 0.suffix is "include/" |
3136 | 1.try in rty/include/thing.inc |
3137 | 2.try in qwe/include/thing.inc |
3138 | 3.try in try/thing.inc | this is useful when t/a.test has |
3139 | 4.try in qwe/thing.inc | source include/b.inc; |
3140 | 5.try in mysql-test/include/thing.inc |
3141 | |
3142 | otherwise the search is as follows |
3143 | 1.try in current file dirname |
3144 | 3.try in overlay-dir (if any) |
3145 | 4.try in suite-dir |
3146 | 5.try in basedir |
3147 | */ |
3148 | |
3149 | fix_win_paths(curname, sizeof(curname)); |
3150 | |
3151 | bool in_overlay= opt_overlay_dir && |
3152 | !strncmp(curname, opt_overlay_dir, overlay_dir_len); |
3153 | bool in_suiteir= opt_overlay_dir && !in_overlay && |
3154 | !strncmp(curname, opt_suite_dir, suite_dir_len); |
3155 | if (in_overlay || in_suiteir) |
3156 | { |
3157 | size_t prefix_len = in_overlay ? overlay_dir_len : suite_dir_len; |
3158 | char buf2[FN_REFLEN], *suffix= buf2 + prefix_len; |
3159 | dirname_part(buf2, curname, &length); |
3160 | |
3161 | /* 1. first we look in the overlay dir */ |
3162 | strxnmov(buff, sizeof(buff), opt_overlay_dir, suffix, name, NullS); |
3163 | |
3164 | /* |
3165 | Overlayed rty/include/thing.inc can contain the line |
3166 | --source thing.inc |
3167 | which would mean to include qwe/include/thing.inc. |
3168 | But it looks like including "itself", so don't try to open the file, |
3169 | if buff contains the same file name as curname. |
3170 | */ |
3171 | if (strcmp(buff, curname) && open_and_set_current(buff)) |
3172 | DBUG_VOID_RETURN; |
3173 | |
3174 | /* 2. if that failed, we look in the suite dir */ |
3175 | strxnmov(buff, sizeof(buff), opt_suite_dir, suffix, name, NullS); |
3176 | |
3177 | /* buff can not be equal to curname, as a file can never include itself */ |
3178 | if (open_and_set_current(buff)) |
3179 | DBUG_VOID_RETURN; |
3180 | } |
3181 | else |
3182 | { |
3183 | /* 1. try in current file dirname */ |
3184 | dirname_part(buff, curname, &length); |
3185 | strxnmov(buff, sizeof(buff), buff, name, NullS); |
3186 | if (open_and_set_current(buff)) |
3187 | DBUG_VOID_RETURN; |
3188 | } |
3189 | |
3190 | /* 3. now, look in the overlay dir */ |
3191 | if (opt_overlay_dir) |
3192 | { |
3193 | strxmov(buff, opt_overlay_dir, name, NullS); |
3194 | if (open_and_set_current(buff)) |
3195 | DBUG_VOID_RETURN; |
3196 | } |
3197 | |
3198 | /* 4. if that failed - look in the suite dir */ |
3199 | strxmov(buff, opt_suite_dir, name, NullS); |
3200 | if (open_and_set_current(buff)) |
3201 | DBUG_VOID_RETURN; |
3202 | |
3203 | /* 5. the last resort - look in the base dir */ |
3204 | strxnmov(buff, sizeof(buff), opt_basedir, name, NullS); |
3205 | if (open_and_set_current(buff)) |
3206 | DBUG_VOID_RETURN; |
3207 | } |
3208 | |
3209 | die("Could not open '%s' for reading, errno: %d" , name, errno); |
3210 | DBUG_VOID_RETURN; |
3211 | } |
3212 | |
3213 | |
3214 | /* |
3215 | Source and execute the given file |
3216 | |
3217 | SYNOPSIS |
3218 | do_source() |
3219 | query called command |
3220 | |
3221 | DESCRIPTION |
3222 | source <file_name> |
3223 | |
3224 | Open the file <file_name> and execute it |
3225 | |
3226 | */ |
3227 | |
3228 | void do_source(struct st_command *command) |
3229 | { |
3230 | static DYNAMIC_STRING ds_filename; |
3231 | const struct command_arg source_args[] = { |
3232 | { "filename" , ARG_STRING, TRUE, &ds_filename, "File to source" } |
3233 | }; |
3234 | DBUG_ENTER("do_source" ); |
3235 | |
3236 | check_command_args(command, command->first_argument, source_args, |
3237 | sizeof(source_args)/sizeof(struct command_arg), |
3238 | ' '); |
3239 | |
3240 | /* |
3241 | If this file has already been sourced, don't source it again. |
3242 | It's already available in the q_lines cache. |
3243 | */ |
3244 | if (parser.current_line < (parser.read_lines - 1)) |
3245 | ; /* Do nothing */ |
3246 | else |
3247 | { |
3248 | DBUG_PRINT("info" , ("sourcing file: %s" , ds_filename.str)); |
3249 | open_file(ds_filename.str); |
3250 | } |
3251 | |
3252 | dynstr_free(&ds_filename); |
3253 | DBUG_VOID_RETURN; |
3254 | } |
3255 | |
3256 | |
3257 | #if defined _WIN32 |
3258 | |
3259 | #ifdef USE_CYGWIN |
3260 | /* Variables used for temporary sh files used for emulating Unix on Windows */ |
3261 | char tmp_sh_name[64], tmp_sh_cmd[70]; |
3262 | #endif |
3263 | |
3264 | void init_tmp_sh_file() |
3265 | { |
3266 | #ifdef USE_CYGWIN |
3267 | /* Format a name for the tmp sh file that is unique for this process */ |
3268 | my_snprintf(tmp_sh_name, sizeof(tmp_sh_name), "tmp_%d.sh" , getpid()); |
3269 | /* Format the command to execute in order to run the script */ |
3270 | my_snprintf(tmp_sh_cmd, sizeof(tmp_sh_cmd), "sh %s" , tmp_sh_name); |
3271 | #endif |
3272 | } |
3273 | |
3274 | |
3275 | void free_tmp_sh_file() |
3276 | { |
3277 | #ifdef USE_CYGWIN |
3278 | my_delete(tmp_sh_name, MYF(0)); |
3279 | #endif |
3280 | } |
3281 | #endif |
3282 | |
3283 | |
3284 | static void init_builtin_echo(void) |
3285 | { |
3286 | #ifdef _WIN32 |
3287 | size_t echo_length; |
3288 | |
3289 | /* Look for "echo.exe" in same dir as mysqltest was started from */ |
3290 | dirname_part(builtin_echo, my_progname, &echo_length); |
3291 | fn_format(builtin_echo, ".\\echo.exe" , |
3292 | builtin_echo, "" , MYF(MY_REPLACE_DIR)); |
3293 | |
3294 | /* Make sure echo.exe exists */ |
3295 | if (access(builtin_echo, F_OK) != 0) |
3296 | builtin_echo[0]= 0; |
3297 | return; |
3298 | |
3299 | #else |
3300 | |
3301 | builtin_echo[0]= 0; |
3302 | return; |
3303 | |
3304 | #endif |
3305 | } |
3306 | |
3307 | |
3308 | /* |
3309 | Replace a substring |
3310 | |
3311 | SYNOPSIS |
3312 | replace |
3313 | ds_str The string to search and perform the replace in |
3314 | search_str The string to search for |
3315 | search_len Length of the string to search for |
3316 | replace_str The string to replace with |
3317 | replace_len Length of the string to replace with |
3318 | |
3319 | RETURN |
3320 | 0 String replaced |
3321 | 1 Could not find search_str in str |
3322 | */ |
3323 | |
3324 | static int replace(DYNAMIC_STRING *ds_str, |
3325 | const char *search_str, size_t search_len, |
3326 | const char *replace_str, size_t replace_len) |
3327 | { |
3328 | DYNAMIC_STRING ds_tmp; |
3329 | const char *start= strstr(ds_str->str, search_str); |
3330 | if (!start) |
3331 | return 1; |
3332 | init_dynamic_string(&ds_tmp, "" , |
3333 | ds_str->length + replace_len, 256); |
3334 | dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str); |
3335 | dynstr_append_mem(&ds_tmp, replace_str, replace_len); |
3336 | dynstr_append(&ds_tmp, start + search_len); |
3337 | dynstr_set(ds_str, ds_tmp.str); |
3338 | dynstr_free(&ds_tmp); |
3339 | return 0; |
3340 | } |
3341 | |
3342 | |
3343 | /* |
3344 | Execute given command. |
3345 | |
3346 | SYNOPSIS |
3347 | do_exec() |
3348 | query called command |
3349 | |
3350 | DESCRIPTION |
3351 | exec <command> |
3352 | |
3353 | Execute the text between exec and end of line in a subprocess. |
3354 | The error code returned from the subprocess is checked against the |
3355 | expected error array, previously set with the --error command. |
3356 | It can thus be used to execute a command that shall fail. |
3357 | |
3358 | NOTE |
3359 | Although mysqltest is executed from cygwin shell, the command will be |
3360 | executed in "cmd.exe". Thus commands like "rm" etc can NOT be used, use |
3361 | mysqltest command(s) like "remove_file" for that |
3362 | */ |
3363 | |
3364 | void do_exec(struct st_command *command) |
3365 | { |
3366 | int error; |
3367 | char buf[512]; |
3368 | FILE *res_file; |
3369 | char *cmd= command->first_argument; |
3370 | DYNAMIC_STRING ds_cmd; |
3371 | DYNAMIC_STRING ds_sorted, *ds_result; |
3372 | DBUG_ENTER("do_exec" ); |
3373 | DBUG_PRINT("enter" , ("cmd: '%s'" , cmd)); |
3374 | |
3375 | var_set_int("$sys_errno" ,0); |
3376 | |
3377 | /* Skip leading space */ |
3378 | while (*cmd && my_isspace(charset_info, *cmd)) |
3379 | cmd++; |
3380 | if (!*cmd) |
3381 | { |
3382 | report_or_die("Missing argument in exec" ); |
3383 | DBUG_VOID_RETURN; |
3384 | } |
3385 | command->last_argument= command->end; |
3386 | |
3387 | init_dynamic_string(&ds_cmd, 0, command->query_len+256, 256); |
3388 | /* Eval the command, thus replacing all environment variables */ |
3389 | do_eval(&ds_cmd, cmd, command->end, !is_windows); |
3390 | |
3391 | /* Check if echo should be replaced with "builtin" echo */ |
3392 | if (builtin_echo[0] && strncmp(cmd, "echo" , 4) == 0) |
3393 | { |
3394 | /* Replace echo with our "builtin" echo */ |
3395 | replace(&ds_cmd, "echo" , 4, builtin_echo, strlen(builtin_echo)); |
3396 | } |
3397 | |
3398 | #ifdef _WIN32 |
3399 | #ifndef USE_CYGWIN |
3400 | /* Replace /dev/null with NUL */ |
3401 | while(replace(&ds_cmd, "/dev/null" , 9, "NUL" , 3) == 0) |
3402 | ; |
3403 | /* Replace "closed stdout" with non existing output fd */ |
3404 | while(replace(&ds_cmd, ">&-" , 3, ">&4" , 3) == 0) |
3405 | ; |
3406 | #endif |
3407 | #endif |
3408 | |
3409 | if (disable_result_log) |
3410 | { |
3411 | /* Collect stderr output as well, for the case app. crashes or returns error.*/ |
3412 | dynstr_append(&ds_cmd, " 2>&1" ); |
3413 | } |
3414 | |
3415 | DBUG_PRINT("info" , ("Executing '%s' as '%s'" , |
3416 | command->first_argument, ds_cmd.str)); |
3417 | |
3418 | if (!(res_file= my_popen(ds_cmd.str, "r" ))) |
3419 | { |
3420 | dynstr_free(&ds_cmd); |
3421 | if (command->abort_on_error) |
3422 | report_or_die("popen(\"%s\", \"r\") failed" , command->first_argument); |
3423 | DBUG_VOID_RETURN; |
3424 | } |
3425 | |
3426 | ds_result= &ds_res; |
3427 | if (display_result_sorted) |
3428 | { |
3429 | init_dynamic_string(&ds_sorted, "" , 1024, 1024); |
3430 | ds_result= &ds_sorted; |
3431 | } |
3432 | int len; |
3433 | while (my_fgets(buf, sizeof(buf), res_file,&len)) |
3434 | { |
3435 | replace_dynstr_append_mem(ds_result, buf, len); |
3436 | } |
3437 | error= pclose(res_file); |
3438 | |
3439 | if (display_result_sorted) |
3440 | { |
3441 | dynstr_append_sorted(&ds_res, &ds_sorted, 0); |
3442 | dynstr_free(&ds_sorted); |
3443 | } |
3444 | |
3445 | if (error) |
3446 | { |
3447 | uint status= WEXITSTATUS(error); |
3448 | int i; |
3449 | |
3450 | if (command->abort_on_error) |
3451 | { |
3452 | report_or_die("exec of '%s' failed, error: %d, status: %d, errno: %d\n" |
3453 | "Output from before failure:\n%s\n" , |
3454 | ds_cmd.str, error, status, errno, |
3455 | ds_res.str); |
3456 | dynstr_free(&ds_cmd); |
3457 | DBUG_VOID_RETURN; |
3458 | } |
3459 | |
3460 | DBUG_PRINT("info" , |
3461 | ("error: %d, status: %d" , error, status)); |
3462 | |
3463 | i= match_expected_error(command, status, NULL); |
3464 | |
3465 | if (i >= 0) |
3466 | DBUG_PRINT("info" , ("command \"%s\" failed with expected error: %d" , |
3467 | command->first_argument, status)); |
3468 | else |
3469 | { |
3470 | dynstr_free(&ds_cmd); |
3471 | if (command->expected_errors.count > 0) |
3472 | report_or_die("command \"%s\" failed with wrong error: %d" , |
3473 | command->first_argument, status); |
3474 | } |
3475 | var_set_int("$sys_errno" ,status); |
3476 | } |
3477 | else if (command->expected_errors.err[0].type == ERR_ERRNO && |
3478 | command->expected_errors.err[0].code.errnum != 0) |
3479 | { |
3480 | /* Error code we wanted was != 0, i.e. not an expected success */ |
3481 | log_msg("exec of '%s failed, error: %d, errno: %d" , |
3482 | ds_cmd.str, error, errno); |
3483 | dynstr_free(&ds_cmd); |
3484 | report_or_die("command \"%s\" succeeded - should have failed with " |
3485 | "errno %d..." , |
3486 | command->first_argument, |
3487 | command->expected_errors.err[0].code.errnum); |
3488 | } |
3489 | |
3490 | dynstr_free(&ds_cmd); |
3491 | |
3492 | if (disable_result_log) |
3493 | { |
3494 | /* Disable output in case of successful exit.*/ |
3495 | dynstr_set(&ds_res,"" ); |
3496 | } |
3497 | DBUG_VOID_RETURN; |
3498 | } |
3499 | |
3500 | enum enum_operator |
3501 | { |
3502 | DO_DEC, |
3503 | DO_INC |
3504 | }; |
3505 | |
3506 | |
3507 | /* |
3508 | Decrease or increase the value of a variable |
3509 | |
3510 | SYNOPSIS |
3511 | do_modify_var() |
3512 | query called command |
3513 | op operation to perform on the var |
3514 | |
3515 | DESCRIPTION |
3516 | dec $var_name |
3517 | inc $var_name |
3518 | |
3519 | */ |
3520 | |
3521 | int do_modify_var(struct st_command *command, |
3522 | enum enum_operator op) |
3523 | { |
3524 | const char *p= command->first_argument; |
3525 | VAR* v; |
3526 | if (!*p) |
3527 | die("Missing argument to %.*s" , command->first_word_len, |
3528 | command->query); |
3529 | if (*p != '$') |
3530 | die("The argument to %.*s must be a variable (start with $)" , |
3531 | command->first_word_len, command->query); |
3532 | v= var_get(p, &p, 1, 0); |
3533 | if (! v->is_int) |
3534 | die("Cannot perform inc/dec on a non-numeric value" ); |
3535 | switch (op) { |
3536 | case DO_DEC: |
3537 | v->int_val--; |
3538 | break; |
3539 | case DO_INC: |
3540 | v->int_val++; |
3541 | break; |
3542 | default: |
3543 | die("Invalid operator to do_modify_var" ); |
3544 | break; |
3545 | } |
3546 | v->int_dirty= true; |
3547 | command->last_argument= (char*)++p; |
3548 | return 0; |
3549 | } |
3550 | |
3551 | |
3552 | /* |
3553 | Wrapper for 'system' function |
3554 | |
3555 | NOTE |
3556 | If mysqltest is executed from cygwin shell, the command will be |
3557 | executed in the "windows command interpreter" cmd.exe and we prepend "sh" |
3558 | to make it be executed by cygwins "bash". Thus commands like "rm", |
3559 | "mkdir" as well as shellscripts can executed by "system" in Windows. |
3560 | |
3561 | */ |
3562 | |
3563 | int my_system(DYNAMIC_STRING* ds_cmd) |
3564 | { |
3565 | #if defined _WIN32 && defined USE_CYGWIN |
3566 | /* Dump the command into a sh script file and execute with system */ |
3567 | str_to_file(tmp_sh_name, ds_cmd->str, ds_cmd->length); |
3568 | return system(tmp_sh_cmd); |
3569 | #else |
3570 | return system(ds_cmd->str); |
3571 | #endif |
3572 | } |
3573 | |
3574 | |
3575 | /* |
3576 | SYNOPSIS |
3577 | do_system |
3578 | command called command |
3579 | |
3580 | DESCRIPTION |
3581 | system <command> |
3582 | |
3583 | Eval the query to expand any $variables in the command. |
3584 | Execute the command with the "system" command. |
3585 | |
3586 | */ |
3587 | |
3588 | void do_system(struct st_command *command) |
3589 | { |
3590 | DYNAMIC_STRING ds_cmd; |
3591 | DBUG_ENTER("do_system" ); |
3592 | |
3593 | if (strlen(command->first_argument) == 0) |
3594 | { |
3595 | report_or_die("Missing arguments to system, nothing to do!" ); |
3596 | DBUG_VOID_RETURN; |
3597 | } |
3598 | |
3599 | init_dynamic_string(&ds_cmd, 0, command->query_len + 64, 256); |
3600 | |
3601 | /* Eval the system command, thus replacing all environment variables */ |
3602 | do_eval(&ds_cmd, command->first_argument, command->end, !is_windows); |
3603 | |
3604 | #ifdef _WIN32 |
3605 | #ifndef USE_CYGWIN |
3606 | /* Replace /dev/null with NUL */ |
3607 | while(replace(&ds_cmd, "/dev/null" , 9, "NUL" , 3) == 0) |
3608 | ; |
3609 | #endif |
3610 | #endif |
3611 | |
3612 | |
3613 | DBUG_PRINT("info" , ("running system command '%s' as '%s'" , |
3614 | command->first_argument, ds_cmd.str)); |
3615 | if (my_system(&ds_cmd)) |
3616 | { |
3617 | if (command->abort_on_error) |
3618 | report_or_die("system command '%s' failed" , command->first_argument); |
3619 | else |
3620 | { |
3621 | /* If ! abort_on_error, log message and continue */ |
3622 | dynstr_append(&ds_res, "system command '" ); |
3623 | replace_dynstr_append(&ds_res, command->first_argument); |
3624 | dynstr_append(&ds_res, "' failed\n" ); |
3625 | } |
3626 | } |
3627 | |
3628 | command->last_argument= command->end; |
3629 | dynstr_free(&ds_cmd); |
3630 | DBUG_VOID_RETURN; |
3631 | } |
3632 | |
3633 | |
3634 | /* returns TRUE if path is inside a sandbox */ |
3635 | bool is_sub_path(const char *path, size_t plen, const char *sandbox) |
3636 | { |
3637 | size_t len= strlen(sandbox); |
3638 | if (!sandbox || !len || plen <= len || memcmp(path, sandbox, len - 1) |
3639 | || path[len] != '/') |
3640 | return false; |
3641 | return true; |
3642 | } |
3643 | |
3644 | |
3645 | /* returns TRUE if path cannot be modified */ |
3646 | bool bad_path(const char *path) |
3647 | { |
3648 | size_t plen= strlen(path); |
3649 | |
3650 | const char *vardir= getenv("MYSQLTEST_VARDIR" ); |
3651 | if (is_sub_path(path, plen, vardir)) |
3652 | return false; |
3653 | |
3654 | const char *tmpdir= getenv("MYSQL_TMP_DIR" ); |
3655 | if (is_sub_path(path, plen, tmpdir)) |
3656 | return false; |
3657 | |
3658 | report_or_die("Path '%s' is not a subdirectory of MYSQLTEST_VARDIR '%s'" |
3659 | "or MYSQL_TMP_DIR '%s'" , |
3660 | path, vardir, tmpdir); |
3661 | return true; |
3662 | } |
3663 | |
3664 | |
3665 | /* |
3666 | SYNOPSIS |
3667 | set_wild_chars |
3668 | set true to set * etc. as wild char, false to reset |
3669 | |
3670 | DESCRIPTION |
3671 | Auxiliary function to set "our" wild chars before calling wild_compare |
3672 | This is needed because the default values are changed to SQL syntax |
3673 | in mysqltest_embedded. |
3674 | */ |
3675 | |
3676 | void set_wild_chars (my_bool set) |
3677 | { |
3678 | static char old_many= 0, old_one, old_prefix; |
3679 | |
3680 | if (set) |
3681 | { |
3682 | if (wild_many == '*') return; // No need |
3683 | old_many= wild_many; |
3684 | old_one= wild_one; |
3685 | old_prefix= wild_prefix; |
3686 | wild_many= '*'; |
3687 | wild_one= '?'; |
3688 | wild_prefix= 0; |
3689 | } |
3690 | else |
3691 | { |
3692 | if (! old_many) return; // Was not set |
3693 | wild_many= old_many; |
3694 | wild_one= old_one; |
3695 | wild_prefix= old_prefix; |
3696 | } |
3697 | } |
3698 | |
3699 | |
3700 | /* |
3701 | SYNOPSIS |
3702 | do_remove_file |
3703 | command called command |
3704 | |
3705 | DESCRIPTION |
3706 | remove_file <file_name> |
3707 | Remove the file <file_name> |
3708 | */ |
3709 | |
3710 | void do_remove_file(struct st_command *command) |
3711 | { |
3712 | int error; |
3713 | static DYNAMIC_STRING ds_filename; |
3714 | const struct command_arg rm_args[] = { |
3715 | { "filename" , ARG_STRING, TRUE, &ds_filename, "File to delete" } |
3716 | }; |
3717 | DBUG_ENTER("do_remove_file" ); |
3718 | |
3719 | check_command_args(command, command->first_argument, |
3720 | rm_args, sizeof(rm_args)/sizeof(struct command_arg), |
3721 | ' '); |
3722 | |
3723 | if (bad_path(ds_filename.str)) |
3724 | DBUG_VOID_RETURN; |
3725 | |
3726 | DBUG_PRINT("info" , ("removing file: %s" , ds_filename.str)); |
3727 | error= my_delete(ds_filename.str, MYF(disable_warnings ? 0 : MY_WME)) != 0; |
3728 | handle_command_error(command, error, my_errno); |
3729 | dynstr_free(&ds_filename); |
3730 | DBUG_VOID_RETURN; |
3731 | } |
3732 | |
3733 | |
3734 | /* |
3735 | SYNOPSIS |
3736 | do_remove_files_wildcard |
3737 | command called command |
3738 | |
3739 | DESCRIPTION |
3740 | remove_files_wildcard <directory> [<file_name_pattern>] |
3741 | Remove the files in <directory> optionally matching <file_name_pattern> |
3742 | */ |
3743 | |
3744 | void do_remove_files_wildcard(struct st_command *command) |
3745 | { |
3746 | int error= 0, sys_errno= 0; |
3747 | uint i; |
3748 | size_t directory_length; |
3749 | MY_DIR *dir_info; |
3750 | FILEINFO *file; |
3751 | char dir_separator[2]; |
3752 | static DYNAMIC_STRING ds_directory; |
3753 | static DYNAMIC_STRING ds_wild; |
3754 | static DYNAMIC_STRING ds_file_to_remove; |
3755 | char dirname[FN_REFLEN]; |
3756 | |
3757 | const struct command_arg rm_args[] = { |
3758 | { "directory" , ARG_STRING, TRUE, &ds_directory, |
3759 | "Directory containing files to delete" }, |
3760 | { "filename" , ARG_STRING, FALSE, &ds_wild, "File pattern to delete" } |
3761 | }; |
3762 | DBUG_ENTER("do_remove_files_wildcard" ); |
3763 | |
3764 | check_command_args(command, command->first_argument, |
3765 | rm_args, sizeof(rm_args)/sizeof(struct command_arg), |
3766 | ' '); |
3767 | fn_format(dirname, ds_directory.str, "" , "" , MY_UNPACK_FILENAME); |
3768 | |
3769 | if (bad_path(ds_directory.str)) |
3770 | DBUG_VOID_RETURN; |
3771 | |
3772 | DBUG_PRINT("info" , ("listing directory: %s" , dirname)); |
3773 | if (!(dir_info= my_dir(dirname, MYF(MY_DONT_SORT | MY_WANT_STAT | MY_WME)))) |
3774 | { |
3775 | error= 1; |
3776 | sys_errno= my_errno; |
3777 | goto end; |
3778 | } |
3779 | init_dynamic_string(&ds_file_to_remove, dirname, 1024, 1024); |
3780 | dir_separator[0]= FN_LIBCHAR; |
3781 | dynstr_append_mem(&ds_file_to_remove, dir_separator, 1); |
3782 | directory_length= ds_file_to_remove.length; |
3783 | |
3784 | /* Set default wild chars for wild_compare, is changed in embedded mode */ |
3785 | set_wild_chars(1); |
3786 | |
3787 | for (i= 0; i < (uint) dir_info->number_of_files; i++) |
3788 | { |
3789 | file= dir_info->dir_entry + i; |
3790 | /* Remove only regular files, i.e. no directories etc. */ |
3791 | /* if (!MY_S_ISREG(file->mystat->st_mode)) */ |
3792 | /* MY_S_ISREG does not work here on Windows, just skip directories */ |
3793 | if (MY_S_ISDIR(file->mystat->st_mode)) |
3794 | continue; |
3795 | if (ds_wild.length && |
3796 | wild_compare(file->name, ds_wild.str, 0)) |
3797 | continue; |
3798 | ds_file_to_remove.length= directory_length; |
3799 | dynstr_append(&ds_file_to_remove, file->name); |
3800 | DBUG_PRINT("info" , ("removing file: %s" , ds_file_to_remove.str)); |
3801 | if ((error= (my_delete(ds_file_to_remove.str, MYF(MY_WME)) != 0))) |
3802 | sys_errno= my_errno; |
3803 | if (error) |
3804 | break; |
3805 | } |
3806 | set_wild_chars(0); |
3807 | my_dirend(dir_info); |
3808 | |
3809 | end: |
3810 | handle_command_error(command, error, sys_errno); |
3811 | dynstr_free(&ds_directory); |
3812 | dynstr_free(&ds_wild); |
3813 | dynstr_free(&ds_file_to_remove); |
3814 | DBUG_VOID_RETURN; |
3815 | } |
3816 | |
3817 | |
3818 | /* |
3819 | SYNOPSIS |
3820 | do_copy_file |
3821 | command command handle |
3822 | |
3823 | DESCRIPTION |
3824 | copy_file <from_file> <to_file> |
3825 | Copy <from_file> to <to_file> |
3826 | |
3827 | NOTE! Will fail if <to_file> exists |
3828 | */ |
3829 | |
3830 | void do_copy_file(struct st_command *command) |
3831 | { |
3832 | int error; |
3833 | static DYNAMIC_STRING ds_from_file; |
3834 | static DYNAMIC_STRING ds_to_file; |
3835 | const struct command_arg copy_file_args[] = { |
3836 | { "from_file" , ARG_STRING, TRUE, &ds_from_file, "Filename to copy from" }, |
3837 | { "to_file" , ARG_STRING, TRUE, &ds_to_file, "Filename to copy to" } |
3838 | }; |
3839 | DBUG_ENTER("do_copy_file" ); |
3840 | |
3841 | check_command_args(command, command->first_argument, |
3842 | copy_file_args, |
3843 | sizeof(copy_file_args)/sizeof(struct command_arg), |
3844 | ' '); |
3845 | |
3846 | if (bad_path(ds_to_file.str)) |
3847 | DBUG_VOID_RETURN; |
3848 | |
3849 | DBUG_PRINT("info" , ("Copy %s to %s" , ds_from_file.str, ds_to_file.str)); |
3850 | /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */ |
3851 | error= (my_copy(ds_from_file.str, ds_to_file.str, |
3852 | MYF(MY_DONT_OVERWRITE_FILE | MY_WME | MY_HOLD_ORIGINAL_MODES)) != 0); |
3853 | handle_command_error(command, error, my_errno); |
3854 | dynstr_free(&ds_from_file); |
3855 | dynstr_free(&ds_to_file); |
3856 | DBUG_VOID_RETURN; |
3857 | } |
3858 | |
3859 | |
3860 | /* |
3861 | SYNOPSIS |
3862 | do_move_file |
3863 | command command handle |
3864 | |
3865 | DESCRIPTION |
3866 | move_file <from_file> <to_file> |
3867 | Move <from_file> to <to_file> |
3868 | */ |
3869 | |
3870 | void do_move_file(struct st_command *command) |
3871 | { |
3872 | int error; |
3873 | static DYNAMIC_STRING ds_from_file; |
3874 | static DYNAMIC_STRING ds_to_file; |
3875 | const struct command_arg move_file_args[] = { |
3876 | { "from_file" , ARG_STRING, TRUE, &ds_from_file, "Filename to move from" }, |
3877 | { "to_file" , ARG_STRING, TRUE, &ds_to_file, "Filename to move to" } |
3878 | }; |
3879 | DBUG_ENTER("do_move_file" ); |
3880 | |
3881 | check_command_args(command, command->first_argument, |
3882 | move_file_args, |
3883 | sizeof(move_file_args)/sizeof(struct command_arg), |
3884 | ' '); |
3885 | |
3886 | if (bad_path(ds_to_file.str)) |
3887 | DBUG_VOID_RETURN; |
3888 | |
3889 | DBUG_PRINT("info" , ("Move %s to %s" , ds_from_file.str, ds_to_file.str)); |
3890 | error= (my_rename(ds_from_file.str, ds_to_file.str, |
3891 | MYF(disable_warnings ? 0 : MY_WME)) != 0); |
3892 | handle_command_error(command, error, my_errno); |
3893 | dynstr_free(&ds_from_file); |
3894 | dynstr_free(&ds_to_file); |
3895 | DBUG_VOID_RETURN; |
3896 | } |
3897 | |
3898 | |
3899 | /* |
3900 | SYNOPSIS |
3901 | do_chmod_file |
3902 | command command handle |
3903 | |
3904 | DESCRIPTION |
3905 | chmod <octal> <file_name> |
3906 | Change file permission of <file_name> |
3907 | |
3908 | */ |
3909 | |
3910 | void do_chmod_file(struct st_command *command) |
3911 | { |
3912 | long mode= 0; |
3913 | int err_code; |
3914 | static DYNAMIC_STRING ds_mode; |
3915 | static DYNAMIC_STRING ds_file; |
3916 | const struct command_arg chmod_file_args[] = { |
3917 | { "mode" , ARG_STRING, TRUE, &ds_mode, "Mode of file(octal) ex. 0660" }, |
3918 | { "filename" , ARG_STRING, TRUE, &ds_file, "Filename of file to modify" } |
3919 | }; |
3920 | DBUG_ENTER("do_chmod_file" ); |
3921 | |
3922 | check_command_args(command, command->first_argument, |
3923 | chmod_file_args, |
3924 | sizeof(chmod_file_args)/sizeof(struct command_arg), |
3925 | ' '); |
3926 | |
3927 | if (bad_path(ds_file.str)) |
3928 | DBUG_VOID_RETURN; |
3929 | |
3930 | /* Parse what mode to set */ |
3931 | if (ds_mode.length != 4 || |
3932 | str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS) |
3933 | die("You must write a 4 digit octal number for mode" ); |
3934 | |
3935 | DBUG_PRINT("info" , ("chmod %o %s" , (uint)mode, ds_file.str)); |
3936 | err_code= chmod(ds_file.str, mode); |
3937 | if (err_code < 0) |
3938 | err_code= 1; |
3939 | handle_command_error(command, err_code, errno); |
3940 | dynstr_free(&ds_mode); |
3941 | dynstr_free(&ds_file); |
3942 | DBUG_VOID_RETURN; |
3943 | } |
3944 | |
3945 | |
3946 | /* |
3947 | SYNOPSIS |
3948 | do_file_exists |
3949 | command called command |
3950 | |
3951 | DESCRIPTION |
3952 | fiile_exist <file_name> |
3953 | Check if file <file_name> exists |
3954 | */ |
3955 | |
3956 | void do_file_exist(struct st_command *command) |
3957 | { |
3958 | int error; |
3959 | static DYNAMIC_STRING ds_filename; |
3960 | const struct command_arg file_exist_args[] = { |
3961 | { "filename" , ARG_STRING, TRUE, &ds_filename, "File to check if it exist" } |
3962 | }; |
3963 | DBUG_ENTER("do_file_exist" ); |
3964 | |
3965 | check_command_args(command, command->first_argument, |
3966 | file_exist_args, |
3967 | sizeof(file_exist_args)/sizeof(struct command_arg), |
3968 | ' '); |
3969 | |
3970 | DBUG_PRINT("info" , ("Checking for existence of file: %s" , ds_filename.str)); |
3971 | error= (access(ds_filename.str, F_OK) != 0); |
3972 | handle_command_error(command, error, errno); |
3973 | dynstr_free(&ds_filename); |
3974 | DBUG_VOID_RETURN; |
3975 | } |
3976 | |
3977 | |
3978 | /* |
3979 | SYNOPSIS |
3980 | do_mkdir |
3981 | command called command |
3982 | |
3983 | DESCRIPTION |
3984 | mkdir <dir_name> |
3985 | Create the directory <dir_name> |
3986 | */ |
3987 | |
3988 | void do_mkdir(struct st_command *command) |
3989 | { |
3990 | int error; |
3991 | static DYNAMIC_STRING ds_dirname; |
3992 | const struct command_arg mkdir_args[] = { |
3993 | {"dirname" , ARG_STRING, TRUE, &ds_dirname, "Directory to create" } |
3994 | }; |
3995 | DBUG_ENTER("do_mkdir" ); |
3996 | |
3997 | check_command_args(command, command->first_argument, |
3998 | mkdir_args, sizeof(mkdir_args)/sizeof(struct command_arg), |
3999 | ' '); |
4000 | |
4001 | if (bad_path(ds_dirname.str)) |
4002 | DBUG_VOID_RETURN; |
4003 | |
4004 | DBUG_PRINT("info" , ("creating directory: %s" , ds_dirname.str)); |
4005 | error= my_mkdir(ds_dirname.str, 0777, MYF(MY_WME)) != 0; |
4006 | handle_command_error(command, error, my_errno); |
4007 | dynstr_free(&ds_dirname); |
4008 | DBUG_VOID_RETURN; |
4009 | } |
4010 | |
4011 | |
4012 | /* |
4013 | Remove directory recursively. |
4014 | */ |
4015 | static int rmtree(const char *dir) |
4016 | { |
4017 | char path[FN_REFLEN]; |
4018 | char sep[]={ FN_LIBCHAR, 0 }; |
4019 | int err=0; |
4020 | |
4021 | MY_DIR *dir_info= my_dir(dir, MYF(MY_DONT_SORT | MY_WANT_STAT)); |
4022 | if (!dir_info) |
4023 | return 1; |
4024 | |
4025 | for (uint i= 0; i < dir_info->number_of_files; i++) |
4026 | { |
4027 | FILEINFO *file= dir_info->dir_entry + i; |
4028 | /* Skip "." and ".." */ |
4029 | if (!strcmp(file->name, "." ) || !strcmp(file->name, ".." )) |
4030 | continue; |
4031 | |
4032 | strxnmov(path, sizeof(path), dir, sep, file->name, NULL); |
4033 | |
4034 | if (!MY_S_ISDIR(file->mystat->st_mode)) |
4035 | { |
4036 | err= my_delete(path, 0); |
4037 | #ifdef _WIN32 |
4038 | /* |
4039 | On Windows, check and possible reset readonly attribute. |
4040 | my_delete(), or DeleteFile does not remove theses files. |
4041 | */ |
4042 | if (err) |
4043 | { |
4044 | DWORD attr= GetFileAttributes(path); |
4045 | if (attr != INVALID_FILE_ATTRIBUTES && |
4046 | (attr & FILE_ATTRIBUTE_READONLY)) |
4047 | { |
4048 | SetFileAttributes(path, attr &~ FILE_ATTRIBUTE_READONLY); |
4049 | err= my_delete(path, 0); |
4050 | } |
4051 | } |
4052 | #endif |
4053 | } |
4054 | else |
4055 | err= rmtree(path); |
4056 | |
4057 | if(err) |
4058 | break; |
4059 | } |
4060 | |
4061 | my_dirend(dir_info); |
4062 | |
4063 | if (!err) |
4064 | err= rmdir(dir); |
4065 | |
4066 | return err; |
4067 | } |
4068 | |
4069 | |
4070 | /* |
4071 | SYNOPSIS |
4072 | do_rmdir |
4073 | command called command |
4074 | |
4075 | DESCRIPTION |
4076 | rmdir <dir_name> |
4077 | Remove the directory tree |
4078 | */ |
4079 | |
4080 | void do_rmdir(struct st_command *command) |
4081 | { |
4082 | static DYNAMIC_STRING ds_dirname; |
4083 | const struct command_arg rmdir_args[] = { |
4084 | { "dirname" , ARG_STRING, TRUE, &ds_dirname, "Directory to remove" } |
4085 | }; |
4086 | DBUG_ENTER("do_rmdir" ); |
4087 | |
4088 | check_command_args(command, command->first_argument, |
4089 | rmdir_args, sizeof(rmdir_args)/sizeof(struct command_arg), |
4090 | ' '); |
4091 | |
4092 | if (bad_path(ds_dirname.str)) |
4093 | DBUG_VOID_RETURN; |
4094 | |
4095 | DBUG_PRINT("info" , ("removing directory: %s" , ds_dirname.str)); |
4096 | if (rmtree(ds_dirname.str)) |
4097 | handle_command_error(command, 1, errno); |
4098 | |
4099 | dynstr_free(&ds_dirname); |
4100 | DBUG_VOID_RETURN; |
4101 | } |
4102 | |
4103 | |
4104 | /* |
4105 | SYNOPSIS |
4106 | get_list_files |
4107 | ds output |
4108 | ds_dirname dir to list |
4109 | ds_wild wild-card file pattern (can be empty) |
4110 | |
4111 | DESCRIPTION |
4112 | list all entries in directory (matching ds_wild if given) |
4113 | */ |
4114 | |
4115 | static int get_list_files(DYNAMIC_STRING *ds, const DYNAMIC_STRING *ds_dirname, |
4116 | const DYNAMIC_STRING *ds_wild) |
4117 | { |
4118 | uint i; |
4119 | MY_DIR *dir_info; |
4120 | FILEINFO *file; |
4121 | DBUG_ENTER("get_list_files" ); |
4122 | |
4123 | DBUG_PRINT("info" , ("listing directory: %s" , ds_dirname->str)); |
4124 | if (!(dir_info= my_dir(ds_dirname->str, MYF(MY_WANT_SORT)))) |
4125 | DBUG_RETURN(1); |
4126 | set_wild_chars(1); |
4127 | for (i= 0; i < (uint) dir_info->number_of_files; i++) |
4128 | { |
4129 | file= dir_info->dir_entry + i; |
4130 | if (ds_wild && ds_wild->length && |
4131 | wild_compare(file->name, ds_wild->str, 0)) |
4132 | continue; |
4133 | replace_dynstr_append(ds, file->name); |
4134 | dynstr_append(ds, "\n" ); |
4135 | } |
4136 | set_wild_chars(0); |
4137 | my_dirend(dir_info); |
4138 | DBUG_RETURN(0); |
4139 | } |
4140 | |
4141 | |
4142 | /* |
4143 | SYNOPSIS |
4144 | do_list_files |
4145 | command called command |
4146 | |
4147 | DESCRIPTION |
4148 | list_files <dir_name> [<file_name>] |
4149 | List files and directories in directory <dir_name> (like `ls`) |
4150 | [Matching <file_name>, where wild-cards are allowed] |
4151 | */ |
4152 | |
4153 | static void do_list_files(struct st_command *command) |
4154 | { |
4155 | int error; |
4156 | static DYNAMIC_STRING ds_dirname; |
4157 | static DYNAMIC_STRING ds_wild; |
4158 | const struct command_arg list_files_args[] = { |
4159 | {"dirname" , ARG_STRING, TRUE, &ds_dirname, "Directory to list" }, |
4160 | {"file" , ARG_STRING, FALSE, &ds_wild, "Filename (incl. wildcard)" } |
4161 | }; |
4162 | DBUG_ENTER("do_list_files" ); |
4163 | command->used_replace= 1; |
4164 | |
4165 | check_command_args(command, command->first_argument, |
4166 | list_files_args, |
4167 | sizeof(list_files_args)/sizeof(struct command_arg), ' '); |
4168 | |
4169 | error= get_list_files(&ds_res, &ds_dirname, &ds_wild); |
4170 | handle_command_error(command, error, my_errno); |
4171 | dynstr_free(&ds_dirname); |
4172 | dynstr_free(&ds_wild); |
4173 | DBUG_VOID_RETURN; |
4174 | } |
4175 | |
4176 | |
4177 | /* |
4178 | SYNOPSIS |
4179 | do_list_files_write_file_command |
4180 | command called command |
4181 | append append file, or create new |
4182 | |
4183 | DESCRIPTION |
4184 | list_files_{write|append}_file <filename> <dir_name> [<match_file>] |
4185 | List files and directories in directory <dir_name> (like `ls`) |
4186 | [Matching <match_file>, where wild-cards are allowed] |
4187 | |
4188 | Note: File will be truncated if exists and append is not true. |
4189 | */ |
4190 | |
4191 | static void do_list_files_write_file_command(struct st_command *command, |
4192 | my_bool append) |
4193 | { |
4194 | int error; |
4195 | static DYNAMIC_STRING ds_content; |
4196 | static DYNAMIC_STRING ds_filename; |
4197 | static DYNAMIC_STRING ds_dirname; |
4198 | static DYNAMIC_STRING ds_wild; |
4199 | const struct command_arg list_files_args[] = { |
4200 | {"filename" , ARG_STRING, TRUE, &ds_filename, "Filename for write" }, |
4201 | {"dirname" , ARG_STRING, TRUE, &ds_dirname, "Directory to list" }, |
4202 | {"file" , ARG_STRING, FALSE, &ds_wild, "Filename (incl. wildcard)" } |
4203 | }; |
4204 | DBUG_ENTER("do_list_files_write_file" ); |
4205 | command->used_replace= 1; |
4206 | |
4207 | check_command_args(command, command->first_argument, |
4208 | list_files_args, |
4209 | sizeof(list_files_args)/sizeof(struct command_arg), ' '); |
4210 | |
4211 | if (bad_path(ds_filename.str)) |
4212 | DBUG_VOID_RETURN; |
4213 | |
4214 | init_dynamic_string(&ds_content, "" , 1024, 1024); |
4215 | error= get_list_files(&ds_content, &ds_dirname, &ds_wild); |
4216 | handle_command_error(command, error, my_errno); |
4217 | str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); |
4218 | dynstr_free(&ds_content); |
4219 | dynstr_free(&ds_filename); |
4220 | dynstr_free(&ds_dirname); |
4221 | dynstr_free(&ds_wild); |
4222 | DBUG_VOID_RETURN; |
4223 | } |
4224 | |
4225 | |
4226 | /* |
4227 | Read characters from line buffer or file. This is needed to allow |
4228 | my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file |
4229 | |
4230 | NOTE: |
4231 | This works as long as one doesn't change files (with 'source file_name') |
4232 | when there is things pushed into the buffer. This should however not |
4233 | happen for any tests in the test suite. |
4234 | */ |
4235 | |
4236 | int my_getc(FILE *file) |
4237 | { |
4238 | if (line_buffer_pos == line_buffer) |
4239 | return fgetc(file); |
4240 | return *--line_buffer_pos; |
4241 | } |
4242 | |
4243 | |
4244 | void my_ungetc(int c) |
4245 | { |
4246 | *line_buffer_pos++= (char) c; |
4247 | } |
4248 | |
4249 | |
4250 | void read_until_delimiter(DYNAMIC_STRING *ds, |
4251 | DYNAMIC_STRING *ds_delimiter) |
4252 | { |
4253 | char c; |
4254 | DBUG_ENTER("read_until_delimiter" ); |
4255 | DBUG_PRINT("enter" , ("delimiter: %s, length: %u" , |
4256 | ds_delimiter->str, (uint) ds_delimiter->length)); |
4257 | |
4258 | if (ds_delimiter->length > MAX_DELIMITER_LENGTH) |
4259 | die("Max delimiter length(%d) exceeded" , MAX_DELIMITER_LENGTH); |
4260 | |
4261 | /* Read from file until delimiter is found */ |
4262 | while (1) |
4263 | { |
4264 | c= my_getc(cur_file->file); |
4265 | if (c == '\r') |
4266 | c= my_getc(cur_file->file); |
4267 | if (c == '\n') |
4268 | { |
4269 | cur_file->lineno++; |
4270 | |
4271 | /* Skip newline from the same line as the command */ |
4272 | if (start_lineno == (cur_file->lineno - 1)) |
4273 | continue; |
4274 | } |
4275 | else if (start_lineno == cur_file->lineno) |
4276 | { |
4277 | /* |
4278 | No characters except \n are allowed on |
4279 | the same line as the command |
4280 | */ |
4281 | report_or_die("Trailing characters found after command" ); |
4282 | } |
4283 | |
4284 | if (feof(cur_file->file)) |
4285 | report_or_die("End of file encountered before '%s' delimiter was found" , |
4286 | ds_delimiter->str); |
4287 | |
4288 | if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) |
4289 | { |
4290 | DBUG_PRINT("exit" , ("Found delimiter '%s'" , ds_delimiter->str)); |
4291 | break; |
4292 | } |
4293 | dynstr_append_mem(ds, (const char*)&c, 1); |
4294 | } |
4295 | DBUG_PRINT("exit" , ("ds: %s" , ds->str)); |
4296 | DBUG_VOID_RETURN; |
4297 | } |
4298 | |
4299 | |
4300 | void do_write_file_command(struct st_command *command, my_bool append) |
4301 | { |
4302 | static DYNAMIC_STRING ds_content; |
4303 | static DYNAMIC_STRING ds_filename; |
4304 | static DYNAMIC_STRING ds_delimiter; |
4305 | const struct command_arg write_file_args[] = { |
4306 | { "filename" , ARG_STRING, TRUE, &ds_filename, "File to write to" }, |
4307 | { "delimiter" , ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } |
4308 | }; |
4309 | DBUG_ENTER("do_write_file" ); |
4310 | |
4311 | check_command_args(command, |
4312 | command->first_argument, |
4313 | write_file_args, |
4314 | sizeof(write_file_args)/sizeof(struct command_arg), |
4315 | ' '); |
4316 | |
4317 | if (bad_path(ds_filename.str)) |
4318 | DBUG_VOID_RETURN; |
4319 | |
4320 | if (!append && access(ds_filename.str, F_OK) == 0) |
4321 | { |
4322 | /* The file should not be overwritten */ |
4323 | die("File already exist: '%s'" , ds_filename.str); |
4324 | } |
4325 | |
4326 | ds_content= command->content; |
4327 | /* If it hasn't been done already by a loop iteration, fill it in */ |
4328 | if (! ds_content.str) |
4329 | { |
4330 | /* If no delimiter was provided, use EOF */ |
4331 | if (ds_delimiter.length == 0) |
4332 | dynstr_set(&ds_delimiter, "EOF" ); |
4333 | |
4334 | init_dynamic_string(&ds_content, "" , 1024, 1024); |
4335 | read_until_delimiter(&ds_content, &ds_delimiter); |
4336 | command->content= ds_content; |
4337 | } |
4338 | /* This function could be called even if "false", so check before printing */ |
4339 | if (cur_block->ok) |
4340 | { |
4341 | DBUG_PRINT("info" , ("Writing to file: %s" , ds_filename.str)); |
4342 | str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); |
4343 | } |
4344 | dynstr_free(&ds_filename); |
4345 | dynstr_free(&ds_delimiter); |
4346 | DBUG_VOID_RETURN; |
4347 | } |
4348 | |
4349 | |
4350 | /* |
4351 | SYNOPSIS |
4352 | do_write_file |
4353 | command called command |
4354 | |
4355 | DESCRIPTION |
4356 | write_file <file_name> [<delimiter>]; |
4357 | <what to write line 1> |
4358 | <...> |
4359 | < what to write line n> |
4360 | EOF |
4361 | |
4362 | --write_file <file_name>; |
4363 | <what to write line 1> |
4364 | <...> |
4365 | < what to write line n> |
4366 | EOF |
4367 | |
4368 | Write everything between the "write_file" command and 'delimiter' |
4369 | to "file_name" |
4370 | |
4371 | NOTE! Will fail if <file_name> exists |
4372 | |
4373 | Default <delimiter> is EOF |
4374 | |
4375 | */ |
4376 | |
4377 | void do_write_file(struct st_command *command) |
4378 | { |
4379 | do_write_file_command(command, FALSE); |
4380 | } |
4381 | |
4382 | |
4383 | /* |
4384 | SYNOPSIS |
4385 | do_append_file |
4386 | command called command |
4387 | |
4388 | DESCRIPTION |
4389 | append_file <file_name> [<delimiter>]; |
4390 | <what to write line 1> |
4391 | <...> |
4392 | < what to write line n> |
4393 | EOF |
4394 | |
4395 | --append_file <file_name>; |
4396 | <what to write line 1> |
4397 | <...> |
4398 | < what to write line n> |
4399 | EOF |
4400 | |
4401 | Append everything between the "append_file" command |
4402 | and 'delimiter' to "file_name" |
4403 | |
4404 | Default <delimiter> is EOF |
4405 | |
4406 | */ |
4407 | |
4408 | void do_append_file(struct st_command *command) |
4409 | { |
4410 | do_write_file_command(command, TRUE); |
4411 | } |
4412 | |
4413 | |
4414 | /* |
4415 | SYNOPSIS |
4416 | do_cat_file |
4417 | command called command |
4418 | |
4419 | DESCRIPTION |
4420 | cat_file <file_name>; |
4421 | |
4422 | Print the given file to result log |
4423 | |
4424 | */ |
4425 | |
4426 | void do_cat_file(struct st_command *command) |
4427 | { |
4428 | int error; |
4429 | static DYNAMIC_STRING ds_filename; |
4430 | const struct command_arg cat_file_args[] = { |
4431 | { "filename" , ARG_STRING, TRUE, &ds_filename, "File to read from" } |
4432 | }; |
4433 | DBUG_ENTER("do_cat_file" ); |
4434 | |
4435 | check_command_args(command, |
4436 | command->first_argument, |
4437 | cat_file_args, |
4438 | sizeof(cat_file_args)/sizeof(struct command_arg), |
4439 | ' '); |
4440 | |
4441 | DBUG_PRINT("info" , ("Reading from, file: %s" , ds_filename.str)); |
4442 | |
4443 | error= cat_file(&ds_res, ds_filename.str); |
4444 | handle_command_error(command, error, my_errno); |
4445 | dynstr_free(&ds_filename); |
4446 | DBUG_VOID_RETURN; |
4447 | } |
4448 | |
4449 | |
4450 | /* |
4451 | SYNOPSIS |
4452 | do_diff_files |
4453 | command called command |
4454 | |
4455 | DESCRIPTION |
4456 | diff_files <file1> <file2>; |
4457 | |
4458 | Fails if the two files differ. |
4459 | |
4460 | */ |
4461 | |
4462 | void do_diff_files(struct st_command *command) |
4463 | { |
4464 | int error= 0; |
4465 | static DYNAMIC_STRING ds_filename; |
4466 | static DYNAMIC_STRING ds_filename2; |
4467 | const struct command_arg diff_file_args[] = { |
4468 | { "file1" , ARG_STRING, TRUE, &ds_filename, "First file to diff" }, |
4469 | { "file2" , ARG_STRING, TRUE, &ds_filename2, "Second file to diff" } |
4470 | }; |
4471 | DBUG_ENTER("do_diff_files" ); |
4472 | |
4473 | check_command_args(command, |
4474 | command->first_argument, |
4475 | diff_file_args, |
4476 | sizeof(diff_file_args)/sizeof(struct command_arg), |
4477 | ' '); |
4478 | |
4479 | if (access(ds_filename.str, F_OK) != 0) |
4480 | die("command \"diff_files\" failed, file '%s' does not exist" , |
4481 | ds_filename.str); |
4482 | |
4483 | if (access(ds_filename2.str, F_OK) != 0) |
4484 | die("command \"diff_files\" failed, file '%s' does not exist" , |
4485 | ds_filename2.str); |
4486 | |
4487 | if ((error= compare_files(ds_filename.str, ds_filename2.str)) && |
4488 | match_expected_error(command, error, NULL) < 0) |
4489 | { |
4490 | /* |
4491 | Compare of the two files failed, append them to output |
4492 | so the failure can be analyzed, but only if it was not |
4493 | expected to fail. |
4494 | */ |
4495 | show_diff(&ds_res, ds_filename.str, ds_filename2.str); |
4496 | log_file.write(&ds_res); |
4497 | log_file.flush(); |
4498 | dynstr_set(&ds_res, 0); |
4499 | } |
4500 | |
4501 | dynstr_free(&ds_filename); |
4502 | dynstr_free(&ds_filename2); |
4503 | handle_command_error(command, error, -1); |
4504 | DBUG_VOID_RETURN; |
4505 | } |
4506 | |
4507 | |
4508 | struct st_connection * find_connection_by_name(const char *name) |
4509 | { |
4510 | struct st_connection *con; |
4511 | for (con= connections; con < next_con; con++) |
4512 | { |
4513 | if (!strcmp(con->name, name)) |
4514 | { |
4515 | return con; |
4516 | } |
4517 | } |
4518 | return 0; /* Connection not found */ |
4519 | } |
4520 | |
4521 | |
4522 | /* |
4523 | SYNOPSIS |
4524 | do_send_quit |
4525 | command called command |
4526 | |
4527 | DESCRIPTION |
4528 | Sends a simple quit command to the server for the named connection. |
4529 | |
4530 | */ |
4531 | |
4532 | void do_send_quit(struct st_command *command) |
4533 | { |
4534 | char *p= command->first_argument, *name; |
4535 | struct st_connection *con; |
4536 | |
4537 | DBUG_ENTER("do_send_quit" ); |
4538 | DBUG_PRINT("enter" ,("name: '%s'" ,p)); |
4539 | |
4540 | if (!*p) |
4541 | die("Missing connection name in send_quit" ); |
4542 | name= p; |
4543 | while (*p && !my_isspace(charset_info,*p)) |
4544 | p++; |
4545 | |
4546 | if (*p) |
4547 | *p++= 0; |
4548 | command->last_argument= p; |
4549 | |
4550 | if (!(con= find_connection_by_name(name))) |
4551 | die("connection '%s' not found in connection pool" , name); |
4552 | |
4553 | simple_command(con->mysql,COM_QUIT,0,0,1); |
4554 | |
4555 | DBUG_VOID_RETURN; |
4556 | } |
4557 | |
4558 | |
4559 | /* |
4560 | SYNOPSIS |
4561 | do_change_user |
4562 | command called command |
4563 | |
4564 | DESCRIPTION |
4565 | change_user [<user>], [<passwd>], [<db>] |
4566 | <user> - user to change to |
4567 | <passwd> - user password |
4568 | <db> - default database |
4569 | |
4570 | Changes the user and causes the database specified by db to become |
4571 | the default (current) database for the the current connection. |
4572 | |
4573 | */ |
4574 | |
4575 | void do_change_user(struct st_command *command) |
4576 | { |
4577 | MYSQL *mysql = cur_con->mysql; |
4578 | /* static keyword to make the NetWare compiler happy. */ |
4579 | static DYNAMIC_STRING ds_user, ds_passwd, ds_db; |
4580 | const struct command_arg change_user_args[] = { |
4581 | { "user" , ARG_STRING, FALSE, &ds_user, "User to connect as" }, |
4582 | { "password" , ARG_STRING, FALSE, &ds_passwd, "Password used when connecting" }, |
4583 | { "database" , ARG_STRING, FALSE, &ds_db, "Database to select after connect" }, |
4584 | }; |
4585 | |
4586 | DBUG_ENTER("do_change_user" ); |
4587 | |
4588 | check_command_args(command, command->first_argument, |
4589 | change_user_args, |
4590 | sizeof(change_user_args)/sizeof(struct command_arg), |
4591 | ','); |
4592 | |
4593 | if (cur_con->stmt) |
4594 | { |
4595 | mysql_stmt_close(cur_con->stmt); |
4596 | cur_con->stmt= NULL; |
4597 | } |
4598 | |
4599 | if (!ds_user.length) |
4600 | { |
4601 | dynstr_set(&ds_user, mysql->user); |
4602 | |
4603 | if (!ds_passwd.length) |
4604 | dynstr_set(&ds_passwd, mysql->passwd); |
4605 | |
4606 | if (!ds_db.length) |
4607 | dynstr_set(&ds_db, mysql->db); |
4608 | } |
4609 | |
4610 | DBUG_PRINT("info" ,("connection: '%s' user: '%s' password: '%s' database: '%s'" , |
4611 | cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); |
4612 | |
4613 | if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) |
4614 | handle_error(command, mysql_errno(mysql), mysql_error(mysql), |
4615 | mysql_sqlstate(mysql), &ds_res); |
4616 | else |
4617 | handle_no_error(command); |
4618 | |
4619 | dynstr_free(&ds_user); |
4620 | dynstr_free(&ds_passwd); |
4621 | dynstr_free(&ds_db); |
4622 | |
4623 | DBUG_VOID_RETURN; |
4624 | } |
4625 | |
4626 | |
4627 | /* |
4628 | SYNOPSIS |
4629 | do_perl |
4630 | command command handle |
4631 | |
4632 | DESCRIPTION |
4633 | perl [<delimiter>]; |
4634 | <perlscript line 1> |
4635 | <...> |
4636 | <perlscript line n> |
4637 | EOF |
4638 | |
4639 | Execute everything after "perl" until <delimiter> as perl. |
4640 | Useful for doing more advanced things |
4641 | but still being able to execute it on all platforms. |
4642 | |
4643 | Default <delimiter> is EOF |
4644 | */ |
4645 | |
4646 | void do_perl(struct st_command *command) |
4647 | { |
4648 | int error; |
4649 | File fd; |
4650 | FILE *res_file; |
4651 | char buf[FN_REFLEN]; |
4652 | char temp_file_path[FN_REFLEN]; |
4653 | static DYNAMIC_STRING ds_script; |
4654 | static DYNAMIC_STRING ds_delimiter; |
4655 | const struct command_arg perl_args[] = { |
4656 | { "delimiter" , ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } |
4657 | }; |
4658 | DBUG_ENTER("do_perl" ); |
4659 | |
4660 | check_command_args(command, |
4661 | command->first_argument, |
4662 | perl_args, |
4663 | sizeof(perl_args)/sizeof(struct command_arg), |
4664 | ' '); |
4665 | |
4666 | ds_script= command->content; |
4667 | /* If it hasn't been done already by a loop iteration, fill it in */ |
4668 | if (! ds_script.str) |
4669 | { |
4670 | /* If no delimiter was provided, use EOF */ |
4671 | if (ds_delimiter.length == 0) |
4672 | dynstr_set(&ds_delimiter, "EOF" ); |
4673 | |
4674 | init_dynamic_string(&ds_script, "" , 1024, 1024); |
4675 | read_until_delimiter(&ds_script, &ds_delimiter); |
4676 | command->content= ds_script; |
4677 | } |
4678 | |
4679 | /* This function could be called even if "false", so check before doing */ |
4680 | if (cur_block->ok) |
4681 | { |
4682 | DBUG_PRINT("info" , ("Executing perl: %s" , ds_script.str)); |
4683 | |
4684 | /* Create temporary file name */ |
4685 | if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR" ), |
4686 | "tmp" , O_SHARE, MYF(MY_WME))) < 0) |
4687 | die("Failed to create temporary file for perl command" ); |
4688 | my_close(fd, MYF(0)); |
4689 | |
4690 | str_to_file(temp_file_path, ds_script.str, ds_script.length); |
4691 | |
4692 | /* Format the "perl <filename>" command */ |
4693 | my_snprintf(buf, sizeof(buf), "perl %s" , temp_file_path); |
4694 | |
4695 | if (!(res_file= my_popen(buf, "r" ))) |
4696 | { |
4697 | if (command->abort_on_error) |
4698 | die("popen(\"%s\", \"r\") failed" , buf); |
4699 | dynstr_free(&ds_delimiter); |
4700 | DBUG_VOID_RETURN; |
4701 | } |
4702 | |
4703 | int len; |
4704 | while (my_fgets(buf, sizeof(buf), res_file,&len)) |
4705 | { |
4706 | if (disable_result_log) |
4707 | { |
4708 | buf[len - 1] = 0; |
4709 | DBUG_PRINT("exec_result" , ("%s" , buf)); |
4710 | } |
4711 | else |
4712 | { |
4713 | replace_dynstr_append_mem(&ds_res, buf, len); |
4714 | } |
4715 | } |
4716 | error= pclose(res_file); |
4717 | |
4718 | /* Remove the temporary file, but keep it if perl failed */ |
4719 | if (!error) |
4720 | my_delete(temp_file_path, MYF(MY_WME)); |
4721 | |
4722 | /* Check for error code that indicates perl could not be started */ |
4723 | int exstat= WEXITSTATUS(error); |
4724 | #ifdef _WIN32 |
4725 | if (exstat == 1) |
4726 | /* Text must begin 'perl not found' as mtr looks for it */ |
4727 | abort_not_supported_test("perl not found in path or did not start" ); |
4728 | #else |
4729 | if (exstat == 127) |
4730 | abort_not_supported_test("perl not found in path" ); |
4731 | #endif |
4732 | else |
4733 | handle_command_error(command, exstat, my_errno); |
4734 | } |
4735 | dynstr_free(&ds_delimiter); |
4736 | DBUG_VOID_RETURN; |
4737 | } |
4738 | |
4739 | |
4740 | /* |
4741 | Print the content between echo and <delimiter> to result file. |
4742 | Evaluate all variables in the string before printing, allow |
4743 | for variable names to be escaped using \ |
4744 | |
4745 | SYNOPSIS |
4746 | do_echo() |
4747 | command called command |
4748 | |
4749 | DESCRIPTION |
4750 | echo text |
4751 | Print the text after echo until end of command to result file |
4752 | |
4753 | echo $<var_name> |
4754 | Print the content of the variable <var_name> to result file |
4755 | |
4756 | echo Some text $<var_name> |
4757 | Print "Some text" plus the content of the variable <var_name> to |
4758 | result file |
4759 | |
4760 | echo Some text \$<var_name> |
4761 | Print "Some text" plus $<var_name> to result file |
4762 | */ |
4763 | |
4764 | int do_echo(struct st_command *command) |
4765 | { |
4766 | DYNAMIC_STRING ds_echo; |
4767 | DBUG_ENTER("do_echo" ); |
4768 | |
4769 | init_dynamic_string(&ds_echo, "" , command->query_len, 256); |
4770 | do_eval(&ds_echo, command->first_argument, command->end, FALSE); |
4771 | dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length); |
4772 | dynstr_append_mem(&ds_res, "\n" , 1); |
4773 | dynstr_free(&ds_echo); |
4774 | command->last_argument= command->end; |
4775 | DBUG_RETURN(0); |
4776 | } |
4777 | |
4778 | |
4779 | void do_wait_for_slave_to_stop(struct st_command *c __attribute__((unused))) |
4780 | { |
4781 | static int SLAVE_POLL_INTERVAL= 300000; |
4782 | MYSQL* mysql = cur_con->mysql; |
4783 | for (;;) |
4784 | { |
4785 | MYSQL_RES *UNINIT_VAR(res); |
4786 | MYSQL_ROW row; |
4787 | int done; |
4788 | |
4789 | if (mysql_query(mysql,"show status like 'Slave_running'" ) || |
4790 | !(res=mysql_store_result(mysql))) |
4791 | die("Query failed while probing slave for stop: %s" , |
4792 | mysql_error(mysql)); |
4793 | if (!(row=mysql_fetch_row(res)) || !row[1]) |
4794 | { |
4795 | mysql_free_result(res); |
4796 | die("Strange result from query while probing slave for stop" ); |
4797 | } |
4798 | done = !strcmp(row[1],"OFF" ); |
4799 | mysql_free_result(res); |
4800 | if (done) |
4801 | break; |
4802 | my_sleep(SLAVE_POLL_INTERVAL); |
4803 | } |
4804 | return; |
4805 | } |
4806 | |
4807 | |
4808 | void do_sync_with_master2(struct st_command *command, long offset, |
4809 | const char *connection_name) |
4810 | { |
4811 | MYSQL_RES *res; |
4812 | MYSQL_ROW row; |
4813 | MYSQL *mysql= cur_con->mysql; |
4814 | char query_buf[FN_REFLEN+128]; |
4815 | int timeout= opt_wait_for_pos_timeout; |
4816 | |
4817 | if (!master_pos.file[0]) |
4818 | die("Calling 'sync_with_master' without calling 'save_master_pos'" ); |
4819 | |
4820 | sprintf(query_buf, "select master_pos_wait('%s', %ld, %d, '%s')" , |
4821 | master_pos.file, master_pos.pos + offset, timeout, |
4822 | connection_name); |
4823 | |
4824 | if (mysql_query(mysql, query_buf)) |
4825 | die("failed in '%s': %d: %s" , query_buf, mysql_errno(mysql), |
4826 | mysql_error(mysql)); |
4827 | |
4828 | if (!(res= mysql_store_result(mysql))) |
4829 | die("mysql_store_result() returned NULL for '%s'" , query_buf); |
4830 | if (!(row= mysql_fetch_row(res))) |
4831 | { |
4832 | mysql_free_result(res); |
4833 | die("empty result in %s" , query_buf); |
4834 | } |
4835 | |
4836 | int result= -99; |
4837 | const char* result_str= row[0]; |
4838 | if (result_str) |
4839 | result= atoi(result_str); |
4840 | |
4841 | mysql_free_result(res); |
4842 | |
4843 | if (!result_str || result < 0) |
4844 | { |
4845 | /* master_pos_wait returned NULL or < 0 */ |
4846 | show_query(mysql, "SHOW MASTER STATUS" ); |
4847 | show_query(mysql, "SHOW SLAVE STATUS" ); |
4848 | show_query(mysql, "SHOW PROCESSLIST" ); |
4849 | fprintf(stderr, "analyze: sync_with_master\n" ); |
4850 | |
4851 | if (!result_str) |
4852 | { |
4853 | /* |
4854 | master_pos_wait returned NULL. This indicates that |
4855 | slave SQL thread is not started, the slave's master |
4856 | information is not initialized, the arguments are |
4857 | incorrect, or an error has occurred |
4858 | */ |
4859 | die("%.*s failed: '%s' returned NULL " \ |
4860 | "indicating slave SQL thread failure" , |
4861 | command->first_word_len, command->query, query_buf); |
4862 | |
4863 | } |
4864 | |
4865 | if (result == -1) |
4866 | die("%.*s failed: '%s' returned -1 " \ |
4867 | "indicating timeout after %d seconds" , |
4868 | command->first_word_len, command->query, query_buf, timeout); |
4869 | else |
4870 | die("%.*s failed: '%s' returned unknown result :%d" , |
4871 | command->first_word_len, command->query, query_buf, result); |
4872 | } |
4873 | |
4874 | return; |
4875 | } |
4876 | |
4877 | void do_sync_with_master(struct st_command *command) |
4878 | { |
4879 | long offset= 0; |
4880 | char *p= command->first_argument; |
4881 | const char *offset_start= p; |
4882 | char *start, *buff= 0; |
4883 | start= const_cast<char*>("" ); |
4884 | |
4885 | if (*offset_start) |
4886 | { |
4887 | for (; my_isdigit(charset_info, *p); p++) |
4888 | offset = offset * 10 + *p - '0'; |
4889 | |
4890 | if (*p && !my_isspace(charset_info, *p) && *p != ',') |
4891 | die("Invalid integer argument \"%s\"" , offset_start); |
4892 | |
4893 | while (*p && my_isspace(charset_info, *p)) |
4894 | p++; |
4895 | if (*p == ',') |
4896 | { |
4897 | p++; |
4898 | while (*p && my_isspace(charset_info, *p)) |
4899 | p++; |
4900 | start= buff= (char*)my_malloc(strlen(p)+1,MYF(MY_WME | MY_FAE)); |
4901 | get_string(&buff, &p, command); |
4902 | } |
4903 | command->last_argument= p; |
4904 | } |
4905 | do_sync_with_master2(command, offset, start); |
4906 | if (buff) |
4907 | my_free(start); |
4908 | return; |
4909 | } |
4910 | |
4911 | |
4912 | int do_save_master_pos() |
4913 | { |
4914 | MYSQL_RES *res; |
4915 | MYSQL_ROW row; |
4916 | MYSQL *mysql = cur_con->mysql; |
4917 | const char *query; |
4918 | DBUG_ENTER("do_save_master_pos" ); |
4919 | |
4920 | if (mysql_query(mysql, query= "show master status" )) |
4921 | die("failed in 'show master status': %d %s" , |
4922 | mysql_errno(mysql), mysql_error(mysql)); |
4923 | |
4924 | if (!(res = mysql_store_result(mysql))) |
4925 | die("mysql_store_result() retuned NULL for '%s'" , query); |
4926 | if (!(row = mysql_fetch_row(res))) |
4927 | die("empty result in show master status" ); |
4928 | strnmov(master_pos.file, row[0], sizeof(master_pos.file)-1); |
4929 | master_pos.pos = strtoul(row[1], (char**) 0, 10); |
4930 | mysql_free_result(res); |
4931 | DBUG_RETURN(0); |
4932 | } |
4933 | |
4934 | |
4935 | /* |
4936 | Assign the variable <var_name> with <var_val> |
4937 | |
4938 | SYNOPSIS |
4939 | do_let() |
4940 | query called command |
4941 | |
4942 | DESCRIPTION |
4943 | let $<var_name>=<var_val><delimiter> |
4944 | |
4945 | <var_name> - is the string string found between the $ and = |
4946 | <var_val> - is the content between the = and <delimiter>, it may span |
4947 | multiple line and contain any characters except <delimiter> |
4948 | <delimiter> - is a string containing of one or more chars, default is ; |
4949 | |
4950 | RETURN VALUES |
4951 | Program will die if error detected |
4952 | */ |
4953 | |
4954 | void do_let(struct st_command *command) |
4955 | { |
4956 | char *p= command->first_argument; |
4957 | char *var_name, *var_name_end; |
4958 | DYNAMIC_STRING let_rhs_expr; |
4959 | DBUG_ENTER("do_let" ); |
4960 | |
4961 | init_dynamic_string(&let_rhs_expr, "" , 512, 2048); |
4962 | |
4963 | /* Find <var_name> */ |
4964 | if (!*p) |
4965 | die("Missing arguments to let" ); |
4966 | var_name= p; |
4967 | while (*p && (*p != '=') && !my_isspace(charset_info,*p)) |
4968 | p++; |
4969 | var_name_end= p; |
4970 | if (var_name == var_name_end || |
4971 | (var_name+1 == var_name_end && *var_name == '$')) |
4972 | die("Missing variable name in let" ); |
4973 | while (my_isspace(charset_info,*p)) |
4974 | p++; |
4975 | if (*p++ != '=') |
4976 | die("Missing assignment operator in let" ); |
4977 | |
4978 | /* Find start of <var_val> */ |
4979 | while (*p && my_isspace(charset_info,*p)) |
4980 | p++; |
4981 | |
4982 | do_eval(&let_rhs_expr, p, command->end, FALSE); |
4983 | |
4984 | command->last_argument= command->end; |
4985 | /* Assign var_val to var_name */ |
4986 | var_set(var_name, var_name_end, let_rhs_expr.str, |
4987 | (let_rhs_expr.str + let_rhs_expr.length)); |
4988 | dynstr_free(&let_rhs_expr); |
4989 | revert_properties(); |
4990 | DBUG_VOID_RETURN; |
4991 | } |
4992 | |
4993 | |
4994 | /* |
4995 | Sleep the number of specified seconds |
4996 | |
4997 | SYNOPSIS |
4998 | do_sleep() |
4999 | q called command |
5000 | real_sleep use the value from opt_sleep as number of seconds to sleep |
5001 | if real_sleep is false |
5002 | |
5003 | DESCRIPTION |
5004 | sleep <seconds> |
5005 | real_sleep <seconds> |
5006 | |
5007 | The difference between the sleep and real_sleep commands is that sleep |
5008 | uses the delay from the --sleep command-line option if there is one. |
5009 | (If the --sleep option is not given, the sleep command uses the delay |
5010 | specified by its argument.) The real_sleep command always uses the |
5011 | delay specified by its argument. The logic is that sometimes delays are |
5012 | cpu-dependent, and --sleep can be used to set this delay. real_sleep is |
5013 | used for cpu-independent delays. |
5014 | */ |
5015 | |
5016 | int do_sleep(struct st_command *command, my_bool real_sleep) |
5017 | { |
5018 | int error= 0; |
5019 | char *sleep_start, *sleep_end; |
5020 | double sleep_val; |
5021 | char *p; |
5022 | static DYNAMIC_STRING ds_sleep; |
5023 | const struct command_arg sleep_args[] = { |
5024 | { "sleep_delay" , ARG_STRING, TRUE, &ds_sleep, "Number of seconds to sleep." } |
5025 | }; |
5026 | check_command_args(command, command->first_argument, sleep_args, |
5027 | sizeof(sleep_args)/sizeof(struct command_arg), |
5028 | ' '); |
5029 | |
5030 | p= ds_sleep.str; |
5031 | sleep_end= ds_sleep.str + ds_sleep.length; |
5032 | while (my_isspace(charset_info, *p)) |
5033 | p++; |
5034 | if (!*p) |
5035 | die("Missing argument to %.*s" , command->first_word_len, |
5036 | command->query); |
5037 | sleep_start= p; |
5038 | /* Check that arg starts with a digit, not handled by my_strtod */ |
5039 | if (!my_isdigit(charset_info, *sleep_start)) |
5040 | die("Invalid argument to %.*s \"%s\"" , command->first_word_len, |
5041 | command->query, sleep_start); |
5042 | sleep_val= my_strtod(sleep_start, &sleep_end, &error); |
5043 | check_eol_junk_line(sleep_end); |
5044 | if (error) |
5045 | die("Invalid argument to %.*s \"%s\"" , command->first_word_len, |
5046 | command->query, command->first_argument); |
5047 | dynstr_free(&ds_sleep); |
5048 | |
5049 | /* Fixed sleep time selected by --sleep option */ |
5050 | if (opt_sleep >= 0 && !real_sleep) |
5051 | sleep_val= opt_sleep; |
5052 | |
5053 | DBUG_PRINT("info" , ("sleep_val: %f" , sleep_val)); |
5054 | if (sleep_val) |
5055 | my_sleep((ulong) (sleep_val * 1000000L)); |
5056 | return 0; |
5057 | } |
5058 | |
5059 | |
5060 | void do_get_file_name(struct st_command *command, |
5061 | char* dest, uint dest_max_len) |
5062 | { |
5063 | char *p= command->first_argument, *name; |
5064 | if (!*p) |
5065 | die("Missing file name argument" ); |
5066 | name= p; |
5067 | while (*p && !my_isspace(charset_info,*p)) |
5068 | p++; |
5069 | if (*p) |
5070 | *p++= 0; |
5071 | command->last_argument= p; |
5072 | strmake(dest, name, dest_max_len - 1); |
5073 | } |
5074 | |
5075 | |
5076 | void do_set_charset(struct st_command *command) |
5077 | { |
5078 | char *charset_name= command->first_argument; |
5079 | char *p; |
5080 | |
5081 | if (!charset_name || !*charset_name) |
5082 | die("Missing charset name in 'character_set'" ); |
5083 | /* Remove end space */ |
5084 | p= charset_name; |
5085 | while (*p && !my_isspace(charset_info,*p)) |
5086 | p++; |
5087 | if(*p) |
5088 | *p++= 0; |
5089 | command->last_argument= p; |
5090 | charset_info= get_charset_by_csname(charset_name,MY_CS_PRIMARY,MYF(MY_WME)); |
5091 | if (!charset_info) |
5092 | abort_not_supported_test("Test requires charset '%s'" , charset_name); |
5093 | } |
5094 | |
5095 | |
5096 | /* |
5097 | Run query and return one field in the result set from the |
5098 | first row and <column> |
5099 | */ |
5100 | |
5101 | int query_get_string(MYSQL* mysql, const char* query, |
5102 | int column, DYNAMIC_STRING* ds) |
5103 | { |
5104 | MYSQL_RES *res= NULL; |
5105 | MYSQL_ROW row; |
5106 | |
5107 | if (mysql_query(mysql, query)) |
5108 | { |
5109 | report_or_die("'%s' failed: %d %s" , query, |
5110 | mysql_errno(mysql), mysql_error(mysql)); |
5111 | return 1; |
5112 | } |
5113 | if ((res= mysql_store_result(mysql)) == NULL) |
5114 | { |
5115 | report_or_die("Failed to store result: %d %s" , |
5116 | mysql_errno(mysql), mysql_error(mysql)); |
5117 | return 1; |
5118 | } |
5119 | |
5120 | if ((row= mysql_fetch_row(res)) == NULL) |
5121 | { |
5122 | mysql_free_result(res); |
5123 | return 1; |
5124 | } |
5125 | init_dynamic_string(ds, (row[column] ? row[column] : "NULL" ), ~0, 32); |
5126 | mysql_free_result(res); |
5127 | return 0; |
5128 | } |
5129 | |
5130 | |
5131 | static int my_kill(int pid, int sig) |
5132 | { |
5133 | DBUG_PRINT("info" , ("Killing server, pid: %d" , pid)); |
5134 | #ifdef _WIN32 |
5135 | #define SIGKILL 9 /* ignored anyway, see below */ |
5136 | HANDLE proc; |
5137 | if ((proc= OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid)) == NULL) |
5138 | return -1; |
5139 | if (sig == 0) |
5140 | { |
5141 | DWORD wait_result= WaitForSingleObject(proc, 0); |
5142 | CloseHandle(proc); |
5143 | return wait_result == WAIT_OBJECT_0?-1:0; |
5144 | } |
5145 | (void)TerminateProcess(proc, 201); |
5146 | CloseHandle(proc); |
5147 | return 1; |
5148 | #else |
5149 | return kill(pid, sig); |
5150 | #endif |
5151 | } |
5152 | |
5153 | |
5154 | |
5155 | /* |
5156 | Shutdown the server of current connection and |
5157 | make sure it goes away within <timeout> seconds |
5158 | |
5159 | NOTE! Currently only works with local server |
5160 | |
5161 | SYNOPSIS |
5162 | do_shutdown_server() |
5163 | command called command |
5164 | |
5165 | DESCRIPTION |
5166 | shutdown_server [<timeout>] |
5167 | |
5168 | */ |
5169 | |
5170 | |
5171 | static int wait_until_dead(int pid, int timeout) |
5172 | { |
5173 | DBUG_ENTER("wait_until_dead" ); |
5174 | /* Check that server dies */ |
5175 | while (timeout--) |
5176 | { |
5177 | if (my_kill(pid, 0) < 0) |
5178 | { |
5179 | DBUG_PRINT("info" , ("Process %d does not exist anymore" , pid)); |
5180 | DBUG_RETURN(0); |
5181 | } |
5182 | DBUG_PRINT("info" , ("Sleeping, timeout: %d" , timeout)); |
5183 | /* Sleep one second */ |
5184 | my_sleep(1000000L); |
5185 | } |
5186 | DBUG_RETURN(1); // Did not die |
5187 | } |
5188 | |
5189 | |
5190 | void do_shutdown_server(struct st_command *command) |
5191 | { |
5192 | long timeout= opt_wait_for_pos_timeout ? opt_wait_for_pos_timeout / 5 : 300; |
5193 | int pid; |
5194 | DYNAMIC_STRING ds_pidfile_name; |
5195 | MYSQL* mysql = cur_con->mysql; |
5196 | static DYNAMIC_STRING ds_timeout; |
5197 | const struct command_arg shutdown_args[] = { |
5198 | {"timeout" , ARG_STRING, FALSE, &ds_timeout, "Timeout before killing server" } |
5199 | }; |
5200 | DBUG_ENTER("do_shutdown_server" ); |
5201 | |
5202 | check_command_args(command, command->first_argument, shutdown_args, |
5203 | sizeof(shutdown_args)/sizeof(struct command_arg), |
5204 | ' '); |
5205 | |
5206 | if (ds_timeout.length) |
5207 | { |
5208 | char* endptr; |
5209 | timeout= strtol(ds_timeout.str, &endptr, 10); |
5210 | if (*endptr != '\0') |
5211 | die("Illegal argument for timeout: '%s'" , ds_timeout.str); |
5212 | } |
5213 | dynstr_free(&ds_timeout); |
5214 | |
5215 | /* Get the servers pid_file name and use it to read pid */ |
5216 | if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'" , 1, |
5217 | &ds_pidfile_name)) |
5218 | die("Failed to get pid_file from server" ); |
5219 | |
5220 | /* Read the pid from the file */ |
5221 | { |
5222 | int fd; |
5223 | char buff[32]; |
5224 | |
5225 | if ((fd= my_open(ds_pidfile_name.str, O_RDONLY, MYF(0))) < 0) |
5226 | die("Failed to open file '%s'" , ds_pidfile_name.str); |
5227 | dynstr_free(&ds_pidfile_name); |
5228 | |
5229 | if (my_read(fd, (uchar*)&buff, sizeof(buff), MYF(0)) <= 0){ |
5230 | my_close(fd, MYF(0)); |
5231 | die("pid file was empty" ); |
5232 | } |
5233 | my_close(fd, MYF(0)); |
5234 | |
5235 | pid= atoi(buff); |
5236 | if (pid == 0) |
5237 | die("Pidfile didn't contain a valid number" ); |
5238 | } |
5239 | DBUG_PRINT("info" , ("Got pid %d" , pid)); |
5240 | |
5241 | /* |
5242 | If timeout == 0, it means we should kill the server hard, without |
5243 | any shutdown or core (SIGKILL) |
5244 | |
5245 | If timeout is given, then we do things in the following order: |
5246 | - mysql_shutdown() |
5247 | - If server is not dead within timeout |
5248 | - kill SIGABRT (to get a core) |
5249 | - If server is not dead within new timeout |
5250 | - kill SIGKILL |
5251 | */ |
5252 | |
5253 | if (timeout && mysql_shutdown(mysql, SHUTDOWN_DEFAULT)) |
5254 | die("mysql_shutdown failed" ); |
5255 | |
5256 | if (!timeout || wait_until_dead(pid, timeout)) |
5257 | { |
5258 | if (timeout) |
5259 | (void) my_kill(pid, SIGABRT); |
5260 | /* Give server a few seconds to die in all cases */ |
5261 | if (!timeout || wait_until_dead(pid, timeout < 5 ? 5 : timeout)) |
5262 | { |
5263 | (void) my_kill(pid, SIGKILL); |
5264 | } |
5265 | } |
5266 | DBUG_VOID_RETURN; |
5267 | } |
5268 | |
5269 | |
5270 | /* List of error names to error codes */ |
5271 | typedef struct |
5272 | { |
5273 | const char *name; |
5274 | uint code; |
5275 | const char *text; |
5276 | } st_error; |
5277 | |
5278 | static st_error global_error_names[] = |
5279 | { |
5280 | { "<No error>" , ~0U, "" }, |
5281 | #include <mysqld_ername.h> |
5282 | { 0, 0, 0 } |
5283 | }; |
5284 | |
5285 | #include <my_base.h> |
5286 | static st_error handler_error_names[] = |
5287 | { |
5288 | { "<No error>" , UINT_MAX, "" }, |
5289 | #include <handler_ername.h> |
5290 | { 0, 0, 0 } |
5291 | }; |
5292 | |
5293 | uint get_errcode_from_name(const char *error_name, const char *error_end, |
5294 | st_error *e) |
5295 | { |
5296 | DBUG_ENTER("get_errcode_from_name" ); |
5297 | DBUG_PRINT("enter" , ("error_name: %s" , error_name)); |
5298 | |
5299 | /* Loop through the array of known error names */ |
5300 | for (; e->name; e++) |
5301 | { |
5302 | /* |
5303 | If we get a match, we need to check the length of the name we |
5304 | matched against in case it was longer than what we are checking |
5305 | (as in ER_WRONG_VALUE vs. ER_WRONG_VALUE_COUNT). |
5306 | */ |
5307 | if (!strncmp(error_name, e->name, (int) (error_end - error_name)) && |
5308 | (uint) strlen(e->name) == (uint) (error_end - error_name)) |
5309 | { |
5310 | DBUG_RETURN(e->code); |
5311 | } |
5312 | } |
5313 | DBUG_RETURN(0); |
5314 | } |
5315 | |
5316 | |
5317 | uint get_errcode_from_name(const char *error_name, const char *error_end) |
5318 | { |
5319 | uint tmp; |
5320 | if ((tmp= get_errcode_from_name(error_name, error_end, |
5321 | global_error_names))) |
5322 | return tmp; |
5323 | if ((tmp= get_errcode_from_name(error_name, error_end, |
5324 | handler_error_names))) |
5325 | return tmp; |
5326 | die("Unknown SQL error name '%s'" , error_name); |
5327 | return 0; // Keep compiler happy |
5328 | } |
5329 | |
5330 | const char *unknown_error= "<Unknown>" ; |
5331 | |
5332 | const char *get_errname_from_code (uint error_code, st_error *e) |
5333 | { |
5334 | DBUG_ENTER("get_errname_from_code" ); |
5335 | DBUG_PRINT("enter" , ("error_code: %d" , error_code)); |
5336 | |
5337 | if (! error_code) |
5338 | { |
5339 | DBUG_RETURN("" ); |
5340 | } |
5341 | for (; e->name; e++) |
5342 | { |
5343 | if (e->code == error_code) |
5344 | { |
5345 | DBUG_RETURN(e->name); |
5346 | } |
5347 | } |
5348 | /* Apparently, errors without known names may occur */ |
5349 | DBUG_RETURN(unknown_error); |
5350 | } |
5351 | |
5352 | const char *get_errname_from_code(uint error_code) |
5353 | { |
5354 | const char *name; |
5355 | if ((name= get_errname_from_code(error_code, global_error_names)) != |
5356 | unknown_error) |
5357 | return name; |
5358 | return get_errname_from_code(error_code, handler_error_names); |
5359 | } |
5360 | |
5361 | void do_get_errcodes(struct st_command *command) |
5362 | { |
5363 | struct st_match_err *to= saved_expected_errors.err; |
5364 | DBUG_ENTER("do_get_errcodes" ); |
5365 | |
5366 | if (!*command->first_argument) |
5367 | die("Missing argument(s) to 'error'" ); |
5368 | |
5369 | /* TODO: Potentially, there is a possibility of variables |
5370 | being expanded twice, e.g. |
5371 | |
5372 | let $errcodes = 1,\$a; |
5373 | let $a = 1051; |
5374 | error $errcodes; |
5375 | DROP TABLE unknown_table; |
5376 | ... |
5377 | Got one of the listed errors |
5378 | |
5379 | But since it requires manual escaping, it does not seem |
5380 | particularly dangerous or error-prone. |
5381 | */ |
5382 | DYNAMIC_STRING ds; |
5383 | init_dynamic_string(&ds, 0, command->query_len + 64, 256); |
5384 | do_eval(&ds, command->first_argument, command->end, !is_windows); |
5385 | char *p= ds.str; |
5386 | |
5387 | uint count= 0; |
5388 | char *next; |
5389 | |
5390 | do |
5391 | { |
5392 | char *end; |
5393 | |
5394 | /* Skip leading spaces */ |
5395 | while (*p && *p == ' ') |
5396 | p++; |
5397 | |
5398 | /* Find end */ |
5399 | end= p; |
5400 | while (*end && *end != ',' && *end != ' ') |
5401 | end++; |
5402 | |
5403 | next=end; |
5404 | |
5405 | /* code to handle variables passed to mysqltest */ |
5406 | if( *p == '$') |
5407 | { |
5408 | const char* fin; |
5409 | VAR *var = var_get(p,&fin,0,0); |
5410 | p=var->str_val; |
5411 | end=p+var->str_val_len; |
5412 | } |
5413 | |
5414 | if (*p == 'S') |
5415 | { |
5416 | char *to_ptr= to->code.sqlstate; |
5417 | |
5418 | /* |
5419 | SQLSTATE string |
5420 | - Must be SQLSTATE_LENGTH long |
5421 | - May contain only digits[0-9] and _uppercase_ letters |
5422 | */ |
5423 | p++; /* Step past the S */ |
5424 | if ((end - p) != SQLSTATE_LENGTH) |
5425 | die("The sqlstate must be exactly %d chars long" , SQLSTATE_LENGTH); |
5426 | |
5427 | /* Check sqlstate string validity */ |
5428 | while (*p && p < end) |
5429 | { |
5430 | if (my_isdigit(charset_info, *p) || my_isupper(charset_info, *p)) |
5431 | *to_ptr++= *p++; |
5432 | else |
5433 | die("The sqlstate may only consist of digits[0-9] " \ |
5434 | "and _uppercase_ letters" ); |
5435 | } |
5436 | |
5437 | *to_ptr= 0; |
5438 | to->type= ERR_SQLSTATE; |
5439 | DBUG_PRINT("info" , ("ERR_SQLSTATE: %s" , to->code.sqlstate)); |
5440 | } |
5441 | else if (*p == 's') |
5442 | { |
5443 | die("The sqlstate definition must start with an uppercase S" ); |
5444 | } |
5445 | else if (*p == 'E' || *p == 'W' || *p == 'H') |
5446 | { |
5447 | /* Error name string */ |
5448 | |
5449 | DBUG_PRINT("info" , ("Error name: %s" , p)); |
5450 | to->code.errnum= get_errcode_from_name(p, end); |
5451 | to->type= ERR_ERRNO; |
5452 | DBUG_PRINT("info" , ("ERR_ERRNO: %d" , to->code.errnum)); |
5453 | } |
5454 | else if (*p == 'e' || *p == 'w' || *p == 'h') |
5455 | { |
5456 | die("The error name definition must start with an uppercase E or W or H" ); |
5457 | } |
5458 | else |
5459 | { |
5460 | long val; |
5461 | char *start= p; |
5462 | /* Check that the string passed to str2int only contain digits */ |
5463 | while (*p && p != end) |
5464 | { |
5465 | if (!my_isdigit(charset_info, *p)) |
5466 | die("Invalid argument to error: '%s' - " \ |
5467 | "the errno may only consist of digits[0-9]" , |
5468 | command->first_argument); |
5469 | p++; |
5470 | } |
5471 | |
5472 | /* Convert the sting to int */ |
5473 | if (!str2int(start, 10, (long) INT_MIN, (long) INT_MAX, &val)) |
5474 | die("Invalid argument to error: '%s'" , command->first_argument); |
5475 | |
5476 | to->code.errnum= (uint) val; |
5477 | to->type= ERR_ERRNO; |
5478 | DBUG_PRINT("info" , ("ERR_ERRNO: %d" , to->code.errnum)); |
5479 | } |
5480 | to++; |
5481 | count++; |
5482 | |
5483 | if (count >= (sizeof(saved_expected_errors.err) / |
5484 | sizeof(struct st_match_err))) |
5485 | die("Too many errorcodes specified" ); |
5486 | |
5487 | /* Set pointer to the end of the last error code */ |
5488 | p= next; |
5489 | |
5490 | /* Find next ',' */ |
5491 | while (*p && *p != ',') |
5492 | p++; |
5493 | |
5494 | if (*p) |
5495 | p++; /* Step past ',' */ |
5496 | |
5497 | } while (*p); |
5498 | |
5499 | command->last_argument= command->first_argument; |
5500 | while (*command->last_argument) |
5501 | command->last_argument++; |
5502 | |
5503 | to->type= ERR_EMPTY; /* End of data */ |
5504 | |
5505 | DBUG_PRINT("info" , ("Expected errors: %d" , count)); |
5506 | saved_expected_errors.count= count; |
5507 | dynstr_free(&ds); |
5508 | DBUG_VOID_RETURN; |
5509 | } |
5510 | |
5511 | |
5512 | /* |
5513 | Get a string; Return ptr to end of string |
5514 | Strings may be surrounded by " or ' |
5515 | |
5516 | If string is a '$variable', return the value of the variable. |
5517 | */ |
5518 | |
5519 | static char *get_string(char **to_ptr, char **from_ptr, |
5520 | struct st_command *command) |
5521 | { |
5522 | char c, sep; |
5523 | char *to= *to_ptr, *from= *from_ptr, *start=to; |
5524 | DBUG_ENTER("get_string" ); |
5525 | |
5526 | /* Find separator */ |
5527 | if (*from == '"' || *from == '\'') |
5528 | sep= *from++; |
5529 | else |
5530 | sep=' '; /* Separated with space */ |
5531 | |
5532 | for ( ; (c=*from) ; from++) |
5533 | { |
5534 | if (c == '\\' && from[1]) |
5535 | { /* Escaped character */ |
5536 | /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */ |
5537 | switch (*++from) { |
5538 | case 'n': |
5539 | *to++= '\n'; |
5540 | break; |
5541 | case 't': |
5542 | *to++= '\t'; |
5543 | break; |
5544 | case 'r': |
5545 | *to++ = '\r'; |
5546 | break; |
5547 | case 'b': |
5548 | *to++ = '\b'; |
5549 | break; |
5550 | case 'Z': /* ^Z must be escaped on Win32 */ |
5551 | *to++='\032'; |
5552 | break; |
5553 | default: |
5554 | *to++ = *from; |
5555 | break; |
5556 | } |
5557 | } |
5558 | else if (c == sep) |
5559 | { |
5560 | if (c == ' ' || c != *++from) |
5561 | break; /* Found end of string */ |
5562 | *to++=c; /* Copy duplicated separator */ |
5563 | } |
5564 | else |
5565 | *to++=c; |
5566 | } |
5567 | if (*from != ' ' && *from) |
5568 | die("Wrong string argument in %s" , command->query); |
5569 | |
5570 | while (my_isspace(charset_info,*from)) /* Point to next string */ |
5571 | from++; |
5572 | |
5573 | *to =0; /* End of string marker */ |
5574 | *to_ptr= to+1; /* Store pointer to end */ |
5575 | *from_ptr= from; |
5576 | |
5577 | /* Check if this was a variable */ |
5578 | if (*start == '$') |
5579 | { |
5580 | const char *end= to; |
5581 | VAR *var=var_get(start, &end, 0, 1); |
5582 | if (var && to == (char*) end+1) |
5583 | { |
5584 | DBUG_PRINT("info" ,("var: '%s' -> '%s'" , start, var->str_val)); |
5585 | DBUG_RETURN(var->str_val); /* return found variable value */ |
5586 | } |
5587 | } |
5588 | DBUG_RETURN(start); |
5589 | } |
5590 | |
5591 | |
5592 | |
5593 | |
5594 | /** |
5595 | Change the current connection to the given st_connection, and update |
5596 | $mysql_get_server_version and $CURRENT_CONNECTION accordingly. |
5597 | */ |
5598 | void set_current_connection(struct st_connection *con) |
5599 | { |
5600 | cur_con= con; |
5601 | /* Update $mysql_get_server_version to that of current connection */ |
5602 | var_set_int("$mysql_get_server_version" , |
5603 | mysql_get_server_version(con->mysql)); |
5604 | /* Update $CURRENT_CONNECTION to the name of the current connection */ |
5605 | var_set_string("$CURRENT_CONNECTION" , con->name); |
5606 | } |
5607 | |
5608 | |
5609 | void select_connection_name(const char *name) |
5610 | { |
5611 | DBUG_ENTER("select_connection_name" ); |
5612 | DBUG_PRINT("enter" ,("name: '%s'" , name)); |
5613 | st_connection *con= find_connection_by_name(name); |
5614 | |
5615 | if (!con) |
5616 | die("connection '%s' not found in connection pool" , name); |
5617 | |
5618 | set_current_connection(con); |
5619 | |
5620 | /* Connection logging if enabled */ |
5621 | if (!disable_connect_log && !disable_query_log) |
5622 | { |
5623 | DYNAMIC_STRING *ds= &ds_res; |
5624 | |
5625 | dynstr_append_mem(ds, "connection " , 11); |
5626 | replace_dynstr_append(ds, name); |
5627 | dynstr_append_mem(ds, ";\n" , 2); |
5628 | } |
5629 | |
5630 | DBUG_VOID_RETURN; |
5631 | } |
5632 | |
5633 | |
5634 | void select_connection(struct st_command *command) |
5635 | { |
5636 | DBUG_ENTER("select_connection" ); |
5637 | static DYNAMIC_STRING ds_connection; |
5638 | const struct command_arg connection_args[] = { |
5639 | { "connection_name" , ARG_STRING, TRUE, &ds_connection, "Name of the connection that we switch to." } |
5640 | }; |
5641 | check_command_args(command, command->first_argument, connection_args, |
5642 | sizeof(connection_args)/sizeof(struct command_arg), |
5643 | ' '); |
5644 | |
5645 | DBUG_PRINT("info" , ("changing connection: %s" , ds_connection.str)); |
5646 | select_connection_name(ds_connection.str); |
5647 | dynstr_free(&ds_connection); |
5648 | DBUG_VOID_RETURN; |
5649 | } |
5650 | |
5651 | |
5652 | void do_close_connection(struct st_command *command) |
5653 | { |
5654 | DBUG_ENTER("do_close_connection" ); |
5655 | |
5656 | struct st_connection *con; |
5657 | static DYNAMIC_STRING ds_connection; |
5658 | const struct command_arg close_connection_args[] = { |
5659 | { "connection_name" , ARG_STRING, TRUE, &ds_connection, |
5660 | "Name of the connection to close." } |
5661 | }; |
5662 | check_command_args(command, command->first_argument, |
5663 | close_connection_args, |
5664 | sizeof(close_connection_args)/sizeof(struct command_arg), |
5665 | ' '); |
5666 | |
5667 | DBUG_PRINT("enter" ,("connection name: '%s'" , ds_connection.str)); |
5668 | |
5669 | if (!(con= find_connection_by_name(ds_connection.str))) |
5670 | die("connection '%s' not found in connection pool" , ds_connection.str); |
5671 | |
5672 | DBUG_PRINT("info" , ("Closing connection %s" , con->name)); |
5673 | #ifndef EMBEDDED_LIBRARY |
5674 | if (command->type == Q_DIRTY_CLOSE) |
5675 | { |
5676 | mariadb_cancel(con->mysql); |
5677 | } |
5678 | #endif /*!EMBEDDED_LIBRARY*/ |
5679 | if (con->stmt) |
5680 | do_stmt_close(con); |
5681 | con->stmt= 0; |
5682 | #ifdef EMBEDDED_LIBRARY |
5683 | /* |
5684 | As query could be still executed in a separate theread |
5685 | we need to check if the query's thread was finished and probably wait |
5686 | (embedded-server specific) |
5687 | */ |
5688 | emb_close_connection(con); |
5689 | #endif /*EMBEDDED_LIBRARY*/ |
5690 | |
5691 | mysql_close(con->mysql); |
5692 | con->mysql= 0; |
5693 | |
5694 | if (con->util_mysql) |
5695 | mysql_close(con->util_mysql); |
5696 | con->util_mysql= 0; |
5697 | con->pending= FALSE; |
5698 | |
5699 | my_free(con->name); |
5700 | |
5701 | /* |
5702 | When the connection is closed set name to "-closed_connection-" |
5703 | to make it possible to reuse the connection name. |
5704 | */ |
5705 | if (!(con->name = my_strdup("-closed_connection-" , MYF(MY_WME)))) |
5706 | die("Out of memory" ); |
5707 | |
5708 | if (con == cur_con) |
5709 | { |
5710 | /* Current connection was closed */ |
5711 | var_set_int("$mysql_get_server_version" , 0xFFFFFFFF); |
5712 | var_set_string("$CURRENT_CONNECTION" , con->name); |
5713 | } |
5714 | |
5715 | /* Connection logging if enabled */ |
5716 | if (!disable_connect_log && !disable_query_log) |
5717 | { |
5718 | DYNAMIC_STRING *ds= &ds_res; |
5719 | |
5720 | dynstr_append_mem(ds, "disconnect " , 11); |
5721 | replace_dynstr_append(ds, ds_connection.str); |
5722 | dynstr_append_mem(ds, ";\n" , 2); |
5723 | } |
5724 | |
5725 | dynstr_free(&ds_connection); |
5726 | DBUG_VOID_RETURN; |
5727 | } |
5728 | |
5729 | |
5730 | /* |
5731 | Connect to a server doing several retries if needed. |
5732 | |
5733 | SYNOPSIS |
5734 | safe_connect() |
5735 | con - connection structure to be used |
5736 | host, user, pass, - connection parameters |
5737 | db, port, sock |
5738 | |
5739 | NOTE |
5740 | |
5741 | Sometimes in a test the client starts before |
5742 | the server - to solve the problem, we try again |
5743 | after some sleep if connection fails the first |
5744 | time |
5745 | |
5746 | This function will try to connect to the given server |
5747 | "opt_max_connect_retries" times and sleep "connection_retry_sleep" |
5748 | seconds between attempts before finally giving up. |
5749 | This helps in situation when the client starts |
5750 | before the server (which happens sometimes). |
5751 | It will only ignore connection errors during these retries. |
5752 | |
5753 | */ |
5754 | |
5755 | void safe_connect(MYSQL* mysql, const char *name, const char *host, |
5756 | const char *user, const char *pass, const char *db, |
5757 | int port, const char *sock) |
5758 | { |
5759 | int failed_attempts= 0; |
5760 | |
5761 | DBUG_ENTER("safe_connect" ); |
5762 | |
5763 | verbose_msg("Connecting to server %s:%d (socket %s) as '%s'" |
5764 | ", connection '%s', attempt %d ..." , |
5765 | host, port, sock, user, name, failed_attempts); |
5766 | |
5767 | mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); |
5768 | mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, |
5769 | "program_name" , "mysqltest" ); |
5770 | while(!mysql_real_connect(mysql, host,user, pass, db, port, sock, |
5771 | CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) |
5772 | { |
5773 | /* |
5774 | Connect failed |
5775 | |
5776 | Only allow retry if this was an error indicating the server |
5777 | could not be contacted. Error code differs depending |
5778 | on protocol/connection type |
5779 | */ |
5780 | |
5781 | if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR || |
5782 | mysql_errno(mysql) == CR_CONNECTION_ERROR) && |
5783 | failed_attempts < opt_max_connect_retries) |
5784 | { |
5785 | verbose_msg("Connect attempt %d/%d failed: %d: %s" , failed_attempts, |
5786 | opt_max_connect_retries, mysql_errno(mysql), |
5787 | mysql_error(mysql)); |
5788 | my_sleep(connection_retry_sleep); |
5789 | } |
5790 | else |
5791 | { |
5792 | if (failed_attempts > 0) |
5793 | die("Could not open connection '%s' after %d attempts: %d %s" , name, |
5794 | failed_attempts, mysql_errno(mysql), mysql_error(mysql)); |
5795 | else |
5796 | die("Could not open connection '%s': %d %s" , name, |
5797 | mysql_errno(mysql), mysql_error(mysql)); |
5798 | } |
5799 | failed_attempts++; |
5800 | } |
5801 | verbose_msg("... Connected." ); |
5802 | DBUG_VOID_RETURN; |
5803 | } |
5804 | |
5805 | |
5806 | /* |
5807 | Connect to a server and handle connection errors in case they occur. |
5808 | |
5809 | SYNOPSIS |
5810 | connect_n_handle_errors() |
5811 | q - context of connect "query" (command) |
5812 | con - connection structure to be used |
5813 | host, user, pass, - connection parameters |
5814 | db, port, sock |
5815 | |
5816 | DESCRIPTION |
5817 | This function will try to establish a connection to server and handle |
5818 | possible errors in the same manner as if "connect" was usual SQL-statement |
5819 | (If error is expected it will ignore it once it occurs and log the |
5820 | "statement" to the query log). |
5821 | Unlike safe_connect() it won't do several attempts. |
5822 | |
5823 | RETURN VALUES |
5824 | 1 - Connected |
5825 | 0 - Not connected |
5826 | |
5827 | */ |
5828 | |
5829 | int connect_n_handle_errors(struct st_command *command, |
5830 | MYSQL* con, const char* host, |
5831 | const char* user, const char* pass, |
5832 | const char* db, int port, const char* sock) |
5833 | { |
5834 | DYNAMIC_STRING *ds; |
5835 | int failed_attempts= 0; |
5836 | |
5837 | ds= &ds_res; |
5838 | |
5839 | /* Only log if an error is expected */ |
5840 | if (command->expected_errors.count > 0 && |
5841 | !disable_query_log) |
5842 | { |
5843 | /* |
5844 | Log the connect to result log |
5845 | */ |
5846 | dynstr_append_mem(ds, "connect(" , 8); |
5847 | replace_dynstr_append(ds, host); |
5848 | dynstr_append_mem(ds, "," , 1); |
5849 | replace_dynstr_append(ds, user); |
5850 | dynstr_append_mem(ds, "," , 1); |
5851 | replace_dynstr_append(ds, pass); |
5852 | dynstr_append_mem(ds, "," , 1); |
5853 | if (db) |
5854 | replace_dynstr_append(ds, db); |
5855 | dynstr_append_mem(ds, "," , 1); |
5856 | replace_dynstr_append_uint(ds, port); |
5857 | dynstr_append_mem(ds, "," , 1); |
5858 | if (sock) |
5859 | replace_dynstr_append(ds, sock); |
5860 | dynstr_append_mem(ds, ")" , 1); |
5861 | dynstr_append_mem(ds, delimiter, delimiter_length); |
5862 | dynstr_append_mem(ds, "\n" , 1); |
5863 | } |
5864 | /* Simlified logging if enabled */ |
5865 | if (!disable_connect_log && !disable_query_log) |
5866 | { |
5867 | replace_dynstr_append(ds, command->query); |
5868 | dynstr_append_mem(ds, ";\n" , 2); |
5869 | } |
5870 | |
5871 | mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, 0); |
5872 | mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name" , "mysqltest" ); |
5873 | while (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock: 0, |
5874 | CLIENT_MULTI_STATEMENTS)) |
5875 | { |
5876 | /* |
5877 | If we have used up all our connections check whether this |
5878 | is expected (by --error). If so, handle the error right away. |
5879 | Otherwise, give it some extra time to rule out race-conditions. |
5880 | If extra-time doesn't help, we have an unexpected error and |
5881 | must abort -- just proceeding to handle_error() when second |
5882 | and third chances are used up will handle that for us. |
5883 | |
5884 | There are various user-limits of which only max_user_connections |
5885 | and max_connections_per_hour apply at connect time. For the |
5886 | the second to create a race in our logic, we'd need a limits |
5887 | test that runs without a FLUSH for longer than an hour, so we'll |
5888 | stay clear of trying to work out which exact user-limit was |
5889 | exceeded. |
5890 | */ |
5891 | |
5892 | if (((mysql_errno(con) == ER_TOO_MANY_USER_CONNECTIONS) || |
5893 | (mysql_errno(con) == ER_USER_LIMIT_REACHED)) && |
5894 | (failed_attempts++ < opt_max_connect_retries)) |
5895 | { |
5896 | int i; |
5897 | |
5898 | i= match_expected_error(command, mysql_errno(con), mysql_sqlstate(con)); |
5899 | |
5900 | if (i >= 0) |
5901 | goto do_handle_error; /* expected error, handle */ |
5902 | |
5903 | my_sleep(connection_retry_sleep); /* unexpected error, wait */ |
5904 | continue; /* and give it 1 more chance */ |
5905 | } |
5906 | |
5907 | do_handle_error: |
5908 | var_set_errno(mysql_errno(con)); |
5909 | handle_error(command, mysql_errno(con), mysql_error(con), |
5910 | mysql_sqlstate(con), ds); |
5911 | return 0; /* Not connected */ |
5912 | } |
5913 | |
5914 | var_set_errno(0); |
5915 | handle_no_error(command); |
5916 | revert_properties(); |
5917 | return 1; /* Connected */ |
5918 | } |
5919 | |
5920 | |
5921 | /* |
5922 | Open a new connection to MySQL Server with the parameters |
5923 | specified. Make the new connection the current connection. |
5924 | |
5925 | SYNOPSIS |
5926 | do_connect() |
5927 | q called command |
5928 | |
5929 | DESCRIPTION |
5930 | connect(<name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]); |
5931 | connect <name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]; |
5932 | |
5933 | <name> - name of the new connection |
5934 | <host> - hostname of server |
5935 | <user> - user to connect as |
5936 | <pass> - password used when connecting |
5937 | <db> - initial db when connected |
5938 | <port> - server port |
5939 | <sock> - server socket |
5940 | <opts> - options to use for the connection |
5941 | * SSL - use SSL if available |
5942 | * COMPRESS - use compression if available |
5943 | * SHM - use shared memory if available |
5944 | * PIPE - use named pipe if available |
5945 | |
5946 | */ |
5947 | |
5948 | void do_connect(struct st_command *command) |
5949 | { |
5950 | int con_port= opt_port; |
5951 | char *con_options; |
5952 | char *ssl_cipher __attribute__((unused))= 0; |
5953 | my_bool con_ssl= 0, con_compress= 0; |
5954 | my_bool con_pipe= 0; |
5955 | my_bool con_shm __attribute__ ((unused))= 0; |
5956 | int read_timeout= 0; |
5957 | int write_timeout= 0; |
5958 | int connect_timeout= 0; |
5959 | struct st_connection* con_slot; |
5960 | |
5961 | static DYNAMIC_STRING ds_connection_name; |
5962 | static DYNAMIC_STRING ds_host; |
5963 | static DYNAMIC_STRING ds_user; |
5964 | static DYNAMIC_STRING ds_password; |
5965 | static DYNAMIC_STRING ds_database; |
5966 | static DYNAMIC_STRING ds_port; |
5967 | static DYNAMIC_STRING ds_sock; |
5968 | static DYNAMIC_STRING ds_options; |
5969 | static DYNAMIC_STRING ds_default_auth; |
5970 | #ifdef HAVE_SMEM |
5971 | static DYNAMIC_STRING ds_shm; |
5972 | #endif |
5973 | const struct command_arg connect_args[] = { |
5974 | { "connection name" , ARG_STRING, TRUE, &ds_connection_name, "Name of the connection" }, |
5975 | { "host" , ARG_STRING, TRUE, &ds_host, "Host to connect to" }, |
5976 | { "user" , ARG_STRING, FALSE, &ds_user, "User to connect as" }, |
5977 | { "passsword" , ARG_STRING, FALSE, &ds_password, "Password used when connecting" }, |
5978 | { "database" , ARG_STRING, FALSE, &ds_database, "Database to select after connect" }, |
5979 | { "port" , ARG_STRING, FALSE, &ds_port, "Port to connect to" }, |
5980 | { "socket" , ARG_STRING, FALSE, &ds_sock, "Socket to connect with" }, |
5981 | { "options" , ARG_STRING, FALSE, &ds_options, "Options to use while connecting" }, |
5982 | { "default_auth" , ARG_STRING, FALSE, &ds_default_auth, "Default authentication to use" } |
5983 | }; |
5984 | |
5985 | DBUG_ENTER("do_connect" ); |
5986 | DBUG_PRINT("enter" ,("connect: %s" , command->first_argument)); |
5987 | |
5988 | strip_parentheses(command); |
5989 | check_command_args(command, command->first_argument, connect_args, |
5990 | sizeof(connect_args)/sizeof(struct command_arg), |
5991 | ','); |
5992 | |
5993 | /* Port */ |
5994 | if (ds_port.length) |
5995 | { |
5996 | con_port= atoi(ds_port.str); |
5997 | if (con_port == 0) |
5998 | die("Illegal argument for port: '%s'" , ds_port.str); |
5999 | } |
6000 | |
6001 | #ifdef HAVE_SMEM |
6002 | /* Shared memory */ |
6003 | init_dynamic_string(&ds_shm, ds_sock.str, 0, 0); |
6004 | #endif |
6005 | |
6006 | /* Sock */ |
6007 | if (ds_sock.length) |
6008 | { |
6009 | /* |
6010 | If the socket is specified just as a name without path |
6011 | append tmpdir in front |
6012 | */ |
6013 | if (*ds_sock.str != FN_LIBCHAR) |
6014 | { |
6015 | char buff[FN_REFLEN]; |
6016 | fn_format(buff, ds_sock.str, TMPDIR, "" , 0); |
6017 | dynstr_set(&ds_sock, buff); |
6018 | } |
6019 | } |
6020 | else |
6021 | { |
6022 | /* No socket specified, use default */ |
6023 | dynstr_set(&ds_sock, unix_sock); |
6024 | } |
6025 | DBUG_PRINT("info" , ("socket: %s" , ds_sock.str)); |
6026 | |
6027 | |
6028 | /* Options */ |
6029 | con_options= ds_options.str; |
6030 | while (*con_options) |
6031 | { |
6032 | size_t length; |
6033 | char *end; |
6034 | /* Step past any spaces in beginning of option*/ |
6035 | while (*con_options && my_isspace(charset_info, *con_options)) |
6036 | con_options++; |
6037 | /* Find end of this option */ |
6038 | end= con_options; |
6039 | while (*end && !my_isspace(charset_info, *end)) |
6040 | end++; |
6041 | length= (size_t) (end - con_options); |
6042 | if (length == 3 && !strncmp(con_options, "SSL" , 3)) |
6043 | con_ssl= 1; |
6044 | else if (!strncmp(con_options, "SSL-CIPHER=" , 11)) |
6045 | { |
6046 | con_ssl= 1; |
6047 | ssl_cipher=con_options + 11; |
6048 | } |
6049 | else if (length == 8 && !strncmp(con_options, "COMPRESS" , 8)) |
6050 | con_compress= 1; |
6051 | else if (length == 4 && !strncmp(con_options, "PIPE" , 4)) |
6052 | con_pipe= 1; |
6053 | else if (length == 3 && !strncmp(con_options, "SHM" , 3)) |
6054 | con_shm= 1; |
6055 | else if (strncasecmp(con_options, "read_timeout=" , |
6056 | sizeof("read_timeout=" )-1) == 0) |
6057 | { |
6058 | read_timeout= atoi(con_options + sizeof("read_timeout=" )-1); |
6059 | } |
6060 | else if (strncasecmp(con_options, "write_timeout=" , |
6061 | sizeof("write_timeout=" )-1) == 0) |
6062 | { |
6063 | write_timeout= atoi(con_options + sizeof("write_timeout=" )-1); |
6064 | } |
6065 | else if (strncasecmp(con_options, "connect_timeout=" , |
6066 | sizeof("connect_timeout=" )-1) == 0) |
6067 | { |
6068 | connect_timeout= atoi(con_options + sizeof("connect_timeout=" )-1); |
6069 | } |
6070 | else |
6071 | die("Illegal option to connect: %.*s" , |
6072 | (int) (end - con_options), con_options); |
6073 | /* Process next option */ |
6074 | con_options= end; |
6075 | } |
6076 | |
6077 | if (find_connection_by_name(ds_connection_name.str)) |
6078 | die("Connection %s already exists" , ds_connection_name.str); |
6079 | |
6080 | if (next_con != connections_end) |
6081 | con_slot= next_con; |
6082 | else |
6083 | { |
6084 | if (!(con_slot= find_connection_by_name("-closed_connection-" ))) |
6085 | die("Connection limit exhausted, you can have max %d connections" , |
6086 | opt_max_connections); |
6087 | my_free(con_slot->name); |
6088 | con_slot->name= 0; |
6089 | } |
6090 | |
6091 | init_connection_thd(con_slot); |
6092 | |
6093 | if (!(con_slot->mysql= mysql_init(0))) |
6094 | die("Failed on mysql_init()" ); |
6095 | |
6096 | if (opt_connect_timeout) |
6097 | mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, |
6098 | (void *) &opt_connect_timeout); |
6099 | #ifndef MY_CONTEXT_DISABLE |
6100 | if (mysql_options(con_slot->mysql, MYSQL_OPT_NONBLOCK, 0)) |
6101 | die("Failed to initialise non-blocking API" ); |
6102 | #endif |
6103 | if (opt_compress || con_compress) |
6104 | mysql_options(con_slot->mysql, MYSQL_OPT_COMPRESS, NullS); |
6105 | mysql_options(con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, 0); |
6106 | mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_NAME, |
6107 | charset_info->csname); |
6108 | if (opt_charsets_dir) |
6109 | mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_DIR, |
6110 | opt_charsets_dir); |
6111 | #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) |
6112 | if (opt_use_ssl) |
6113 | con_ssl= 1; |
6114 | #endif |
6115 | |
6116 | if (con_ssl) |
6117 | { |
6118 | #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) |
6119 | mysql_ssl_set(con_slot->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, |
6120 | opt_ssl_capath, ssl_cipher ? ssl_cipher : opt_ssl_cipher); |
6121 | mysql_options(con_slot->mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); |
6122 | mysql_options(con_slot->mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); |
6123 | #if MYSQL_VERSION_ID >= 50000 |
6124 | /* Turn on ssl_verify_server_cert only if host is "localhost" */ |
6125 | opt_ssl_verify_server_cert= !strcmp(ds_host.str, "localhost" ); |
6126 | mysql_options(con_slot->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, |
6127 | &opt_ssl_verify_server_cert); |
6128 | #endif |
6129 | #endif |
6130 | } |
6131 | |
6132 | if (con_pipe) |
6133 | { |
6134 | #ifdef _WIN32 |
6135 | opt_protocol= MYSQL_PROTOCOL_PIPE; |
6136 | #endif |
6137 | } |
6138 | |
6139 | if (opt_protocol) |
6140 | mysql_options(con_slot->mysql, MYSQL_OPT_PROTOCOL, (char*) &opt_protocol); |
6141 | |
6142 | if (read_timeout) |
6143 | { |
6144 | mysql_options(con_slot->mysql, MYSQL_OPT_READ_TIMEOUT, |
6145 | (char*)&read_timeout); |
6146 | } |
6147 | |
6148 | if (write_timeout) |
6149 | { |
6150 | mysql_options(con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT, |
6151 | (char*)&write_timeout); |
6152 | } |
6153 | |
6154 | if (connect_timeout) |
6155 | { |
6156 | mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, |
6157 | (char*)&connect_timeout); |
6158 | } |
6159 | |
6160 | #ifdef HAVE_SMEM |
6161 | if (con_shm) |
6162 | { |
6163 | uint protocol= MYSQL_PROTOCOL_MEMORY; |
6164 | if (!ds_shm.length) |
6165 | die("Missing shared memory base name" ); |
6166 | mysql_options(con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME, ds_shm.str); |
6167 | mysql_options(con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol); |
6168 | } |
6169 | else if (shared_memory_base_name) |
6170 | { |
6171 | mysql_options(con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME, |
6172 | shared_memory_base_name); |
6173 | } |
6174 | #endif |
6175 | |
6176 | /* Use default db name */ |
6177 | if (ds_database.length == 0) |
6178 | dynstr_set(&ds_database, opt_db); |
6179 | |
6180 | if (opt_plugin_dir && *opt_plugin_dir) |
6181 | mysql_options(con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); |
6182 | |
6183 | if (ds_default_auth.length) |
6184 | mysql_options(con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str); |
6185 | |
6186 | /* Special database to allow one to connect without a database name */ |
6187 | if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*" )) |
6188 | dynstr_set(&ds_database, "" ); |
6189 | |
6190 | if (connect_n_handle_errors(command, con_slot->mysql, |
6191 | ds_host.str,ds_user.str, |
6192 | ds_password.str, ds_database.str, |
6193 | con_port, ds_sock.str)) |
6194 | { |
6195 | DBUG_PRINT("info" , ("Inserting connection %s in connection pool" , |
6196 | ds_connection_name.str)); |
6197 | if (!(con_slot->name= my_strdup(ds_connection_name.str, MYF(MY_WME)))) |
6198 | die("Out of memory" ); |
6199 | con_slot->name_len= strlen(con_slot->name); |
6200 | set_current_connection(con_slot); |
6201 | |
6202 | if (con_slot == next_con) |
6203 | next_con++; /* if we used the next_con slot, advance the pointer */ |
6204 | } |
6205 | |
6206 | dynstr_free(&ds_connection_name); |
6207 | dynstr_free(&ds_host); |
6208 | dynstr_free(&ds_user); |
6209 | dynstr_free(&ds_password); |
6210 | dynstr_free(&ds_database); |
6211 | dynstr_free(&ds_port); |
6212 | dynstr_free(&ds_sock); |
6213 | dynstr_free(&ds_options); |
6214 | dynstr_free(&ds_default_auth); |
6215 | #ifdef HAVE_SMEM |
6216 | dynstr_free(&ds_shm); |
6217 | #endif |
6218 | DBUG_VOID_RETURN; |
6219 | } |
6220 | |
6221 | |
6222 | int do_done(struct st_command *command) |
6223 | { |
6224 | /* Check if empty block stack */ |
6225 | if (cur_block == block_stack) |
6226 | { |
6227 | if (*command->query != '}') |
6228 | die("Stray 'end' command - end of block before beginning" ); |
6229 | die("Stray '}' - end of block before beginning" ); |
6230 | } |
6231 | |
6232 | /* Test if inner block has been executed */ |
6233 | if (cur_block->ok && cur_block->cmd == cmd_while) |
6234 | { |
6235 | /* Pop block from stack, re-execute outer block */ |
6236 | cur_block--; |
6237 | parser.current_line = cur_block->line; |
6238 | } |
6239 | else |
6240 | { |
6241 | if (*cur_block->delim) |
6242 | { |
6243 | /* Restore "old" delimiter after false if block */ |
6244 | strcpy (delimiter, cur_block->delim); |
6245 | delimiter_length= strlen(delimiter); |
6246 | } |
6247 | /* Pop block from stack, goto next line */ |
6248 | cur_block--; |
6249 | parser.current_line++; |
6250 | } |
6251 | return 0; |
6252 | } |
6253 | |
6254 | /* Operands available in if or while conditions */ |
6255 | |
6256 | enum block_op { |
6257 | EQ_OP, |
6258 | NE_OP, |
6259 | GT_OP, |
6260 | GE_OP, |
6261 | LT_OP, |
6262 | LE_OP, |
6263 | ILLEG_OP |
6264 | }; |
6265 | |
6266 | |
6267 | enum block_op find_operand(const char *start) |
6268 | { |
6269 | char first= *start; |
6270 | char next= *(start+1); |
6271 | |
6272 | if (first == '=' && next == '=') |
6273 | return EQ_OP; |
6274 | if (first == '!' && next == '=') |
6275 | return NE_OP; |
6276 | if (first == '>' && next == '=') |
6277 | return GE_OP; |
6278 | if (first == '>') |
6279 | return GT_OP; |
6280 | if (first == '<' && next == '=') |
6281 | return LE_OP; |
6282 | if (first == '<') |
6283 | return LT_OP; |
6284 | |
6285 | return ILLEG_OP; |
6286 | } |
6287 | |
6288 | |
6289 | /* |
6290 | Process start of a "if" or "while" statement |
6291 | |
6292 | SYNOPSIS |
6293 | do_block() |
6294 | cmd Type of block |
6295 | q called command |
6296 | |
6297 | DESCRIPTION |
6298 | if ([!]<expr>) |
6299 | { |
6300 | <block statements> |
6301 | } |
6302 | |
6303 | while ([!]<expr>) |
6304 | { |
6305 | <block statements> |
6306 | } |
6307 | |
6308 | Evaluates the <expr> and if it evaluates to |
6309 | greater than zero executes the following code block. |
6310 | A '!' can be used before the <expr> to indicate it should |
6311 | be executed if it evaluates to zero. |
6312 | |
6313 | <expr> can also be a simple comparison condition: |
6314 | |
6315 | <variable> <op> <expr> |
6316 | |
6317 | The left hand side must be a variable, the right hand side can be a |
6318 | variable, number, string or `query`. Operands are ==, !=, <, <=, >, >=. |
6319 | == and != can be used for strings, all can be used for numerical values. |
6320 | */ |
6321 | |
6322 | void do_block(enum block_cmd cmd, struct st_command* command) |
6323 | { |
6324 | char *p= command->first_argument; |
6325 | const char *expr_start, *expr_end; |
6326 | VAR v; |
6327 | const char *cmd_name= (cmd == cmd_while ? "while" : "if" ); |
6328 | my_bool not_expr= FALSE; |
6329 | DBUG_ENTER("do_block" ); |
6330 | DBUG_PRINT("enter" , ("%s" , cmd_name)); |
6331 | |
6332 | /* Check stack overflow */ |
6333 | if (cur_block == block_stack_end) |
6334 | die("Nesting too deeply" ); |
6335 | |
6336 | /* Set way to find outer block again, increase line counter */ |
6337 | cur_block->line= parser.current_line++; |
6338 | |
6339 | /* If this block is ignored */ |
6340 | if (!cur_block->ok) |
6341 | { |
6342 | /* Inner block should be ignored too */ |
6343 | cur_block++; |
6344 | cur_block->cmd= cmd; |
6345 | cur_block->ok= FALSE; |
6346 | cur_block->delim[0]= '\0'; |
6347 | DBUG_VOID_RETURN; |
6348 | } |
6349 | |
6350 | /* Parse and evaluate test expression */ |
6351 | expr_start= strchr(p, '('); |
6352 | if (!expr_start++) |
6353 | die("missing '(' in %s" , cmd_name); |
6354 | |
6355 | while (my_isspace(charset_info, *expr_start)) |
6356 | expr_start++; |
6357 | |
6358 | /* Check for !<expr> */ |
6359 | if (*expr_start == '!') |
6360 | { |
6361 | not_expr= TRUE; |
6362 | expr_start++; /* Step past the '!', then any whitespace */ |
6363 | while (*expr_start && my_isspace(charset_info, *expr_start)) |
6364 | expr_start++; |
6365 | } |
6366 | /* Find ending ')' */ |
6367 | expr_end= strrchr(expr_start, ')'); |
6368 | if (!expr_end) |
6369 | die("missing ')' in %s" , cmd_name); |
6370 | p= (char*)expr_end+1; |
6371 | |
6372 | while (*p && my_isspace(charset_info, *p)) |
6373 | p++; |
6374 | if (*p && *p != '{') |
6375 | die("Missing '{' after %s. Found \"%s\"" , cmd_name, p); |
6376 | |
6377 | var_init(&v,0,0,0,0); |
6378 | |
6379 | /* If expression starts with a variable, it may be a compare condition */ |
6380 | |
6381 | if (*expr_start == '$') |
6382 | { |
6383 | const char *curr_ptr= expr_end; |
6384 | eval_expr(&v, expr_start, &curr_ptr, true); |
6385 | while (my_isspace(charset_info, *++curr_ptr)) |
6386 | {} |
6387 | /* If there was nothing past the variable, skip condition part */ |
6388 | if (curr_ptr == expr_end) |
6389 | goto NO_COMPARE; |
6390 | |
6391 | enum block_op operand= find_operand(curr_ptr); |
6392 | if (operand == ILLEG_OP) |
6393 | die("Found junk '%.*s' after $variable in condition" , |
6394 | (int)(expr_end - curr_ptr), curr_ptr); |
6395 | |
6396 | /* We could silently allow this, but may be confusing */ |
6397 | if (not_expr) |
6398 | die("Negation and comparison should not be combined, please rewrite" ); |
6399 | |
6400 | /* Skip the 1 or 2 chars of the operand, then white space */ |
6401 | if (operand == LT_OP || operand == GT_OP) |
6402 | { |
6403 | curr_ptr++; |
6404 | } |
6405 | else |
6406 | { |
6407 | curr_ptr+= 2; |
6408 | } |
6409 | while (my_isspace(charset_info, *curr_ptr)) |
6410 | curr_ptr++; |
6411 | if (curr_ptr == expr_end) |
6412 | die("Missing right operand in comparison" ); |
6413 | |
6414 | /* Strip off trailing white space */ |
6415 | while (my_isspace(charset_info, expr_end[-1])) |
6416 | expr_end--; |
6417 | /* strip off ' or " around the string */ |
6418 | if (*curr_ptr == '\'' || *curr_ptr == '"') |
6419 | { |
6420 | if (expr_end[-1] != *curr_ptr) |
6421 | die("Unterminated string value" ); |
6422 | curr_ptr++; |
6423 | expr_end--; |
6424 | } |
6425 | VAR v2; |
6426 | var_init(&v2,0,0,0,0); |
6427 | eval_expr(&v2, curr_ptr, &expr_end); |
6428 | |
6429 | if ((operand!=EQ_OP && operand!=NE_OP) && ! (v.is_int && v2.is_int)) |
6430 | die("Only == and != are supported for string values" ); |
6431 | |
6432 | /* Now we overwrite the first variable with 0 or 1 (for false or true) */ |
6433 | |
6434 | switch (operand) |
6435 | { |
6436 | case EQ_OP: |
6437 | if (v.is_int) |
6438 | v.int_val= (v2.is_int && v2.int_val == v.int_val); |
6439 | else |
6440 | v.int_val= !strcmp (v.str_val, v2.str_val); |
6441 | break; |
6442 | |
6443 | case NE_OP: |
6444 | if (v.is_int) |
6445 | v.int_val= ! (v2.is_int && v2.int_val == v.int_val); |
6446 | else |
6447 | v.int_val= (strcmp (v.str_val, v2.str_val) != 0); |
6448 | break; |
6449 | |
6450 | case LT_OP: |
6451 | v.int_val= (v.int_val < v2.int_val); |
6452 | break; |
6453 | case LE_OP: |
6454 | v.int_val= (v.int_val <= v2.int_val); |
6455 | break; |
6456 | case GT_OP: |
6457 | v.int_val= (v.int_val > v2.int_val); |
6458 | break; |
6459 | case GE_OP: |
6460 | v.int_val= (v.int_val >= v2.int_val); |
6461 | break; |
6462 | case ILLEG_OP: |
6463 | die("Impossible operator, this cannot happen" ); |
6464 | } |
6465 | |
6466 | v.is_int= TRUE; |
6467 | var_free(&v2); |
6468 | } else |
6469 | { |
6470 | if (*expr_start != '`' && ! my_isdigit(charset_info, *expr_start)) |
6471 | die("Expression in if/while must beging with $, ` or a number" ); |
6472 | eval_expr(&v, expr_start, &expr_end); |
6473 | } |
6474 | |
6475 | NO_COMPARE: |
6476 | /* Define inner block */ |
6477 | cur_block++; |
6478 | cur_block->cmd= cmd; |
6479 | if (v.is_int) |
6480 | { |
6481 | cur_block->ok= (v.int_val != 0); |
6482 | } else |
6483 | /* Any non-empty string which does not begin with 0 is also TRUE */ |
6484 | { |
6485 | p= v.str_val; |
6486 | /* First skip any leading white space or unary -+ */ |
6487 | while (*p && ((my_isspace(charset_info, *p) || *p == '-' || *p == '+'))) |
6488 | p++; |
6489 | |
6490 | cur_block->ok= (*p && *p != '0') ? TRUE : FALSE; |
6491 | } |
6492 | |
6493 | if (not_expr) |
6494 | cur_block->ok = !cur_block->ok; |
6495 | |
6496 | if (cur_block->ok) |
6497 | { |
6498 | cur_block->delim[0]= '\0'; |
6499 | } else |
6500 | { |
6501 | /* Remember "old" delimiter if entering a false if block */ |
6502 | strcpy (cur_block->delim, delimiter); |
6503 | } |
6504 | |
6505 | DBUG_PRINT("info" , ("OK: %d" , cur_block->ok)); |
6506 | |
6507 | var_free(&v); |
6508 | DBUG_VOID_RETURN; |
6509 | } |
6510 | |
6511 | |
6512 | void do_delimiter(struct st_command* command) |
6513 | { |
6514 | char* p= command->first_argument; |
6515 | DBUG_ENTER("do_delimiter" ); |
6516 | DBUG_PRINT("enter" , ("first_argument: %s" , command->first_argument)); |
6517 | |
6518 | while (*p && my_isspace(charset_info, *p)) |
6519 | p++; |
6520 | |
6521 | if (!(*p)) |
6522 | die("Can't set empty delimiter" ); |
6523 | |
6524 | delimiter_length= (uint)(strmake_buf(delimiter, p) - delimiter); |
6525 | |
6526 | DBUG_PRINT("exit" , ("delimiter: %s" , delimiter)); |
6527 | command->last_argument= p + delimiter_length; |
6528 | DBUG_VOID_RETURN; |
6529 | } |
6530 | |
6531 | |
6532 | my_bool match_delimiter(int c, const char *delim, size_t length) |
6533 | { |
6534 | uint i; |
6535 | char tmp[MAX_DELIMITER_LENGTH]; |
6536 | |
6537 | if (c != *delim) |
6538 | return 0; |
6539 | |
6540 | for (i= 1; i < length && |
6541 | (c= my_getc(cur_file->file)) == *(delim + i); |
6542 | i++) |
6543 | tmp[i]= c; |
6544 | |
6545 | if (i == length) |
6546 | return 1; /* Found delimiter */ |
6547 | |
6548 | /* didn't find delimiter, push back things that we read */ |
6549 | my_ungetc(c); |
6550 | while (i > 1) |
6551 | my_ungetc(tmp[--i]); |
6552 | return 0; |
6553 | } |
6554 | |
6555 | |
6556 | my_bool end_of_query(int c) |
6557 | { |
6558 | return match_delimiter(c, delimiter, delimiter_length); |
6559 | } |
6560 | |
6561 | |
6562 | static inline bool is_escape_char(char c, char in_string) |
6563 | { |
6564 | if (c != '\\' || in_string == '`') return false; |
6565 | if (!cur_con) return true; |
6566 | uint server_status= cur_con->mysql->server_status; |
6567 | if (server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) return false; |
6568 | return !(server_status & SERVER_STATUS_ANSI_QUOTES && in_string == '"'); |
6569 | } |
6570 | |
6571 | |
6572 | /* |
6573 | Read one "line" from the file |
6574 | |
6575 | SYNOPSIS |
6576 | read_line |
6577 | buf buffer for the read line |
6578 | size size of the buffer i.e max size to read |
6579 | |
6580 | DESCRIPTION |
6581 | This function actually reads several lines and adds them to the |
6582 | buffer buf. It continues to read until it finds what it believes |
6583 | is a complete query. |
6584 | |
6585 | Normally that means it will read lines until it reaches the |
6586 | "delimiter" that marks end of query. Default delimiter is ';' |
6587 | The function should be smart enough not to detect delimiter's |
6588 | found inside strings surrounded with '"' and '\'' escaped strings. |
6589 | |
6590 | If the first line in a query starts with '#' or '-' this line is treated |
6591 | as a comment. A comment is always terminated when end of line '\n' is |
6592 | reached. |
6593 | |
6594 | */ |
6595 | |
6596 | int read_line(char *buf, int size) |
6597 | { |
6598 | char c, last_quote=0, last_char= 0; |
6599 | char *p= buf, *buf_end= buf + size - 1; |
6600 | int skip_char= 0; |
6601 | my_bool have_slash= FALSE; |
6602 | |
6603 | enum {R_NORMAL, R_Q, R_SLASH_IN_Q, |
6604 | , R_LINE_START} state= R_LINE_START; |
6605 | DBUG_ENTER("read_line" ); |
6606 | |
6607 | start_lineno= cur_file->lineno; |
6608 | DBUG_PRINT("info" , ("Starting to read at lineno: %d" , start_lineno)); |
6609 | for (; p < buf_end ;) |
6610 | { |
6611 | skip_char= 0; |
6612 | c= my_getc(cur_file->file); |
6613 | if (feof(cur_file->file)) |
6614 | { |
6615 | found_eof: |
6616 | if (cur_file->file != stdin) |
6617 | { |
6618 | fclose(cur_file->file); |
6619 | cur_file->file= 0; |
6620 | } |
6621 | my_free(cur_file->file_name); |
6622 | cur_file->file_name= 0; |
6623 | if (cur_file == file_stack) |
6624 | { |
6625 | /* We're back at the first file, check if |
6626 | all { have matching } |
6627 | */ |
6628 | if (cur_block != block_stack) |
6629 | die("Missing end of block" ); |
6630 | |
6631 | *p= 0; |
6632 | DBUG_PRINT("info" , ("end of file at line %d" , cur_file->lineno)); |
6633 | DBUG_RETURN(1); |
6634 | } |
6635 | cur_file--; |
6636 | start_lineno= cur_file->lineno; |
6637 | continue; |
6638 | } |
6639 | |
6640 | if (c == '\n') |
6641 | { |
6642 | /* Line counting is independent of state */ |
6643 | cur_file->lineno++; |
6644 | |
6645 | /* Convert cr/lf to lf */ |
6646 | if (p != buf && *(p-1) == '\r') |
6647 | p--; |
6648 | } |
6649 | |
6650 | switch(state) { |
6651 | case R_NORMAL: |
6652 | if (end_of_query(c)) |
6653 | { |
6654 | *p= 0; |
6655 | DBUG_PRINT("exit" , ("Found delimiter '%s' at line %d" , |
6656 | delimiter, cur_file->lineno)); |
6657 | DBUG_RETURN(0); |
6658 | } |
6659 | else if ((c == '{' && |
6660 | (!my_strnncoll_simple(charset_info, (const uchar*) "while" , 5, |
6661 | (uchar*) buf, MY_MIN(5, p - buf), 0) || |
6662 | !my_strnncoll_simple(charset_info, (const uchar*) "if" , 2, |
6663 | (uchar*) buf, MY_MIN(2, p - buf), 0)))) |
6664 | { |
6665 | /* Only if and while commands can be terminated by { */ |
6666 | *p++= c; |
6667 | *p= 0; |
6668 | DBUG_PRINT("exit" , ("Found '{' indicating start of block at line %d" , |
6669 | cur_file->lineno)); |
6670 | DBUG_RETURN(0); |
6671 | } |
6672 | else if (c == '\'' || c == '"' || c == '`') |
6673 | { |
6674 | if (! have_slash) |
6675 | { |
6676 | last_quote= c; |
6677 | state= R_Q; |
6678 | } |
6679 | } |
6680 | have_slash= is_escape_char(c, last_quote); |
6681 | break; |
6682 | |
6683 | case R_COMMENT: |
6684 | if (c == '\n') |
6685 | { |
6686 | /* Comments are terminated by newline */ |
6687 | *p= 0; |
6688 | DBUG_PRINT("exit" , ("Found newline in comment at line: %d" , |
6689 | cur_file->lineno)); |
6690 | DBUG_RETURN(0); |
6691 | } |
6692 | break; |
6693 | |
6694 | case R_LINE_START: |
6695 | if (c == '#' || c == '-') |
6696 | { |
6697 | /* A # or - in the first position of the line - this is a comment */ |
6698 | state = R_COMMENT; |
6699 | } |
6700 | else if (my_isspace(charset_info, c)) |
6701 | { |
6702 | if (c == '\n') |
6703 | { |
6704 | if (last_char == '\n') |
6705 | { |
6706 | /* Two new lines in a row, return empty line */ |
6707 | DBUG_PRINT("info" , ("Found two new lines in a row" )); |
6708 | *p++= c; |
6709 | *p= 0; |
6710 | DBUG_RETURN(0); |
6711 | } |
6712 | |
6713 | /* Query hasn't started yet */ |
6714 | start_lineno= cur_file->lineno; |
6715 | DBUG_PRINT("info" , ("Query hasn't started yet, start_lineno: %d" , |
6716 | start_lineno)); |
6717 | } |
6718 | |
6719 | /* Skip all space at beginning of line */ |
6720 | skip_char= 1; |
6721 | } |
6722 | else if (end_of_query(c)) |
6723 | { |
6724 | *p= 0; |
6725 | DBUG_PRINT("exit" , ("Found delimiter '%s' at line: %d" , |
6726 | delimiter, cur_file->lineno)); |
6727 | DBUG_RETURN(0); |
6728 | } |
6729 | else if (c == '}') |
6730 | { |
6731 | /* A "}" need to be by itself in the beginning of a line to terminate */ |
6732 | *p++= c; |
6733 | *p= 0; |
6734 | DBUG_PRINT("exit" , ("Found '}' in beginning of a line at line: %d" , |
6735 | cur_file->lineno)); |
6736 | DBUG_RETURN(0); |
6737 | } |
6738 | else if (c == '\'' || c == '"' || c == '`') |
6739 | { |
6740 | last_quote= c; |
6741 | state= R_Q; |
6742 | } |
6743 | else |
6744 | state= R_NORMAL; |
6745 | break; |
6746 | |
6747 | case R_Q: |
6748 | if (c == last_quote) |
6749 | state= R_NORMAL; |
6750 | else if (is_escape_char(c, last_quote)) |
6751 | state= R_SLASH_IN_Q; |
6752 | break; |
6753 | |
6754 | case R_SLASH_IN_Q: |
6755 | state= R_Q; |
6756 | break; |
6757 | |
6758 | } |
6759 | |
6760 | last_char= c; |
6761 | |
6762 | if (!skip_char) |
6763 | { |
6764 | *p++= c; |
6765 | if (use_mb(charset_info)) |
6766 | { |
6767 | const char *mb_start= p - 1; |
6768 | /* Could be a multibyte character */ |
6769 | /* See a similar code in "sql_load.cc" */ |
6770 | for ( ; p < buf_end; ) |
6771 | { |
6772 | int charlen= my_charlen(charset_info, mb_start, p); |
6773 | if (charlen > 0) |
6774 | break; /* Full character */ |
6775 | if (MY_CS_IS_TOOSMALL(charlen)) |
6776 | { |
6777 | /* We give up if multibyte character is started but not */ |
6778 | /* completed before we pass buf_end */ |
6779 | c= my_getc(cur_file->file); |
6780 | if (feof(cur_file->file)) |
6781 | goto found_eof; |
6782 | *p++ = c; |
6783 | continue; |
6784 | } |
6785 | DBUG_ASSERT(charlen == MY_CS_ILSEQ); |
6786 | /* It was not a multiline char, push back the characters */ |
6787 | /* We leave first 'c', i.e. pretend it was a normal char */ |
6788 | while (p - 1 > mb_start) |
6789 | my_ungetc(*--p); |
6790 | break; |
6791 | } |
6792 | } |
6793 | } |
6794 | } |
6795 | die("The input buffer is too small for this query.\n" |
6796 | "check your query or increase MAX_QUERY and recompile" ); |
6797 | DBUG_RETURN(0); |
6798 | } |
6799 | |
6800 | |
6801 | /* |
6802 | Convert the read query to result format version 1 |
6803 | |
6804 | That is: After newline, all spaces need to be skipped |
6805 | unless the previous char was a quote |
6806 | |
6807 | This is due to an old bug that has now been fixed, but the |
6808 | version 1 output format is preserved by using this function |
6809 | |
6810 | */ |
6811 | |
6812 | void convert_to_format_v1(char* query) |
6813 | { |
6814 | int last_c_was_quote= 0; |
6815 | char *p= query, *to= query; |
6816 | char *end= strend(query); |
6817 | char last_c; |
6818 | |
6819 | while (p <= end) |
6820 | { |
6821 | if (*p == '\n' && !last_c_was_quote) |
6822 | { |
6823 | *to++ = *p++; /* Save the newline */ |
6824 | |
6825 | /* Skip any spaces on next line */ |
6826 | while (*p && my_isspace(charset_info, *p)) |
6827 | p++; |
6828 | |
6829 | last_c_was_quote= 0; |
6830 | } |
6831 | else if (*p == '\'' || *p == '"' || *p == '`') |
6832 | { |
6833 | last_c= *p; |
6834 | *to++ = *p++; |
6835 | |
6836 | /* Copy anything until the next quote of same type */ |
6837 | while (*p && *p != last_c) |
6838 | *to++ = *p++; |
6839 | |
6840 | *to++ = *p++; |
6841 | |
6842 | last_c_was_quote= 1; |
6843 | } |
6844 | else |
6845 | { |
6846 | *to++ = *p++; |
6847 | last_c_was_quote= 0; |
6848 | } |
6849 | } |
6850 | } |
6851 | |
6852 | |
6853 | /* |
6854 | Check for unexpected "junk" after the end of query |
6855 | This is normally caused by missing delimiters or when |
6856 | switching between different delimiters |
6857 | */ |
6858 | |
6859 | void check_eol_junk_line(const char *line) |
6860 | { |
6861 | const char *p= line; |
6862 | DBUG_ENTER("check_eol_junk_line" ); |
6863 | DBUG_PRINT("enter" , ("line: %s" , line)); |
6864 | |
6865 | /* Check for extra delimiter */ |
6866 | if (*p && !strncmp(p, delimiter, delimiter_length)) |
6867 | die("Extra delimiter \"%s\" found" , delimiter); |
6868 | |
6869 | /* Allow trailing # comment */ |
6870 | if (*p && *p != '#') |
6871 | { |
6872 | if (*p == '\n') |
6873 | die("Missing delimiter" ); |
6874 | die("End of line junk detected: \"%s\"" , p); |
6875 | } |
6876 | DBUG_VOID_RETURN; |
6877 | } |
6878 | |
6879 | void check_eol_junk(const char *eol) |
6880 | { |
6881 | const char *p= eol; |
6882 | DBUG_ENTER("check_eol_junk" ); |
6883 | DBUG_PRINT("enter" , ("eol: %s" , eol)); |
6884 | |
6885 | /* Skip past all spacing chars and comments */ |
6886 | while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) |
6887 | { |
6888 | /* Skip past comments started with # and ended with newline */ |
6889 | if (*p && *p == '#') |
6890 | { |
6891 | p++; |
6892 | while (*p && *p != '\n') |
6893 | p++; |
6894 | } |
6895 | |
6896 | /* Check this line */ |
6897 | if (*p && *p == '\n') |
6898 | check_eol_junk_line(p); |
6899 | |
6900 | if (*p) |
6901 | p++; |
6902 | } |
6903 | |
6904 | check_eol_junk_line(p); |
6905 | |
6906 | DBUG_VOID_RETURN; |
6907 | } |
6908 | |
6909 | |
6910 | bool is_delimiter(const char* p) |
6911 | { |
6912 | uint match= 0; |
6913 | char* delim= delimiter; |
6914 | while (*p && *p == *delim++) |
6915 | { |
6916 | match++; |
6917 | p++; |
6918 | } |
6919 | |
6920 | return (match == delimiter_length); |
6921 | } |
6922 | |
6923 | |
6924 | /* |
6925 | Create a command from a set of lines |
6926 | |
6927 | SYNOPSIS |
6928 | read_command() |
6929 | command_ptr pointer where to return the new query |
6930 | |
6931 | DESCRIPTION |
6932 | Converts lines returned by read_line into a command, this involves |
6933 | parsing the first word in the read line to find the command type. |
6934 | |
6935 | A -- comment may contain a valid query as the first word after the |
6936 | comment start. Thus it's always checked to see if that is the case. |
6937 | The advantage with this approach is to be able to execute commands |
6938 | terminated by new line '\n' regardless how many "delimiter" it contain. |
6939 | */ |
6940 | |
6941 | #define MAX_QUERY (256*1024*2) /* 256K -- a test in sp-big is >128K */ |
6942 | static char read_command_buf[MAX_QUERY]; |
6943 | |
6944 | int read_command(struct st_command** command_ptr) |
6945 | { |
6946 | char *p= read_command_buf; |
6947 | struct st_command* command; |
6948 | DBUG_ENTER("read_command" ); |
6949 | |
6950 | if (parser.current_line < parser.read_lines) |
6951 | { |
6952 | get_dynamic(&q_lines, command_ptr, parser.current_line) ; |
6953 | DBUG_PRINT("info" , ("query: %s" , (*command_ptr)->query)); |
6954 | DBUG_RETURN(0); |
6955 | } |
6956 | if (!(*command_ptr= command= |
6957 | (struct st_command*) my_malloc(sizeof(*command), |
6958 | MYF(MY_WME|MY_ZEROFILL))) || |
6959 | insert_dynamic(&q_lines, &command)) |
6960 | die("Out of memory" ); |
6961 | command->type= Q_UNKNOWN; |
6962 | |
6963 | read_command_buf[0]= 0; |
6964 | if (read_line(read_command_buf, sizeof(read_command_buf))) |
6965 | { |
6966 | check_eol_junk(read_command_buf); |
6967 | DBUG_RETURN(1); |
6968 | } |
6969 | |
6970 | if (opt_result_format_version == 1) |
6971 | convert_to_format_v1(read_command_buf); |
6972 | |
6973 | DBUG_PRINT("info" , ("query: '%s'" , read_command_buf)); |
6974 | if (*p == '#') |
6975 | { |
6976 | command->type= Q_COMMENT; |
6977 | } |
6978 | else if (p[0] == '-' && p[1] == '-') |
6979 | { |
6980 | command->type= Q_COMMENT_WITH_COMMAND; |
6981 | p+= 2; /* Skip past -- */ |
6982 | } |
6983 | else if (*p == '\n') |
6984 | { |
6985 | command->type= Q_EMPTY_LINE; |
6986 | } |
6987 | |
6988 | /* Skip leading spaces */ |
6989 | while (*p && my_isspace(charset_info, *p)) |
6990 | p++; |
6991 | |
6992 | if (!(command->query_buf= command->query= my_strdup(p, MYF(MY_WME)))) |
6993 | die("Out of memory" ); |
6994 | |
6995 | /* |
6996 | Calculate first word length(the command), terminated |
6997 | by 'space' , '(' or 'delimiter' */ |
6998 | p= command->query; |
6999 | while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) |
7000 | p++; |
7001 | command->first_word_len= (uint) (p - command->query); |
7002 | DBUG_PRINT("info" , ("first_word: %.*s" , |
7003 | command->first_word_len, command->query)); |
7004 | |
7005 | /* Skip spaces between command and first argument */ |
7006 | while (*p && my_isspace(charset_info, *p)) |
7007 | p++; |
7008 | command->first_argument= p; |
7009 | |
7010 | command->end= strend(command->query); |
7011 | command->query_len= (int)(command->end - command->query); |
7012 | parser.read_lines++; |
7013 | DBUG_RETURN(0); |
7014 | } |
7015 | |
7016 | |
7017 | static struct my_option my_long_options[] = |
7018 | { |
7019 | {"help" , '?', "Display this help and exit." , 0, 0, 0, GET_NO_ARG, NO_ARG, |
7020 | 0, 0, 0, 0, 0, 0}, |
7021 | {"basedir" , 'b', "Basedir for tests." , &opt_basedir, |
7022 | &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7023 | {"character-sets-dir" , 0, |
7024 | "Directory for character set files." , &opt_charsets_dir, |
7025 | &opt_charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7026 | {"compress" , 'C', "Use the compressed server/client protocol." , |
7027 | &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, |
7028 | 0, 0, 0}, |
7029 | {"continue-on-error" , 0, |
7030 | "Continue test even if we got an error. " |
7031 | "This is mostly useful when testing a storage engine to see what from a test file it can execute, " |
7032 | "or to find all syntax errors in a newly created big test file" , |
7033 | &opt_continue_on_error, &opt_continue_on_error, 0, |
7034 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7035 | {"cursor-protocol" , 0, "Use cursors for prepared statements." , |
7036 | &cursor_protocol, &cursor_protocol, 0, |
7037 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7038 | {"database" , 'D', "Database to use." , &opt_db, &opt_db, 0, |
7039 | GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7040 | #ifdef DBUG_OFF |
7041 | {"debug" , '#', "This is a non-debug version. Catch this and exit" , |
7042 | 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, |
7043 | #else |
7044 | {"debug" , '#', "Output debug log. Often this is 'd:t:o,filename'." , |
7045 | 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, |
7046 | #endif |
7047 | {"debug-check" , 0, "Check memory and open file usage at exit." , |
7048 | &debug_check_flag, &debug_check_flag, 0, |
7049 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7050 | {"debug-info" , 0, "Print some debug info at exit." , |
7051 | &debug_info_flag, &debug_info_flag, |
7052 | 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7053 | {"host" , 'h', "Connect to host." , &opt_host, &opt_host, 0, |
7054 | GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7055 | {"prologue" , 0, "Include SQL before each test case." , &opt_prologue, |
7056 | &opt_prologue, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7057 | {"logdir" , OPT_LOG_DIR, "Directory for log files" , &opt_logdir, |
7058 | &opt_logdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7059 | {"mark-progress" , 0, |
7060 | "Write line number and elapsed time to <testname>.progress." , |
7061 | &opt_mark_progress, &opt_mark_progress, 0, |
7062 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7063 | {"max-connect-retries" , 0, |
7064 | "Maximum number of attempts to connect to server." , |
7065 | &opt_max_connect_retries, &opt_max_connect_retries, 0, |
7066 | GET_INT, REQUIRED_ARG, 500, 1, 10000, 0, 0, 0}, |
7067 | {"max-connections" , 0, |
7068 | "Max number of open connections to server" , |
7069 | &opt_max_connections, &opt_max_connections, 0, |
7070 | GET_INT, REQUIRED_ARG, DEFAULT_MAX_CONN, 8, 5120, 0, 0, 0}, |
7071 | {"password" , 'p', "Password to use when connecting to server." , |
7072 | 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, |
7073 | {"protocol" , OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe,memory)." , |
7074 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7075 | {"port" , 'P', "Port number to use for connection or 0 for default to, in " |
7076 | "order of preference, my.cnf, $MYSQL_TCP_PORT, " |
7077 | #if MYSQL_PORT_DEFAULT == 0 |
7078 | "/etc/services, " |
7079 | #endif |
7080 | "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ")." , |
7081 | &opt_port, &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7082 | {"ps-protocol" , 0, |
7083 | "Use prepared-statement protocol for communication." , |
7084 | &ps_protocol, &ps_protocol, 0, |
7085 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7086 | {"non-blocking-api" , 0, |
7087 | "Use the non-blocking client API for communication." , |
7088 | &non_blocking_api_enabled, &non_blocking_api_enabled, 0, |
7089 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7090 | {"quiet" , 's', "Suppress all normal output." , &silent, |
7091 | &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7092 | {"record" , 'r', "Record output of test_file into result file." , |
7093 | 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7094 | {"result-file" , 'R', "Read/store result from/in this file." , |
7095 | &result_file_name, &result_file_name, 0, |
7096 | GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7097 | {"result-format-version" , OPT_RESULT_FORMAT_VERSION, |
7098 | "Version of the result file format to use" , |
7099 | &opt_result_format_version, |
7100 | &opt_result_format_version, 0, |
7101 | GET_INT, REQUIRED_ARG, 1, 1, 2, 0, 0, 0}, |
7102 | {"server-arg" , 'A', "Send option value to embedded server as a parameter." , |
7103 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7104 | {"server-file" , 'F', "Read embedded server arguments from file." , |
7105 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7106 | {"shared-memory-base-name" , 0, |
7107 | "Base name of shared memory." , &shared_memory_base_name, |
7108 | &shared_memory_base_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, |
7109 | 0, 0, 0}, |
7110 | {"silent" , 's', "Suppress all normal output. Synonym for --quiet." , |
7111 | &silent, &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7112 | {"sleep" , 'T', "Always sleep this many seconds on sleep commands." , |
7113 | &opt_sleep, &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, -1, 0, |
7114 | 0, 0, 0}, |
7115 | {"socket" , 'S', "The socket file to use for connection." , |
7116 | &unix_sock, &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, |
7117 | 0, 0, 0}, |
7118 | {"sp-protocol" , 0, "Use stored procedures for select." , |
7119 | &sp_protocol, &sp_protocol, 0, |
7120 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7121 | #include "sslopt-longopts.h" |
7122 | {"tail-lines" , 0, |
7123 | "Number of lines of the result to include in a failure report." , |
7124 | &opt_tail_lines, &opt_tail_lines, 0, |
7125 | GET_INT, REQUIRED_ARG, 0, 0, 10000, 0, 0, 0}, |
7126 | {"test-file" , 'x', "Read test from/in this file (default stdin)." , |
7127 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7128 | {"timer-file" , 'm', "File where the timing in microseconds is stored." , |
7129 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7130 | {"tmpdir" , 't', "Temporary directory where sockets are put." , |
7131 | 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7132 | {"user" , 'u', "User for login." , &opt_user, &opt_user, 0, |
7133 | GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7134 | {"verbose" , 'v', "Write more." , &verbose, &verbose, 0, |
7135 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7136 | {"version" , 'V', "Output version information and exit." , |
7137 | 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7138 | {"view-protocol" , 0, "Use views for select." , |
7139 | &view_protocol, &view_protocol, 0, |
7140 | GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, |
7141 | {"connect_timeout" , 0, |
7142 | "Number of seconds before connection timeout." , |
7143 | &opt_connect_timeout, &opt_connect_timeout, 0, GET_UINT, REQUIRED_ARG, |
7144 | 120, 0, 3600 * 12, 0, 0, 0}, |
7145 | {"wait_for_pos_timeout" , 0, |
7146 | "Number of seconds to wait for master_pos_wait" , |
7147 | &opt_wait_for_pos_timeout, &opt_wait_for_pos_timeout, 0, GET_UINT, |
7148 | REQUIRED_ARG, 300, 0, 3600 * 12, 0, 0, 0}, |
7149 | {"plugin_dir" , 0, "Directory for client-side plugins." , |
7150 | &opt_plugin_dir, &opt_plugin_dir, 0, |
7151 | GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7152 | {"overlay-dir" , 0, "Overlay directory." , &opt_overlay_dir, |
7153 | &opt_overlay_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7154 | {"suite-dir" , 0, "Suite directory." , &opt_suite_dir, |
7155 | &opt_suite_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
7156 | { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} |
7157 | }; |
7158 | |
7159 | |
7160 | void print_version(void) |
7161 | { |
7162 | printf("%s Ver %s Distrib %s, for %s (%s)\n" ,my_progname,MTEST_VERSION, |
7163 | MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); |
7164 | } |
7165 | |
7166 | void usage() |
7167 | { |
7168 | print_version(); |
7169 | puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000" )); |
7170 | printf("Runs a test against the mysql server and compares output with a results file.\n\n" ); |
7171 | printf("Usage: %s [OPTIONS] [database] < test_file\n" , my_progname); |
7172 | print_defaults("my" ,load_default_groups); |
7173 | puts("" ); |
7174 | my_print_help(my_long_options); |
7175 | my_print_variables(my_long_options); |
7176 | } |
7177 | |
7178 | |
7179 | /* |
7180 | Read arguments for embedded server and put them into |
7181 | embedded_server_args[] |
7182 | */ |
7183 | |
7184 | void read_embedded_server_arguments(const char *name) |
7185 | { |
7186 | char argument[1024],buff[FN_REFLEN], *str=0; |
7187 | FILE *file; |
7188 | |
7189 | if (!test_if_hard_path(name)) |
7190 | { |
7191 | strxmov(buff, opt_basedir, name, NullS); |
7192 | name=buff; |
7193 | } |
7194 | fn_format(buff, name, "" , "" , MY_UNPACK_FILENAME); |
7195 | |
7196 | if (!embedded_server_arg_count) |
7197 | { |
7198 | embedded_server_arg_count=1; |
7199 | embedded_server_args[0]= const_cast<char*>("" ); /* Progname */ |
7200 | } |
7201 | if (!(file=my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(MY_WME)))) |
7202 | die("Failed to open file '%s'" , buff); |
7203 | |
7204 | while (embedded_server_arg_count < MAX_EMBEDDED_SERVER_ARGS && |
7205 | (str=fgets(argument,sizeof(argument), file))) |
7206 | { |
7207 | *(strend(str)-1)=0; /* Remove end newline */ |
7208 | if (!(embedded_server_args[embedded_server_arg_count]= |
7209 | my_strdup(str, MYF(MY_WME)))) |
7210 | { |
7211 | my_fclose(file,MYF(0)); |
7212 | die("Out of memory" ); |
7213 | |
7214 | } |
7215 | embedded_server_arg_count++; |
7216 | } |
7217 | my_fclose(file,MYF(0)); |
7218 | if (str) |
7219 | die("Too many arguments in option file: %s" ,name); |
7220 | |
7221 | return; |
7222 | } |
7223 | |
7224 | |
7225 | static my_bool |
7226 | get_one_option(int optid, const struct my_option *opt, char *argument) |
7227 | { |
7228 | switch(optid) { |
7229 | case '#': |
7230 | #ifndef DBUG_OFF |
7231 | DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace" ); |
7232 | debug_check_flag= 1; |
7233 | debug_info_flag= 1; |
7234 | #endif |
7235 | break; |
7236 | case 'r': |
7237 | record = 1; |
7238 | break; |
7239 | case 'x': |
7240 | { |
7241 | char buff[FN_REFLEN]; |
7242 | if (!test_if_hard_path(argument)) |
7243 | { |
7244 | strxmov(buff, opt_basedir, argument, NullS); |
7245 | argument= buff; |
7246 | } |
7247 | fn_format(buff, argument, "" , "" , MY_UNPACK_FILENAME); |
7248 | DBUG_ASSERT(cur_file == file_stack && cur_file->file == 0); |
7249 | if (!(cur_file->file= |
7250 | fopen(buff, "rb" ))) |
7251 | die("Could not open '%s' for reading, errno: %d" , buff, errno); |
7252 | cur_file->file_name= my_strdup(buff, MYF(MY_FAE)); |
7253 | cur_file->lineno= 1; |
7254 | break; |
7255 | } |
7256 | case 'm': |
7257 | { |
7258 | static char buff[FN_REFLEN]; |
7259 | if (!test_if_hard_path(argument)) |
7260 | { |
7261 | strxmov(buff, opt_basedir, argument, NullS); |
7262 | argument= buff; |
7263 | } |
7264 | fn_format(buff, argument, "" , "" , MY_UNPACK_FILENAME); |
7265 | timer_file= buff; |
7266 | unlink(timer_file); /* Ignore error, may not exist */ |
7267 | break; |
7268 | } |
7269 | case 'p': |
7270 | if (argument == disabled_my_option) |
7271 | argument= const_cast<char*>("" ); // Don't require password |
7272 | if (argument) |
7273 | { |
7274 | my_free(opt_pass); |
7275 | opt_pass= my_strdup(argument, MYF(MY_FAE)); |
7276 | while (*argument) *argument++= 'x'; /* Destroy argument */ |
7277 | tty_password= 0; |
7278 | } |
7279 | else |
7280 | tty_password= 1; |
7281 | break; |
7282 | #include <sslopt-case.h> |
7283 | case 't': |
7284 | strnmov(TMPDIR, argument, sizeof(TMPDIR)); |
7285 | break; |
7286 | case 'A': |
7287 | if (!embedded_server_arg_count) |
7288 | { |
7289 | embedded_server_arg_count=1; |
7290 | embedded_server_args[0]= const_cast<char*>("" ); |
7291 | } |
7292 | if (embedded_server_arg_count == MAX_EMBEDDED_SERVER_ARGS-1 || |
7293 | !(embedded_server_args[embedded_server_arg_count++]= |
7294 | my_strdup(argument, MYF(MY_FAE)))) |
7295 | { |
7296 | die("Can't use server argument" ); |
7297 | } |
7298 | break; |
7299 | case OPT_LOG_DIR: |
7300 | /* Check that the file exists */ |
7301 | if (access(opt_logdir, F_OK) != 0) |
7302 | die("The specified log directory does not exist: '%s'" , opt_logdir); |
7303 | break; |
7304 | case 'F': |
7305 | read_embedded_server_arguments(argument); |
7306 | break; |
7307 | case OPT_RESULT_FORMAT_VERSION: |
7308 | set_result_format_version(opt_result_format_version); |
7309 | break; |
7310 | case 'V': |
7311 | print_version(); |
7312 | exit(0); |
7313 | case OPT_MYSQL_PROTOCOL: |
7314 | #ifndef EMBEDDED_LIBRARY |
7315 | if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, |
7316 | opt->name)) <= 0) |
7317 | exit(1); |
7318 | #endif |
7319 | break; |
7320 | case '?': |
7321 | usage(); |
7322 | exit(0); |
7323 | } |
7324 | return 0; |
7325 | } |
7326 | |
7327 | |
7328 | int parse_args(int argc, char **argv) |
7329 | { |
7330 | load_defaults_or_exit("my" , load_default_groups, &argc, &argv); |
7331 | default_argv= argv; |
7332 | |
7333 | if ((handle_options(&argc, &argv, my_long_options, get_one_option))) |
7334 | exit(1); |
7335 | |
7336 | if (argc > 1) |
7337 | { |
7338 | usage(); |
7339 | exit(1); |
7340 | } |
7341 | if (argc == 1) |
7342 | opt_db= *argv; |
7343 | if (tty_password) |
7344 | opt_pass= get_tty_password(NullS); /* purify tested */ |
7345 | if (debug_info_flag) |
7346 | my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; |
7347 | if (debug_check_flag) |
7348 | my_end_arg|= MY_CHECK_ERROR; |
7349 | |
7350 | if (global_subst != NULL) |
7351 | { |
7352 | char *comma= strstr(global_subst, "," ); |
7353 | if (comma == NULL) |
7354 | die("wrong --global-subst, must be X,Y" ); |
7355 | memcpy(global_subst_from, global_subst, (comma-global_subst)); |
7356 | global_subst_from[comma-global_subst]= 0; |
7357 | memcpy(global_subst_to, comma+1, strlen(comma)); |
7358 | } |
7359 | |
7360 | if (!opt_suite_dir) |
7361 | opt_suite_dir= "./" ; |
7362 | suite_dir_len= strlen(opt_suite_dir); |
7363 | overlay_dir_len= opt_overlay_dir ? strlen(opt_overlay_dir) : 0; |
7364 | |
7365 | if (!record) |
7366 | { |
7367 | /* Check that the result file exists */ |
7368 | if (result_file_name && access(result_file_name, F_OK) != 0) |
7369 | die("The specified result file '%s' does not exist" , |
7370 | result_file_name); |
7371 | } |
7372 | |
7373 | return 0; |
7374 | } |
7375 | |
7376 | /* |
7377 | Write the content of str into file |
7378 | |
7379 | SYNOPSIS |
7380 | str_to_file2 |
7381 | fname - name of file to truncate/create and write to |
7382 | str - content to write to file |
7383 | size - size of content witten to file |
7384 | append - append to file instead of overwriting old file |
7385 | */ |
7386 | |
7387 | void str_to_file2(const char *fname, char *str, size_t size, my_bool append) |
7388 | { |
7389 | int fd; |
7390 | char buff[FN_REFLEN]; |
7391 | int flags= O_WRONLY | O_CREAT; |
7392 | if (!test_if_hard_path(fname)) |
7393 | { |
7394 | strxmov(buff, opt_basedir, fname, NullS); |
7395 | fname= buff; |
7396 | } |
7397 | fn_format(buff, fname, "" , "" , MY_UNPACK_FILENAME); |
7398 | |
7399 | if (!append) |
7400 | flags|= O_TRUNC; |
7401 | if ((fd= my_open(buff, flags, |
7402 | MYF(MY_WME | MY_FFNF))) < 0) |
7403 | die("Could not open '%s' for writing, errno: %d" , buff, errno); |
7404 | if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR) |
7405 | die("Could not find end of file '%s', errno: %d" , buff, errno); |
7406 | if (my_write(fd, (uchar*)str, size, MYF(MY_WME|MY_FNABP))) |
7407 | die("write failed, errno: %d" , errno); |
7408 | my_close(fd, MYF(0)); |
7409 | } |
7410 | |
7411 | /* |
7412 | Write the content of str into file |
7413 | |
7414 | SYNOPSIS |
7415 | str_to_file |
7416 | fname - name of file to truncate/create and write to |
7417 | str - content to write to file |
7418 | size - size of content witten to file |
7419 | */ |
7420 | |
7421 | void str_to_file(const char *fname, char *str, size_t size) |
7422 | { |
7423 | str_to_file2(fname, str, size, FALSE); |
7424 | } |
7425 | |
7426 | |
7427 | void check_regerr(regex_t* r, int err) |
7428 | { |
7429 | char err_buf[1024]; |
7430 | |
7431 | if (err) |
7432 | { |
7433 | regerror(err,r,err_buf,sizeof(err_buf)); |
7434 | die("Regex error: %s\n" , err_buf); |
7435 | } |
7436 | } |
7437 | |
7438 | |
7439 | #ifdef _WIN32 |
7440 | |
7441 | DYNAMIC_ARRAY patterns; |
7442 | |
7443 | /* |
7444 | init_win_path_patterns |
7445 | |
7446 | DESCRIPTION |
7447 | Setup string patterns that will be used to detect filenames that |
7448 | needs to be converted from Win to Unix format |
7449 | |
7450 | */ |
7451 | |
7452 | void init_win_path_patterns() |
7453 | { |
7454 | /* List of string patterns to match in order to find paths */ |
7455 | const char* paths[] = { "$MYSQL_TEST_DIR" , |
7456 | "$MYSQL_TMP_DIR" , |
7457 | "$MYSQLTEST_VARDIR" , |
7458 | "$MASTER_MYSOCK" , |
7459 | "$MYSQL_SHAREDIR" , |
7460 | "$MYSQL_LIBDIR" , |
7461 | "./test/" }; |
7462 | int num_paths= sizeof(paths)/sizeof(char*); |
7463 | int i; |
7464 | char* p; |
7465 | |
7466 | DBUG_ENTER("init_win_path_patterns" ); |
7467 | |
7468 | my_init_dynamic_array(&patterns, sizeof(const char*), 16, 16, MYF(0)); |
7469 | |
7470 | /* Loop through all paths in the array */ |
7471 | for (i= 0; i < num_paths; i++) |
7472 | { |
7473 | VAR* v; |
7474 | if (*(paths[i]) == '$') |
7475 | { |
7476 | v= var_get(paths[i], 0, 0, 0); |
7477 | p= my_strdup(v->str_val, MYF(MY_FAE)); |
7478 | } |
7479 | else |
7480 | p= my_strdup(paths[i], MYF(MY_FAE)); |
7481 | |
7482 | /* Don't insert zero length strings in patterns array */ |
7483 | if (strlen(p) == 0) |
7484 | { |
7485 | my_free(p); |
7486 | continue; |
7487 | } |
7488 | |
7489 | if (insert_dynamic(&patterns, &p)) |
7490 | die("Out of memory" ); |
7491 | |
7492 | DBUG_PRINT("info" , ("p: %s" , p)); |
7493 | while (*p) |
7494 | { |
7495 | if (*p == '/') |
7496 | *p='\\'; |
7497 | p++; |
7498 | } |
7499 | } |
7500 | DBUG_VOID_RETURN; |
7501 | } |
7502 | |
7503 | void free_win_path_patterns() |
7504 | { |
7505 | uint i= 0; |
7506 | for (i=0 ; i < patterns.elements ; i++) |
7507 | { |
7508 | const char** pattern= dynamic_element(&patterns, i, const char**); |
7509 | my_free((void *) *pattern); |
7510 | } |
7511 | delete_dynamic(&patterns); |
7512 | } |
7513 | #endif |
7514 | |
7515 | /* |
7516 | fix_win_paths |
7517 | |
7518 | DESCRIPTION |
7519 | Search the string 'val' for the patterns that are known to be |
7520 | strings that contain filenames. Convert all \ to / in the |
7521 | filenames that are found. |
7522 | |
7523 | Ex: |
7524 | val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' |
7525 | => $MYSQL_TEST_DIR is found by strstr |
7526 | => all \ from c:\mysql\m... until next space is converted into / |
7527 | */ |
7528 | |
7529 | void fix_win_paths(char *val, size_t len) |
7530 | { |
7531 | #ifdef _WIN32 |
7532 | uint i; |
7533 | char *p; |
7534 | |
7535 | DBUG_ENTER("fix_win_paths" ); |
7536 | for (i= 0; i < patterns.elements; i++) |
7537 | { |
7538 | const char** pattern= dynamic_element(&patterns, i, const char**); |
7539 | DBUG_PRINT("info" , ("pattern: %s" , *pattern)); |
7540 | |
7541 | /* Search for the path in string */ |
7542 | while ((p= strstr(val, *pattern))) |
7543 | { |
7544 | DBUG_PRINT("info" , ("Found %s in val p: %s" , *pattern, p)); |
7545 | |
7546 | while (*p && !my_isspace(charset_info, *p)) |
7547 | { |
7548 | if (*p == '\\') |
7549 | *p= '/'; |
7550 | p++; |
7551 | } |
7552 | DBUG_PRINT("info" , ("Converted \\ to /, p: %s" , p)); |
7553 | } |
7554 | } |
7555 | DBUG_PRINT("exit" , (" val: %s, len: %zu" , val, len)); |
7556 | DBUG_VOID_RETURN; |
7557 | #endif |
7558 | } |
7559 | |
7560 | |
7561 | |
7562 | /* |
7563 | Append the result for one field to the dynamic string ds |
7564 | */ |
7565 | |
7566 | void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, |
7567 | char* val, size_t len, my_bool is_null) |
7568 | { |
7569 | char null[]= "NULL" ; |
7570 | |
7571 | if (col_idx < max_replace_column && replace_column[col_idx]) |
7572 | { |
7573 | val= replace_column[col_idx]; |
7574 | len= strlen(val); |
7575 | } |
7576 | else if (is_null) |
7577 | { |
7578 | val= null; |
7579 | len= 4; |
7580 | } |
7581 | #ifdef _WIN32 |
7582 | else if ((field->type == MYSQL_TYPE_DOUBLE || |
7583 | field->type == MYSQL_TYPE_FLOAT ) && |
7584 | field->decimals >= 31) |
7585 | { |
7586 | /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */ |
7587 | char *start= strchr(val, 'e'); |
7588 | if (start && strlen(start) >= 5 && |
7589 | (start[1] == '-' || start[1] == '+') && start[2] == '0') |
7590 | { |
7591 | start+=2; /* Now points at first '0' */ |
7592 | if (field->flags & ZEROFILL_FLAG) |
7593 | { |
7594 | /* Move all chars before the first '0' one step right */ |
7595 | memmove(val + 1, val, start - val); |
7596 | *val= '0'; |
7597 | } |
7598 | else |
7599 | { |
7600 | /* Move all chars after the first '0' one step left */ |
7601 | memmove(start, start + 1, strlen(start)); |
7602 | len--; |
7603 | } |
7604 | } |
7605 | } |
7606 | #endif |
7607 | |
7608 | if (!display_result_vertically) |
7609 | { |
7610 | if (col_idx) |
7611 | dynstr_append_mem(ds, "\t" , 1); |
7612 | replace_dynstr_append_mem(ds, val, len); |
7613 | } |
7614 | else |
7615 | { |
7616 | dynstr_append(ds, field->name); |
7617 | dynstr_append_mem(ds, "\t" , 1); |
7618 | replace_dynstr_append_mem(ds, val, len); |
7619 | dynstr_append_mem(ds, "\n" , 1); |
7620 | } |
7621 | } |
7622 | |
7623 | |
7624 | /* |
7625 | Append all results to the dynamic string separated with '\t' |
7626 | Values may be converted with 'replace_column' |
7627 | */ |
7628 | |
7629 | void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) |
7630 | { |
7631 | MYSQL_ROW row; |
7632 | uint num_fields= mysql_num_fields(res); |
7633 | MYSQL_FIELD *fields= mysql_fetch_fields(res); |
7634 | ulong *lengths; |
7635 | |
7636 | while ((row = mysql_fetch_row(res))) |
7637 | { |
7638 | uint i; |
7639 | lengths = mysql_fetch_lengths(res); |
7640 | for (i = 0; i < num_fields; i++) |
7641 | append_field(ds, i, &fields[i], |
7642 | row[i], lengths[i], !row[i]); |
7643 | if (!display_result_vertically) |
7644 | dynstr_append_mem(ds, "\n" , 1); |
7645 | } |
7646 | } |
7647 | |
7648 | |
7649 | /* |
7650 | Append all results from ps execution to the dynamic string separated |
7651 | with '\t'. Values may be converted with 'replace_column' |
7652 | */ |
7653 | |
7654 | void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, |
7655 | MYSQL_FIELD *fields, uint num_fields) |
7656 | { |
7657 | MYSQL_BIND *my_bind; |
7658 | my_bool *is_null; |
7659 | ulong *length; |
7660 | uint i; |
7661 | int error; |
7662 | |
7663 | /* Allocate array with bind structs, lengths and NULL flags */ |
7664 | my_bind= (MYSQL_BIND*) my_malloc(num_fields * sizeof(MYSQL_BIND), |
7665 | MYF(MY_WME | MY_FAE | MY_ZEROFILL)); |
7666 | length= (ulong*) my_malloc(num_fields * sizeof(ulong), |
7667 | MYF(MY_WME | MY_FAE)); |
7668 | is_null= (my_bool*) my_malloc(num_fields * sizeof(my_bool), |
7669 | MYF(MY_WME | MY_FAE)); |
7670 | |
7671 | /* Allocate data for the result of each field */ |
7672 | for (i= 0; i < num_fields; i++) |
7673 | { |
7674 | uint max_length= fields[i].max_length + 1; |
7675 | my_bind[i].buffer_type= MYSQL_TYPE_STRING; |
7676 | my_bind[i].buffer= my_malloc(max_length, MYF(MY_WME | MY_FAE)); |
7677 | my_bind[i].buffer_length= max_length; |
7678 | my_bind[i].is_null= &is_null[i]; |
7679 | my_bind[i].length= &length[i]; |
7680 | |
7681 | DBUG_PRINT("bind" , ("col[%d]: buffer_type: %d, buffer_length: %lu" , |
7682 | i, my_bind[i].buffer_type, my_bind[i].buffer_length)); |
7683 | } |
7684 | |
7685 | if (mysql_stmt_bind_result(stmt, my_bind)) |
7686 | die("mysql_stmt_bind_result failed: %d: %s" , |
7687 | mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); |
7688 | |
7689 | while ((error=mysql_stmt_fetch(stmt)) == 0) |
7690 | { |
7691 | for (i= 0; i < num_fields; i++) |
7692 | append_field(ds, i, &fields[i], (char*)my_bind[i].buffer, |
7693 | *my_bind[i].length, *my_bind[i].is_null); |
7694 | if (!display_result_vertically) |
7695 | dynstr_append_mem(ds, "\n" , 1); |
7696 | } |
7697 | |
7698 | if (error != MYSQL_NO_DATA) |
7699 | die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: " |
7700 | "error: %d" , error); |
7701 | if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) |
7702 | die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: %d %s" , |
7703 | mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); |
7704 | |
7705 | for (i= 0; i < num_fields; i++) |
7706 | { |
7707 | /* Free data for output */ |
7708 | my_free(my_bind[i].buffer); |
7709 | } |
7710 | /* Free array with bind structs, lengths and NULL flags */ |
7711 | my_free(my_bind); |
7712 | my_free(length); |
7713 | my_free(is_null); |
7714 | } |
7715 | |
7716 | |
7717 | /* |
7718 | Append metadata for fields to output |
7719 | */ |
7720 | |
7721 | void append_metadata(DYNAMIC_STRING *ds, |
7722 | MYSQL_FIELD *field, |
7723 | uint num_fields) |
7724 | { |
7725 | MYSQL_FIELD *field_end; |
7726 | dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" |
7727 | "Column_alias\tType\tLength\tMax length\tIs_null\t" |
7728 | "Flags\tDecimals\tCharsetnr\n" ); |
7729 | |
7730 | for (field_end= field+num_fields ; |
7731 | field < field_end ; |
7732 | field++) |
7733 | { |
7734 | dynstr_append_mem(ds, field->catalog, |
7735 | field->catalog_length); |
7736 | dynstr_append_mem(ds, "\t" , 1); |
7737 | dynstr_append_mem(ds, field->db, field->db_length); |
7738 | dynstr_append_mem(ds, "\t" , 1); |
7739 | dynstr_append_mem(ds, field->org_table, |
7740 | field->org_table_length); |
7741 | dynstr_append_mem(ds, "\t" , 1); |
7742 | dynstr_append_mem(ds, field->table, |
7743 | field->table_length); |
7744 | dynstr_append_mem(ds, "\t" , 1); |
7745 | dynstr_append_mem(ds, field->org_name, |
7746 | field->org_name_length); |
7747 | dynstr_append_mem(ds, "\t" , 1); |
7748 | dynstr_append_mem(ds, field->name, field->name_length); |
7749 | dynstr_append_mem(ds, "\t" , 1); |
7750 | replace_dynstr_append_uint(ds, field->type); |
7751 | dynstr_append_mem(ds, "\t" , 1); |
7752 | replace_dynstr_append_uint(ds, field->length); |
7753 | dynstr_append_mem(ds, "\t" , 1); |
7754 | replace_dynstr_append_uint(ds, field->max_length); |
7755 | dynstr_append_mem(ds, "\t" , 1); |
7756 | dynstr_append_mem(ds, (IS_NOT_NULL(field->flags) ? "N" : "Y" ), 1); |
7757 | dynstr_append_mem(ds, "\t" , 1); |
7758 | replace_dynstr_append_uint(ds, field->flags); |
7759 | dynstr_append_mem(ds, "\t" , 1); |
7760 | replace_dynstr_append_uint(ds, field->decimals); |
7761 | dynstr_append_mem(ds, "\t" , 1); |
7762 | replace_dynstr_append_uint(ds, field->charsetnr); |
7763 | dynstr_append_mem(ds, "\n" , 1); |
7764 | } |
7765 | } |
7766 | |
7767 | |
7768 | /* |
7769 | Append affected row count and other info to output |
7770 | */ |
7771 | |
7772 | void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, |
7773 | const char *info) |
7774 | { |
7775 | char buf[40], buff2[21]; |
7776 | sprintf(buf,"affected rows: %s\n" , llstr(affected_rows, buff2)); |
7777 | dynstr_append(ds, buf); |
7778 | if (info) |
7779 | { |
7780 | dynstr_append(ds, "info: " ); |
7781 | dynstr_append(ds, info); |
7782 | dynstr_append_mem(ds, "\n" , 1); |
7783 | } |
7784 | } |
7785 | |
7786 | |
7787 | /* |
7788 | Display the table headings with the names tab separated |
7789 | */ |
7790 | |
7791 | void append_table_headings(DYNAMIC_STRING *ds, |
7792 | MYSQL_FIELD *field, |
7793 | uint num_fields) |
7794 | { |
7795 | uint col_idx; |
7796 | if (disable_column_names) |
7797 | return; |
7798 | for (col_idx= 0; col_idx < num_fields; col_idx++) |
7799 | { |
7800 | if (col_idx) |
7801 | dynstr_append_mem(ds, "\t" , 1); |
7802 | replace_dynstr_append(ds, field[col_idx].name); |
7803 | } |
7804 | dynstr_append_mem(ds, "\n" , 1); |
7805 | } |
7806 | |
7807 | /* |
7808 | Fetch warnings from server and append to ds |
7809 | |
7810 | RETURN VALUE |
7811 | Number of warnings appended to ds |
7812 | */ |
7813 | |
7814 | int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) |
7815 | { |
7816 | uint count; |
7817 | MYSQL_RES *warn_res; |
7818 | DYNAMIC_STRING res; |
7819 | DBUG_ENTER("append_warnings" ); |
7820 | |
7821 | if (!(count= mysql_warning_count(mysql))) |
7822 | DBUG_RETURN(0); |
7823 | |
7824 | /* |
7825 | If one day we will support execution of multi-statements |
7826 | through PS API we should not issue SHOW WARNINGS until |
7827 | we have not read all results... |
7828 | */ |
7829 | DBUG_ASSERT(!mysql_more_results(mysql)); |
7830 | |
7831 | if (mysql_real_query(mysql, "SHOW WARNINGS" , 13)) |
7832 | die("Error running query \"SHOW WARNINGS\": %s" , mysql_error(mysql)); |
7833 | |
7834 | if (!(warn_res= mysql_store_result(mysql))) |
7835 | die("Warning count is %u but didn't get any warnings" , |
7836 | count); |
7837 | |
7838 | init_dynamic_string(&res, "" , 1024, 1024); |
7839 | |
7840 | append_result(&res, warn_res); |
7841 | mysql_free_result(warn_res); |
7842 | |
7843 | DBUG_PRINT("warnings" , ("%s" , res.str)); |
7844 | |
7845 | if (display_result_sorted) |
7846 | dynstr_append_sorted(ds, &res, 0); |
7847 | else |
7848 | dynstr_append_mem(ds, res.str, res.length); |
7849 | dynstr_free(&res); |
7850 | DBUG_RETURN(count); |
7851 | } |
7852 | |
7853 | |
7854 | /* |
7855 | Handle situation where query is sent but there is no active connection |
7856 | (e.g directly after disconnect). |
7857 | |
7858 | We emulate MySQL-compatible behaviour of sending something on a closed |
7859 | connection. |
7860 | */ |
7861 | static void handle_no_active_connection(struct st_command *command, |
7862 | struct st_connection *cn, DYNAMIC_STRING *ds) |
7863 | { |
7864 | handle_error(command, 2006, "MySQL server has gone away" , "000000" , ds); |
7865 | cn->pending= FALSE; |
7866 | var_set_errno(2006); |
7867 | } |
7868 | |
7869 | |
7870 | /* |
7871 | Run query using MySQL C API |
7872 | |
7873 | SYNOPSIS |
7874 | run_query_normal() |
7875 | mysql mysql handle |
7876 | command current command pointer |
7877 | flags flags indicating if we should SEND and/or REAP |
7878 | query query string to execute |
7879 | query_len length query string to execute |
7880 | ds output buffer where to store result form query |
7881 | */ |
7882 | |
7883 | void run_query_normal(struct st_connection *cn, struct st_command *command, |
7884 | int flags, char *query, size_t query_len, |
7885 | DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) |
7886 | { |
7887 | MYSQL_RES *res= 0; |
7888 | MYSQL *mysql= cn->mysql; |
7889 | int err= 0, counter= 0; |
7890 | DBUG_ENTER("run_query_normal" ); |
7891 | DBUG_PRINT("enter" ,("flags: %d" , flags)); |
7892 | DBUG_PRINT("enter" , ("query: '%-.60s'" , query)); |
7893 | |
7894 | if (!mysql) |
7895 | { |
7896 | handle_no_active_connection(command, cn, ds); |
7897 | DBUG_VOID_RETURN; |
7898 | } |
7899 | |
7900 | if (flags & QUERY_SEND_FLAG) |
7901 | { |
7902 | /* |
7903 | Send the query |
7904 | */ |
7905 | if (do_send_query(cn, query, query_len)) |
7906 | { |
7907 | handle_error(command, mysql_errno(mysql), mysql_error(mysql), |
7908 | mysql_sqlstate(mysql), ds); |
7909 | goto end; |
7910 | } |
7911 | } |
7912 | if (!(flags & QUERY_REAP_FLAG)) |
7913 | { |
7914 | cn->pending= TRUE; |
7915 | DBUG_VOID_RETURN; |
7916 | } |
7917 | |
7918 | do |
7919 | { |
7920 | /* |
7921 | When on first result set, call mysql_read_query_result to retrieve |
7922 | answer to the query sent earlier |
7923 | */ |
7924 | if ((counter==0) && do_read_query_result(cn)) |
7925 | { |
7926 | /* we've failed to collect the result set */ |
7927 | cn->pending= TRUE; |
7928 | handle_error(command, mysql_errno(mysql), mysql_error(mysql), |
7929 | mysql_sqlstate(mysql), ds); |
7930 | goto end; |
7931 | |
7932 | } |
7933 | |
7934 | /* |
7935 | Store the result of the query if it will return any fields |
7936 | */ |
7937 | if (mysql_field_count(mysql) && ((res= mysql_store_result(mysql)) == 0)) |
7938 | { |
7939 | handle_error(command, mysql_errno(mysql), mysql_error(mysql), |
7940 | mysql_sqlstate(mysql), ds); |
7941 | goto end; |
7942 | } |
7943 | |
7944 | if (!disable_result_log) |
7945 | { |
7946 | if (res) |
7947 | { |
7948 | MYSQL_FIELD *fields= mysql_fetch_fields(res); |
7949 | uint num_fields= mysql_num_fields(res); |
7950 | |
7951 | if (display_metadata) |
7952 | append_metadata(ds, fields, num_fields); |
7953 | |
7954 | if (!display_result_vertically) |
7955 | append_table_headings(ds, fields, num_fields); |
7956 | |
7957 | append_result(ds, res); |
7958 | } |
7959 | |
7960 | /* |
7961 | Need to call mysql_affected_rows() before the "new" |
7962 | query to find the warnings. |
7963 | */ |
7964 | if (!disable_info) |
7965 | append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql)); |
7966 | |
7967 | /* |
7968 | Add all warnings to the result. We can't do this if we are in |
7969 | the middle of processing results from multi-statement, because |
7970 | this will break protocol. |
7971 | */ |
7972 | if (!disable_warnings && !mysql_more_results(mysql)) |
7973 | { |
7974 | if (append_warnings(ds_warnings, mysql) || ds_warnings->length) |
7975 | { |
7976 | dynstr_append_mem(ds, "Warnings:\n" , 10); |
7977 | dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length); |
7978 | } |
7979 | } |
7980 | } |
7981 | |
7982 | if (res) |
7983 | { |
7984 | mysql_free_result(res); |
7985 | res= 0; |
7986 | } |
7987 | counter++; |
7988 | } while (!(err= mysql_next_result(mysql))); |
7989 | if (err > 0) |
7990 | { |
7991 | /* We got an error from mysql_next_result, maybe expected */ |
7992 | handle_error(command, mysql_errno(mysql), mysql_error(mysql), |
7993 | mysql_sqlstate(mysql), ds); |
7994 | goto end; |
7995 | } |
7996 | DBUG_ASSERT(err == -1); /* Successful and there are no more results */ |
7997 | |
7998 | /* If we come here the query is both executed and read successfully */ |
7999 | handle_no_error(command); |
8000 | revert_properties(); |
8001 | |
8002 | end: |
8003 | |
8004 | cn->pending= FALSE; |
8005 | /* |
8006 | We save the return code (mysql_errno(mysql)) from the last call sent |
8007 | to the server into the mysqltest builtin variable $mysql_errno. This |
8008 | variable then can be used from the test case itself. |
8009 | */ |
8010 | var_set_errno(mysql_errno(mysql)); |
8011 | DBUG_VOID_RETURN; |
8012 | } |
8013 | |
8014 | |
8015 | /* |
8016 | Check whether given error is in list of expected errors |
8017 | |
8018 | SYNOPSIS |
8019 | match_expected_error() |
8020 | |
8021 | PARAMETERS |
8022 | command the current command (and its expect-list) |
8023 | err_errno error number of the error that actually occurred |
8024 | err_sqlstate SQL-state that was thrown, or NULL for impossible |
8025 | (file-ops, diff, etc.) |
8026 | |
8027 | RETURNS |
8028 | -1 for not in list, index in list of expected errors otherwise |
8029 | |
8030 | NOTE |
8031 | If caller needs to know whether the list was empty, they should |
8032 | check command->expected_errors.count. |
8033 | */ |
8034 | |
8035 | static int match_expected_error(struct st_command *command, |
8036 | unsigned int err_errno, |
8037 | const char *err_sqlstate) |
8038 | { |
8039 | uint i; |
8040 | |
8041 | for (i= 0 ; (uint) i < command->expected_errors.count ; i++) |
8042 | { |
8043 | if ((command->expected_errors.err[i].type == ERR_ERRNO) && |
8044 | (command->expected_errors.err[i].code.errnum == err_errno)) |
8045 | return i; |
8046 | |
8047 | if (command->expected_errors.err[i].type == ERR_SQLSTATE) |
8048 | { |
8049 | /* |
8050 | NULL is quite likely, but not in conjunction with a SQL-state expect! |
8051 | */ |
8052 | if (unlikely(err_sqlstate == NULL)) |
8053 | die("expecting a SQL-state (%s) from query '%s' which cannot " |
8054 | "produce one..." , |
8055 | command->expected_errors.err[i].code.sqlstate, command->query); |
8056 | |
8057 | if (strncmp(command->expected_errors.err[i].code.sqlstate, |
8058 | err_sqlstate, SQLSTATE_LENGTH) == 0) |
8059 | return i; |
8060 | } |
8061 | } |
8062 | return -1; |
8063 | } |
8064 | |
8065 | |
8066 | /* |
8067 | Handle errors which occurred during execution |
8068 | |
8069 | SYNOPSIS |
8070 | handle_error() |
8071 | q - query context |
8072 | err_errno - error number |
8073 | err_error - error message |
8074 | err_sqlstate - sql state |
8075 | ds - dynamic string which is used for output buffer |
8076 | |
8077 | NOTE |
8078 | If there is an unexpected error this function will abort mysqltest |
8079 | immediately. |
8080 | */ |
8081 | |
8082 | void handle_error(struct st_command *command, |
8083 | unsigned int err_errno, const char *err_error, |
8084 | const char *err_sqlstate, DYNAMIC_STRING *ds) |
8085 | { |
8086 | int i; |
8087 | |
8088 | DBUG_ENTER("handle_error" ); |
8089 | |
8090 | command->used_replace= 1; |
8091 | if (command->require_file) |
8092 | { |
8093 | /* |
8094 | The query after a "--require" failed. This is fine as long the server |
8095 | returned a valid response. Don't allow 2013 or 2006 to trigger an |
8096 | abort_not_supported_test |
8097 | */ |
8098 | if (err_errno == CR_SERVER_LOST || |
8099 | err_errno == CR_SERVER_GONE_ERROR) |
8100 | die("require query '%s' failed: %d: %s" , command->query, |
8101 | err_errno, err_error); |
8102 | |
8103 | /* Abort the run of this test, pass the failed query as reason */ |
8104 | abort_not_supported_test("Query '%s' failed, required functionality " \ |
8105 | "not supported" , command->query); |
8106 | } |
8107 | |
8108 | if (command->abort_on_error) |
8109 | { |
8110 | report_or_die("query '%s' failed: %d: %s" , command->query, err_errno, |
8111 | err_error); |
8112 | DBUG_VOID_RETURN; |
8113 | } |
8114 | |
8115 | DBUG_PRINT("info" , ("expected_errors.count: %d" , |
8116 | command->expected_errors.count)); |
8117 | |
8118 | i= match_expected_error(command, err_errno, err_sqlstate); |
8119 | |
8120 | if (i >= 0) |
8121 | { |
8122 | if (!disable_result_log) |
8123 | { |
8124 | if (command->expected_errors.count == 1) |
8125 | { |
8126 | /* Only log error if there is one possible error */ |
8127 | dynstr_append_mem(ds, "ERROR " , 6); |
8128 | replace_dynstr_append(ds, err_sqlstate); |
8129 | dynstr_append_mem(ds, ": " , 2); |
8130 | replace_dynstr_append(ds, err_error); |
8131 | dynstr_append_mem(ds,"\n" ,1); |
8132 | } |
8133 | /* Don't log error if we may not get an error */ |
8134 | else if (command->expected_errors.err[0].type == ERR_SQLSTATE || |
8135 | (command->expected_errors.err[0].type == ERR_ERRNO && |
8136 | command->expected_errors.err[0].code.errnum != 0)) |
8137 | dynstr_append(ds,"Got one of the listed errors\n" ); |
8138 | } |
8139 | /* OK */ |
8140 | revert_properties(); |
8141 | DBUG_VOID_RETURN; |
8142 | } |
8143 | |
8144 | DBUG_PRINT("info" ,("i: %d expected_errors: %d" , i, |
8145 | command->expected_errors.count)); |
8146 | |
8147 | if (!disable_result_log) |
8148 | { |
8149 | dynstr_append_mem(ds, "ERROR " ,6); |
8150 | replace_dynstr_append(ds, err_sqlstate); |
8151 | dynstr_append_mem(ds, ": " , 2); |
8152 | replace_dynstr_append(ds, err_error); |
8153 | dynstr_append_mem(ds, "\n" , 1); |
8154 | } |
8155 | |
8156 | if (command->expected_errors.count > 0) |
8157 | { |
8158 | if (command->expected_errors.err[0].type == ERR_ERRNO) |
8159 | report_or_die("query '%s' failed with wrong errno %d: '%s', instead of " |
8160 | "%d..." , |
8161 | command->query, err_errno, err_error, |
8162 | command->expected_errors.err[0].code.errnum); |
8163 | else |
8164 | report_or_die("query '%s' failed with wrong sqlstate %s: '%s', " |
8165 | "instead of %s..." , |
8166 | command->query, err_sqlstate, err_error, |
8167 | command->expected_errors.err[0].code.sqlstate); |
8168 | } |
8169 | |
8170 | revert_properties(); |
8171 | DBUG_VOID_RETURN; |
8172 | } |
8173 | |
8174 | |
8175 | /* |
8176 | Handle absence of errors after execution |
8177 | |
8178 | SYNOPSIS |
8179 | handle_no_error() |
8180 | q - context of query |
8181 | |
8182 | RETURN VALUE |
8183 | error - function will not return |
8184 | */ |
8185 | |
8186 | void handle_no_error(struct st_command *command) |
8187 | { |
8188 | DBUG_ENTER("handle_no_error" ); |
8189 | |
8190 | if (command->expected_errors.err[0].type == ERR_ERRNO && |
8191 | command->expected_errors.err[0].code.errnum != 0) |
8192 | { |
8193 | /* Error code we wanted was != 0, i.e. not an expected success */ |
8194 | report_or_die("query '%s' succeeded - should have failed with errno %d..." , |
8195 | command->query, command->expected_errors.err[0].code.errnum); |
8196 | } |
8197 | else if (command->expected_errors.err[0].type == ERR_SQLSTATE && |
8198 | strcmp(command->expected_errors.err[0].code.sqlstate,"00000" ) != 0) |
8199 | { |
8200 | /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ |
8201 | report_or_die("query '%s' succeeded - should have failed with " |
8202 | "sqlstate %s..." , |
8203 | command->query, |
8204 | command->expected_errors.err[0].code.sqlstate); |
8205 | } |
8206 | DBUG_VOID_RETURN; |
8207 | } |
8208 | |
8209 | |
8210 | /* |
8211 | Run query using prepared statement C API |
8212 | |
8213 | SYNPOSIS |
8214 | run_query_stmt |
8215 | mysql - mysql handle |
8216 | command - currrent command pointer |
8217 | query - query string to execute |
8218 | query_len - length query string to execute |
8219 | ds - output buffer where to store result form query |
8220 | |
8221 | RETURN VALUE |
8222 | error - function will not return |
8223 | */ |
8224 | |
8225 | void run_query_stmt(struct st_connection *cn, struct st_command *command, |
8226 | char *query, size_t query_len, DYNAMIC_STRING *ds, |
8227 | DYNAMIC_STRING *ds_warnings) |
8228 | { |
8229 | MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ |
8230 | MYSQL *mysql= cn->mysql; |
8231 | MYSQL_STMT *stmt; |
8232 | DYNAMIC_STRING ds_prepare_warnings; |
8233 | DYNAMIC_STRING ds_execute_warnings; |
8234 | DBUG_ENTER("run_query_stmt" ); |
8235 | DBUG_PRINT("query" , ("'%-.60s'" , query)); |
8236 | |
8237 | /* |
8238 | Init a new stmt if it's not already one created for this connection |
8239 | */ |
8240 | if(!(stmt= cn->stmt)) |
8241 | { |
8242 | if (!(stmt= mysql_stmt_init(mysql))) |
8243 | die("unable to init stmt structure" ); |
8244 | cn->stmt= stmt; |
8245 | } |
8246 | |
8247 | /* Init dynamic strings for warnings */ |
8248 | if (!disable_warnings) |
8249 | { |
8250 | init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); |
8251 | init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); |
8252 | } |
8253 | |
8254 | /* |
8255 | Prepare the query |
8256 | */ |
8257 | if (do_stmt_prepare(cn, query, query_len)) |
8258 | { |
8259 | handle_error(command, mysql_stmt_errno(stmt), |
8260 | mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); |
8261 | goto end; |
8262 | } |
8263 | |
8264 | /* |
8265 | Get the warnings from mysql_stmt_prepare and keep them in a |
8266 | separate string |
8267 | */ |
8268 | if (!disable_warnings) |
8269 | append_warnings(&ds_prepare_warnings, mysql); |
8270 | |
8271 | /* |
8272 | No need to call mysql_stmt_bind_param() because we have no |
8273 | parameter markers. |
8274 | */ |
8275 | |
8276 | #if MYSQL_VERSION_ID >= 50000 |
8277 | if (cursor_protocol_enabled) |
8278 | { |
8279 | /* |
8280 | Use cursor when retrieving result |
8281 | */ |
8282 | ulong type= CURSOR_TYPE_READ_ONLY; |
8283 | if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) |
8284 | die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s" , |
8285 | mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); |
8286 | } |
8287 | #endif |
8288 | |
8289 | /* |
8290 | Execute the query |
8291 | */ |
8292 | if (do_stmt_execute(cn)) |
8293 | { |
8294 | handle_error(command, mysql_stmt_errno(stmt), |
8295 | mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); |
8296 | goto end; |
8297 | } |
8298 | |
8299 | /* |
8300 | When running in cursor_protocol get the warnings from execute here |
8301 | and keep them in a separate string for later. |
8302 | */ |
8303 | if (cursor_protocol_enabled && !disable_warnings) |
8304 | append_warnings(&ds_execute_warnings, mysql); |
8305 | |
8306 | /* |
8307 | We instruct that we want to update the "max_length" field in |
8308 | mysql_stmt_store_result(), this is our only way to know how much |
8309 | buffer to allocate for result data |
8310 | */ |
8311 | { |
8312 | my_bool one= 1; |
8313 | if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) |
8314 | die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s" , |
8315 | mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); |
8316 | } |
8317 | |
8318 | /* |
8319 | If we got here the statement succeeded and was expected to do so, |
8320 | get data. Note that this can still give errors found during execution! |
8321 | Store the result of the query if if will return any fields |
8322 | */ |
8323 | if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) |
8324 | { |
8325 | handle_error(command, mysql_stmt_errno(stmt), |
8326 | mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); |
8327 | goto end; |
8328 | } |
8329 | |
8330 | /* If we got here the statement was both executed and read successfully */ |
8331 | handle_no_error(command); |
8332 | if (!disable_result_log) |
8333 | { |
8334 | /* |
8335 | Not all statements creates a result set. If there is one we can |
8336 | now create another normal result set that contains the meta |
8337 | data. This set can be handled almost like any other non prepared |
8338 | statement result set. |
8339 | */ |
8340 | if ((res= mysql_stmt_result_metadata(stmt)) != NULL) |
8341 | { |
8342 | /* Take the column count from meta info */ |
8343 | MYSQL_FIELD *fields= mysql_fetch_fields(res); |
8344 | uint num_fields= mysql_num_fields(res); |
8345 | |
8346 | if (display_metadata) |
8347 | append_metadata(ds, fields, num_fields); |
8348 | |
8349 | if (!display_result_vertically) |
8350 | append_table_headings(ds, fields, num_fields); |
8351 | |
8352 | append_stmt_result(ds, stmt, fields, num_fields); |
8353 | |
8354 | mysql_free_result(res); /* Free normal result set with meta data */ |
8355 | |
8356 | /* |
8357 | Normally, if there is a result set, we do not show warnings from the |
8358 | prepare phase. This is because some warnings are generated both during |
8359 | prepare and execute; this would generate different warning output |
8360 | between normal and ps-protocol test runs. |
8361 | |
8362 | The --enable_prepare_warnings command can be used to change this so |
8363 | that warnings from both the prepare and execute phase are shown. |
8364 | */ |
8365 | if (!disable_warnings && !prepare_warnings_enabled) |
8366 | dynstr_set(&ds_prepare_warnings, NULL); |
8367 | } |
8368 | else |
8369 | { |
8370 | /* |
8371 | This is a query without resultset |
8372 | */ |
8373 | } |
8374 | |
8375 | /* |
8376 | Fetch info before fetching warnings, since it will be reset |
8377 | otherwise. |
8378 | */ |
8379 | if (!disable_info) |
8380 | append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql)); |
8381 | |
8382 | if (!disable_warnings) |
8383 | { |
8384 | /* Get the warnings from execute */ |
8385 | |
8386 | /* Append warnings to ds - if there are any */ |
8387 | if (append_warnings(&ds_execute_warnings, mysql) || |
8388 | ds_execute_warnings.length || |
8389 | ds_prepare_warnings.length || |
8390 | ds_warnings->length) |
8391 | { |
8392 | dynstr_append_mem(ds, "Warnings:\n" , 10); |
8393 | if (ds_warnings->length) |
8394 | dynstr_append_mem(ds, ds_warnings->str, |
8395 | ds_warnings->length); |
8396 | if (ds_prepare_warnings.length) |
8397 | dynstr_append_mem(ds, ds_prepare_warnings.str, |
8398 | ds_prepare_warnings.length); |
8399 | if (ds_execute_warnings.length) |
8400 | dynstr_append_mem(ds, ds_execute_warnings.str, |
8401 | ds_execute_warnings.length); |
8402 | } |
8403 | } |
8404 | } |
8405 | |
8406 | end: |
8407 | if (!disable_warnings) |
8408 | { |
8409 | dynstr_free(&ds_prepare_warnings); |
8410 | dynstr_free(&ds_execute_warnings); |
8411 | } |
8412 | |
8413 | /* |
8414 | We save the return code (mysql_stmt_errno(stmt)) from the last call sent |
8415 | to the server into the mysqltest builtin variable $mysql_errno. This |
8416 | variable then can be used from the test case itself. |
8417 | */ |
8418 | |
8419 | var_set_errno(mysql_stmt_errno(stmt)); |
8420 | |
8421 | revert_properties(); |
8422 | |
8423 | /* Close the statement if reconnect, need new prepare */ |
8424 | { |
8425 | #ifndef EMBEDDED_LIBRARY |
8426 | my_bool reconnect; |
8427 | mysql_get_option(mysql, MYSQL_OPT_RECONNECT, &reconnect); |
8428 | if (reconnect) |
8429 | #else |
8430 | if (mysql->reconnect) |
8431 | #endif |
8432 | { |
8433 | mysql_stmt_close(stmt); |
8434 | cn->stmt= NULL; |
8435 | } |
8436 | } |
8437 | |
8438 | DBUG_VOID_RETURN; |
8439 | } |
8440 | |
8441 | |
8442 | |
8443 | /* |
8444 | Create a util connection if one does not already exists |
8445 | and use that to run the query |
8446 | This is done to avoid implict commit when creating/dropping objects such |
8447 | as view, sp etc. |
8448 | */ |
8449 | |
8450 | int util_query(MYSQL* org_mysql, const char* query){ |
8451 | |
8452 | MYSQL* mysql; |
8453 | DBUG_ENTER("util_query" ); |
8454 | |
8455 | if(!(mysql= cur_con->util_mysql)) |
8456 | { |
8457 | DBUG_PRINT("info" , ("Creating util_mysql" )); |
8458 | if (!(mysql= mysql_init(mysql))) |
8459 | die("Failed in mysql_init()" ); |
8460 | |
8461 | if (opt_connect_timeout) |
8462 | mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, |
8463 | (void *) &opt_connect_timeout); |
8464 | |
8465 | /* enable local infile, in non-binary builds often disabled by default */ |
8466 | mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0); |
8467 | mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); |
8468 | safe_connect(mysql, "util" , org_mysql->host, org_mysql->user, |
8469 | org_mysql->passwd, org_mysql->db, org_mysql->port, |
8470 | org_mysql->unix_socket); |
8471 | |
8472 | cur_con->util_mysql= mysql; |
8473 | } |
8474 | |
8475 | int ret= mysql_query(mysql, query); |
8476 | DBUG_RETURN(ret); |
8477 | } |
8478 | |
8479 | |
8480 | |
8481 | /* |
8482 | Run query |
8483 | |
8484 | SYNPOSIS |
8485 | run_query() |
8486 | mysql mysql handle |
8487 | command currrent command pointer |
8488 | |
8489 | flags control the phased/stages of query execution to be performed |
8490 | if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG |
8491 | is on the result will be read - for regular query, both bits must be on |
8492 | */ |
8493 | |
8494 | void run_query(struct st_connection *cn, struct st_command *command, int flags) |
8495 | { |
8496 | MYSQL *mysql= cn->mysql; |
8497 | DYNAMIC_STRING *ds; |
8498 | DYNAMIC_STRING *save_ds= NULL; |
8499 | DYNAMIC_STRING ds_result; |
8500 | DYNAMIC_STRING ds_sorted; |
8501 | DYNAMIC_STRING ds_warnings; |
8502 | char *query; |
8503 | size_t query_len; |
8504 | my_bool view_created= 0, sp_created= 0; |
8505 | my_bool complete_query= ((flags & QUERY_SEND_FLAG) && |
8506 | (flags & QUERY_REAP_FLAG)); |
8507 | DBUG_ENTER("run_query" ); |
8508 | |
8509 | if (cn->pending && (flags & QUERY_SEND_FLAG)) |
8510 | die("Cannot run query on connection between send and reap" ); |
8511 | |
8512 | if (!(flags & QUERY_SEND_FLAG) && !cn->pending) |
8513 | die("Cannot reap on a connection without pending send" ); |
8514 | |
8515 | init_dynamic_string(&ds_warnings, NULL, 0, 256); |
8516 | ds_warn= &ds_warnings; |
8517 | |
8518 | /* |
8519 | Evaluate query if this is an eval command |
8520 | */ |
8521 | if (command->type == Q_EVAL || command->type == Q_SEND_EVAL || |
8522 | command->type == Q_EVALP) |
8523 | { |
8524 | if (!command->eval_query.str) |
8525 | init_dynamic_string(&command->eval_query, "" , command->query_len + 256, |
8526 | 1024); |
8527 | else |
8528 | dynstr_set(&command->eval_query, 0); |
8529 | do_eval(&command->eval_query, command->query, command->end, FALSE); |
8530 | query= command->eval_query.str; |
8531 | query_len= command->eval_query.length; |
8532 | } |
8533 | else |
8534 | { |
8535 | query = command->query; |
8536 | query_len = strlen(query); |
8537 | } |
8538 | |
8539 | /* |
8540 | When command->require_file is set the output of _this_ query |
8541 | should be compared with an already existing file |
8542 | Create a temporary dynamic string to contain the output from |
8543 | this query. |
8544 | */ |
8545 | if (command->require_file) |
8546 | { |
8547 | init_dynamic_string(&ds_result, "" , 1024, 1024); |
8548 | ds= &ds_result; |
8549 | } |
8550 | else |
8551 | ds= &ds_res; |
8552 | |
8553 | /* |
8554 | Log the query into the output buffer |
8555 | */ |
8556 | if (!disable_query_log && (flags & QUERY_SEND_FLAG)) |
8557 | { |
8558 | char *print_query= query; |
8559 | size_t print_len= query_len; |
8560 | if (flags & QUERY_PRINT_ORIGINAL_FLAG) |
8561 | { |
8562 | print_query= command->query; |
8563 | print_len= (int)(command->end - command->query); |
8564 | } |
8565 | replace_dynstr_append_mem(ds, print_query, print_len); |
8566 | dynstr_append_mem(ds, delimiter, delimiter_length); |
8567 | dynstr_append_mem(ds, "\n" , 1); |
8568 | } |
8569 | |
8570 | /* We're done with this flag */ |
8571 | flags &= ~QUERY_PRINT_ORIGINAL_FLAG; |
8572 | |
8573 | /* |
8574 | Write the command to the result file before we execute the query |
8575 | This is needed to be able to analyse the log if something goes |
8576 | wrong |
8577 | */ |
8578 | log_file.write(&ds_res); |
8579 | log_file.flush(); |
8580 | dynstr_set(&ds_res, 0); |
8581 | |
8582 | if (view_protocol_enabled && |
8583 | complete_query && |
8584 | match_re(&view_re, query)) |
8585 | { |
8586 | /* |
8587 | Create the query as a view. |
8588 | Use replace since view can exist from a failed mysqltest run |
8589 | */ |
8590 | DYNAMIC_STRING query_str; |
8591 | init_dynamic_string(&query_str, |
8592 | "CREATE OR REPLACE VIEW mysqltest_tmp_v AS " , |
8593 | query_len+64, 256); |
8594 | dynstr_append_mem(&query_str, query, query_len); |
8595 | if (util_query(mysql, query_str.str)) |
8596 | { |
8597 | /* |
8598 | Failed to create the view, this is not fatal |
8599 | just run the query the normal way |
8600 | */ |
8601 | DBUG_PRINT("view_create_error" , |
8602 | ("Failed to create view '%s': %d: %s" , query_str.str, |
8603 | mysql_errno(mysql), mysql_error(mysql))); |
8604 | |
8605 | /* Log error to create view */ |
8606 | verbose_msg("Failed to create view '%s' %d: %s" , query_str.str, |
8607 | mysql_errno(mysql), mysql_error(mysql)); |
8608 | } |
8609 | else |
8610 | { |
8611 | /* |
8612 | Yes, it was possible to create this query as a view |
8613 | */ |
8614 | view_created= 1; |
8615 | query= const_cast<char*>("SELECT * FROM mysqltest_tmp_v" ); |
8616 | query_len = strlen(query); |
8617 | |
8618 | /* |
8619 | Collect warnings from create of the view that should otherwise |
8620 | have been produced when the SELECT was executed |
8621 | */ |
8622 | append_warnings(&ds_warnings, cur_con->util_mysql); |
8623 | } |
8624 | |
8625 | dynstr_free(&query_str); |
8626 | } |
8627 | |
8628 | if (sp_protocol_enabled && |
8629 | complete_query && |
8630 | match_re(&sp_re, query)) |
8631 | { |
8632 | /* |
8633 | Create the query as a stored procedure |
8634 | Drop first since sp can exist from a failed mysqltest run |
8635 | */ |
8636 | DYNAMIC_STRING query_str; |
8637 | init_dynamic_string(&query_str, |
8638 | "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;" , |
8639 | query_len+64, 256); |
8640 | util_query(mysql, query_str.str); |
8641 | dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n" ); |
8642 | dynstr_append_mem(&query_str, query, query_len); |
8643 | if (util_query(mysql, query_str.str)) |
8644 | { |
8645 | /* |
8646 | Failed to create the stored procedure for this query, |
8647 | this is not fatal just run the query the normal way |
8648 | */ |
8649 | DBUG_PRINT("sp_create_error" , |
8650 | ("Failed to create sp '%s': %d: %s" , query_str.str, |
8651 | mysql_errno(mysql), mysql_error(mysql))); |
8652 | |
8653 | /* Log error to create sp */ |
8654 | verbose_msg("Failed to create sp '%s' %d: %s" , query_str.str, |
8655 | mysql_errno(mysql), mysql_error(mysql)); |
8656 | |
8657 | } |
8658 | else |
8659 | { |
8660 | sp_created= 1; |
8661 | |
8662 | query= const_cast<char*>("CALL mysqltest_tmp_sp()" ); |
8663 | query_len = strlen(query); |
8664 | } |
8665 | dynstr_free(&query_str); |
8666 | } |
8667 | |
8668 | if (display_result_sorted) |
8669 | { |
8670 | /* |
8671 | Collect the query output in a separate string |
8672 | that can be sorted before it's added to the |
8673 | global result string |
8674 | */ |
8675 | init_dynamic_string(&ds_sorted, "" , 1024, 1024); |
8676 | save_ds= ds; /* Remember original ds */ |
8677 | ds= &ds_sorted; |
8678 | } |
8679 | |
8680 | /* |
8681 | Find out how to run this query |
8682 | |
8683 | Always run with normal C API if it's not a complete |
8684 | SEND + REAP |
8685 | |
8686 | If it is a '?' in the query it may be a SQL level prepared |
8687 | statement already and we can't do it twice |
8688 | */ |
8689 | if (ps_protocol_enabled && |
8690 | complete_query && |
8691 | match_re(&ps_re, query)) |
8692 | run_query_stmt(cn, command, query, query_len, ds, &ds_warnings); |
8693 | else |
8694 | run_query_normal(cn, command, flags, query, query_len, |
8695 | ds, &ds_warnings); |
8696 | |
8697 | dynstr_free(&ds_warnings); |
8698 | ds_warn= 0; |
8699 | |
8700 | if (display_result_sorted) |
8701 | { |
8702 | /* Sort the result set and append it to result */ |
8703 | dynstr_append_sorted(save_ds, &ds_sorted, 1); |
8704 | ds= save_ds; |
8705 | dynstr_free(&ds_sorted); |
8706 | } |
8707 | |
8708 | if (sp_created) |
8709 | { |
8710 | if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp " )) |
8711 | report_or_die("Failed to drop sp: %d: %s" , mysql_errno(mysql), |
8712 | mysql_error(mysql)); |
8713 | } |
8714 | |
8715 | if (view_created) |
8716 | { |
8717 | if (util_query(mysql, "DROP VIEW mysqltest_tmp_v " )) |
8718 | report_or_die("Failed to drop view: %d: %s" , |
8719 | mysql_errno(mysql), mysql_error(mysql)); |
8720 | } |
8721 | |
8722 | if (command->require_file) |
8723 | { |
8724 | /* A result file was specified for _this_ query |
8725 | and the output should be checked against an already |
8726 | existing file which has been specified using --require or --result |
8727 | */ |
8728 | check_require(ds, command->require_file); |
8729 | } |
8730 | |
8731 | if (ds == &ds_result) |
8732 | dynstr_free(&ds_result); |
8733 | DBUG_VOID_RETURN; |
8734 | } |
8735 | |
8736 | /****************************************************************************/ |
8737 | /* |
8738 | Functions to detect different SQL statements |
8739 | */ |
8740 | |
8741 | char *re_eprint(int err) |
8742 | { |
8743 | static char epbuf[100]; |
8744 | size_t len __attribute__((unused))= |
8745 | regerror(err, (regex_t *)NULL, epbuf, sizeof(epbuf)); |
8746 | assert(len <= sizeof(epbuf)); |
8747 | return(epbuf); |
8748 | } |
8749 | |
8750 | void init_re_comp(regex_t *re, const char* str) |
8751 | { |
8752 | int err= regcomp(re, str, (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_DOTALL)); |
8753 | if (err) |
8754 | { |
8755 | char erbuf[100]; |
8756 | size_t len= regerror(err, re, erbuf, sizeof(erbuf)); |
8757 | die("error %s, %d/%d `%s'\n" , |
8758 | re_eprint(err), (int)len, (int)sizeof(erbuf), erbuf); |
8759 | } |
8760 | } |
8761 | |
8762 | void init_re(void) |
8763 | { |
8764 | /* |
8765 | Filter for queries that can be run using the |
8766 | MySQL Prepared Statements C API |
8767 | */ |
8768 | const char *ps_re_str = |
8769 | "^(" |
8770 | "[[:space:]]*REPLACE[[:space:]]|" |
8771 | "[[:space:]]*INSERT[[:space:]]|" |
8772 | "[[:space:]]*UPDATE[[:space:]]|" |
8773 | "[[:space:]]*DELETE[[:space:]]|" |
8774 | "[[:space:]]*SELECT[[:space:]]|" |
8775 | "[[:space:]]*CREATE[[:space:]]+TABLE[[:space:]]|" |
8776 | "[[:space:]]*DO[[:space:]]|" |
8777 | "[[:space:]]*SET[[:space:]]+OPTION[[:space:]]|" |
8778 | "[[:space:]]*DELETE[[:space:]]+MULTI[[:space:]]|" |
8779 | "[[:space:]]*UPDATE[[:space:]]+MULTI[[:space:]]|" |
8780 | "[[:space:]]*INSERT[[:space:]]+SELECT[[:space:]])" ; |
8781 | |
8782 | /* |
8783 | Filter for queries that can be run using the |
8784 | Stored procedures |
8785 | */ |
8786 | const char *sp_re_str =ps_re_str; |
8787 | |
8788 | /* |
8789 | Filter for queries that can be run as views |
8790 | */ |
8791 | const char *view_re_str = |
8792 | "^(" |
8793 | "[[:space:]]*SELECT[[:space:]])" ; |
8794 | |
8795 | init_re_comp(&ps_re, ps_re_str); |
8796 | init_re_comp(&sp_re, sp_re_str); |
8797 | init_re_comp(&view_re, view_re_str); |
8798 | } |
8799 | |
8800 | |
8801 | int match_re(regex_t *re, char *str) |
8802 | { |
8803 | while (my_isspace(charset_info, *str)) |
8804 | str++; |
8805 | if (str[0] == '/' && str[1] == '*') |
8806 | { |
8807 | char *comm_end= strstr (str, "*/" ); |
8808 | if (! comm_end) |
8809 | die("Statement is unterminated comment" ); |
8810 | str= comm_end + 2; |
8811 | } |
8812 | |
8813 | int err= regexec(re, str, (size_t)0, NULL, 0); |
8814 | |
8815 | if (err == 0) |
8816 | return 1; |
8817 | else if (err == REG_NOMATCH) |
8818 | return 0; |
8819 | |
8820 | { |
8821 | char erbuf[100]; |
8822 | size_t len= regerror(err, re, erbuf, sizeof(erbuf)); |
8823 | die("error %s, %d/%d `%s'\n" , |
8824 | re_eprint(err), (int)len, (int)sizeof(erbuf), erbuf); |
8825 | } |
8826 | return 0; |
8827 | } |
8828 | |
8829 | void free_re(void) |
8830 | { |
8831 | regfree(&ps_re); |
8832 | regfree(&sp_re); |
8833 | regfree(&view_re); |
8834 | } |
8835 | |
8836 | /****************************************************************************/ |
8837 | |
8838 | void get_command_type(struct st_command* command) |
8839 | { |
8840 | char save; |
8841 | uint type; |
8842 | DBUG_ENTER("get_command_type" ); |
8843 | |
8844 | if (*command->query == '}') |
8845 | { |
8846 | command->type = Q_END_BLOCK; |
8847 | DBUG_VOID_RETURN; |
8848 | } |
8849 | |
8850 | save= command->query[command->first_word_len]; |
8851 | command->query[command->first_word_len]= 0; |
8852 | type= find_type(command->query, &command_typelib, FIND_TYPE_NO_PREFIX); |
8853 | command->query[command->first_word_len]= save; |
8854 | if (type > 0) |
8855 | { |
8856 | command->type=(enum enum_commands) type; /* Found command */ |
8857 | |
8858 | /* |
8859 | Look for case where "query" was explicitly specified to |
8860 | force command being sent to server |
8861 | */ |
8862 | if (type == Q_QUERY) |
8863 | { |
8864 | /* Skip the "query" part */ |
8865 | command->query= command->first_argument; |
8866 | } |
8867 | } |
8868 | else |
8869 | { |
8870 | /* No mysqltest command matched */ |
8871 | |
8872 | if (command->type != Q_COMMENT_WITH_COMMAND) |
8873 | { |
8874 | /* A query that will sent to mysqld */ |
8875 | command->type= Q_QUERY; |
8876 | } |
8877 | else |
8878 | { |
8879 | /* -- "comment" that didn't contain a mysqltest command */ |
8880 | report_or_die("Found line beginning with -- that didn't contain " \ |
8881 | "a valid mysqltest command, check your syntax or " \ |
8882 | "use # if you intended to write a comment" ); |
8883 | command->type= Q_COMMENT; |
8884 | } |
8885 | } |
8886 | |
8887 | /* Set expected error on command */ |
8888 | memcpy(&command->expected_errors, &saved_expected_errors, |
8889 | sizeof(saved_expected_errors)); |
8890 | DBUG_PRINT("info" , ("There are %d expected errors" , |
8891 | command->expected_errors.count)); |
8892 | DBUG_VOID_RETURN; |
8893 | } |
8894 | |
8895 | |
8896 | |
8897 | /* |
8898 | Record how many milliseconds it took to execute the test file |
8899 | up until the current line and write it to .progress file |
8900 | |
8901 | */ |
8902 | |
8903 | void mark_progress(struct st_command* command __attribute__((unused)), |
8904 | int line) |
8905 | { |
8906 | static ulonglong progress_start= 0; // < Beware |
8907 | DYNAMIC_STRING ds_progress; |
8908 | |
8909 | char buf[32], *end; |
8910 | ulonglong timer= timer_now(); |
8911 | if (!progress_start) |
8912 | progress_start= timer; |
8913 | timer-= progress_start; |
8914 | |
8915 | if (init_dynamic_string(&ds_progress, "" , 256, 256)) |
8916 | die("Out of memory" ); |
8917 | |
8918 | /* Milliseconds since start */ |
8919 | end= longlong10_to_str(timer, buf, 10); |
8920 | dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); |
8921 | dynstr_append_mem(&ds_progress, "\t" , 1); |
8922 | |
8923 | /* Parser line number */ |
8924 | end= int10_to_str(line, buf, 10); |
8925 | dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); |
8926 | dynstr_append_mem(&ds_progress, "\t" , 1); |
8927 | |
8928 | /* Filename */ |
8929 | dynstr_append(&ds_progress, cur_file->file_name); |
8930 | dynstr_append_mem(&ds_progress, ":" , 1); |
8931 | |
8932 | /* Line in file */ |
8933 | end= int10_to_str(cur_file->lineno, buf, 10); |
8934 | dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); |
8935 | |
8936 | |
8937 | dynstr_append_mem(&ds_progress, "\n" , 1); |
8938 | |
8939 | progress_file.write(&ds_progress); |
8940 | |
8941 | dynstr_free(&ds_progress); |
8942 | |
8943 | } |
8944 | |
8945 | #ifdef HAVE_STACKTRACE |
8946 | |
8947 | static void dump_backtrace(void) |
8948 | { |
8949 | struct st_connection *conn= cur_con; |
8950 | |
8951 | fprintf(stderr, "read_command_buf (%p): " , read_command_buf); |
8952 | my_safe_print_str(read_command_buf, sizeof(read_command_buf)); |
8953 | fputc('\n', stderr); |
8954 | |
8955 | if (conn) |
8956 | { |
8957 | fprintf(stderr, "conn->name (%p): " , conn->name); |
8958 | my_safe_print_str(conn->name, conn->name_len); |
8959 | fputc('\n', stderr); |
8960 | #ifdef EMBEDDED_LIBRARY |
8961 | fprintf(stderr, "conn->cur_query (%p): " , conn->cur_query); |
8962 | my_safe_print_str(conn->cur_query, conn->cur_query_len); |
8963 | fputc('\n', stderr); |
8964 | #endif |
8965 | } |
8966 | fputs("Attempting backtrace...\n" , stderr); |
8967 | my_print_stacktrace(NULL, (ulong)my_thread_stack_size, 0); |
8968 | } |
8969 | |
8970 | #else |
8971 | |
8972 | static void dump_backtrace(void) |
8973 | { |
8974 | fputs("Backtrace not available.\n" , stderr); |
8975 | } |
8976 | |
8977 | #endif |
8978 | |
8979 | static sig_handler signal_handler(int sig) |
8980 | { |
8981 | fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n" , sig); |
8982 | dump_backtrace(); |
8983 | |
8984 | fprintf(stderr, "Writing a core file...\n" ); |
8985 | fflush(stderr); |
8986 | my_write_core(sig); |
8987 | #ifndef _WIN32 |
8988 | exit(1); // Shouldn't get here but just in case |
8989 | #endif |
8990 | } |
8991 | |
8992 | #ifdef _WIN32 |
8993 | |
8994 | LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) |
8995 | { |
8996 | __try |
8997 | { |
8998 | my_set_exception_pointers(exp); |
8999 | signal_handler(exp->ExceptionRecord->ExceptionCode); |
9000 | } |
9001 | __except(EXCEPTION_EXECUTE_HANDLER) |
9002 | { |
9003 | fputs("Got exception in exception handler!\n" , stderr); |
9004 | } |
9005 | |
9006 | return EXCEPTION_CONTINUE_SEARCH; |
9007 | } |
9008 | |
9009 | |
9010 | static void init_signal_handling(void) |
9011 | { |
9012 | UINT mode; |
9013 | |
9014 | /* Set output destination of messages to the standard error stream. */ |
9015 | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); |
9016 | _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); |
9017 | _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); |
9018 | _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); |
9019 | _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); |
9020 | _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); |
9021 | |
9022 | /* Do not not display the a error message box. */ |
9023 | mode= SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX; |
9024 | SetErrorMode(mode); |
9025 | |
9026 | SetUnhandledExceptionFilter(exception_filter); |
9027 | } |
9028 | |
9029 | #else /* _WIN32 */ |
9030 | |
9031 | static void init_signal_handling(void) |
9032 | { |
9033 | struct sigaction sa; |
9034 | DBUG_ENTER("init_signal_handling" ); |
9035 | |
9036 | #ifdef HAVE_STACKTRACE |
9037 | my_init_stacktrace(); |
9038 | #endif |
9039 | |
9040 | sa.sa_flags = SA_RESETHAND | SA_NODEFER; |
9041 | sigemptyset(&sa.sa_mask); |
9042 | sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); |
9043 | |
9044 | sa.sa_handler= signal_handler; |
9045 | |
9046 | sigaction(SIGSEGV, &sa, NULL); |
9047 | sigaction(SIGABRT, &sa, NULL); |
9048 | #ifdef SIGBUS |
9049 | sigaction(SIGBUS, &sa, NULL); |
9050 | #endif |
9051 | sigaction(SIGILL, &sa, NULL); |
9052 | sigaction(SIGFPE, &sa, NULL); |
9053 | DBUG_VOID_RETURN; |
9054 | } |
9055 | |
9056 | #endif /* !_WIN32 */ |
9057 | |
9058 | int main(int argc, char **argv) |
9059 | { |
9060 | struct st_command *command; |
9061 | my_bool q_send_flag= 0, abort_flag= 0; |
9062 | uint command_executed= 0, last_command_executed= 0; |
9063 | char save_file[FN_REFLEN]; |
9064 | bool empty_result= FALSE; |
9065 | MY_INIT(argv[0]); |
9066 | DBUG_ENTER("main" ); |
9067 | |
9068 | /* mysqltest has no way to free all its memory correctly */ |
9069 | sf_leaking_memory= 1; |
9070 | |
9071 | save_file[0]= 0; |
9072 | TMPDIR[0]= 0; |
9073 | |
9074 | init_signal_handling(); |
9075 | |
9076 | /* Init expected errors */ |
9077 | memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); |
9078 | |
9079 | #ifdef EMBEDDED_LIBRARY |
9080 | /* set appropriate stack for the 'query' threads */ |
9081 | (void) pthread_attr_init(&cn_thd_attrib); |
9082 | pthread_attr_setstacksize(&cn_thd_attrib, DEFAULT_THREAD_STACK); |
9083 | #endif /*EMBEDDED_LIBRARY*/ |
9084 | |
9085 | /* Init file stack */ |
9086 | memset(file_stack, 0, sizeof(file_stack)); |
9087 | file_stack_end= |
9088 | file_stack + (sizeof(file_stack)/sizeof(struct st_test_file)) - 1; |
9089 | cur_file= file_stack; |
9090 | |
9091 | /* Init block stack */ |
9092 | memset(block_stack, 0, sizeof(block_stack)); |
9093 | block_stack_end= |
9094 | block_stack + (sizeof(block_stack)/sizeof(struct st_block)) - 1; |
9095 | cur_block= block_stack; |
9096 | cur_block->ok= TRUE; /* Outer block should always be executed */ |
9097 | cur_block->cmd= cmd_none; |
9098 | |
9099 | my_init_dynamic_array(&q_lines, sizeof(struct st_command*), 1024, 1024, MYF(0)); |
9100 | |
9101 | if (my_hash_init2(&var_hash, 64, charset_info, |
9102 | 128, 0, 0, get_var_key, 0, var_free, MYF(0))) |
9103 | die("Variable hash initialization failed" ); |
9104 | |
9105 | { |
9106 | char path_separator[]= { FN_LIBCHAR, 0 }; |
9107 | var_set_string("SYSTEM_PATH_SEPARATOR" , path_separator); |
9108 | } |
9109 | var_set_string("MYSQL_SERVER_VERSION" , MYSQL_SERVER_VERSION); |
9110 | var_set_string("MYSQL_SYSTEM_TYPE" , SYSTEM_TYPE); |
9111 | var_set_string("MYSQL_MACHINE_TYPE" , MACHINE_TYPE); |
9112 | if (sizeof(void *) == 8) { |
9113 | var_set_string("MYSQL_SYSTEM_ARCHITECTURE" , "64" ); |
9114 | } else { |
9115 | var_set_string("MYSQL_SYSTEM_ARCHITECTURE" , "32" ); |
9116 | } |
9117 | |
9118 | memset(&master_pos, 0, sizeof(master_pos)); |
9119 | |
9120 | parser.current_line= parser.read_lines= 0; |
9121 | memset(&var_reg, 0, sizeof(var_reg)); |
9122 | |
9123 | init_builtin_echo(); |
9124 | #ifdef _WIN32 |
9125 | #ifndef USE_CYGWIN |
9126 | is_windows= 1; |
9127 | #endif |
9128 | init_tmp_sh_file(); |
9129 | init_win_path_patterns(); |
9130 | #endif |
9131 | |
9132 | init_dynamic_string(&ds_res, "" , 2048, 2048); |
9133 | init_alloc_root(&require_file_root, "require_file" , 1024, 1024, MYF(0)); |
9134 | |
9135 | parse_args(argc, argv); |
9136 | |
9137 | log_file.open(opt_logdir, result_file_name, ".log" ); |
9138 | verbose_msg("Logging to '%s'." , log_file.file_name()); |
9139 | if (opt_mark_progress) |
9140 | { |
9141 | progress_file.open(opt_logdir, result_file_name, ".progress" ); |
9142 | verbose_msg("Tracing progress in '%s'." , progress_file.file_name()); |
9143 | } |
9144 | |
9145 | /* Init connections, allocate 1 extra as buffer + 1 for default */ |
9146 | connections= (struct st_connection*) |
9147 | my_malloc((opt_max_connections+2) * sizeof(struct st_connection), |
9148 | MYF(MY_WME | MY_ZEROFILL)); |
9149 | connections_end= connections + opt_max_connections +1; |
9150 | next_con= connections + 1; |
9151 | |
9152 | var_set_int("$PS_PROTOCOL" , ps_protocol); |
9153 | var_set_int("$NON_BLOCKING_API" , non_blocking_api_enabled); |
9154 | var_set_int("$SP_PROTOCOL" , sp_protocol); |
9155 | var_set_int("$VIEW_PROTOCOL" , view_protocol); |
9156 | var_set_int("$CURSOR_PROTOCOL" , cursor_protocol); |
9157 | |
9158 | var_set_int("$ENABLED_QUERY_LOG" , 1); |
9159 | var_set_int("$ENABLED_ABORT_ON_ERROR" , 1); |
9160 | var_set_int("$ENABLED_RESULT_LOG" , 1); |
9161 | var_set_int("$ENABLED_CONNECT_LOG" , 0); |
9162 | var_set_int("$ENABLED_WARNINGS" , 1); |
9163 | var_set_int("$ENABLED_INFO" , 0); |
9164 | var_set_int("$ENABLED_METADATA" , 0); |
9165 | |
9166 | DBUG_PRINT("info" ,("result_file: '%s'" , |
9167 | result_file_name ? result_file_name : "" )); |
9168 | verbose_msg("Results saved in '%s'." , |
9169 | result_file_name ? result_file_name : "" ); |
9170 | if (mysql_server_init(embedded_server_arg_count, |
9171 | embedded_server_args, |
9172 | (char**) embedded_server_groups)) |
9173 | die("Can't initialize MySQL server" ); |
9174 | server_initialized= 1; |
9175 | if (cur_file == file_stack && cur_file->file == 0) |
9176 | { |
9177 | cur_file->file= stdin; |
9178 | cur_file->file_name= my_strdup("<stdin>" , MYF(MY_WME)); |
9179 | cur_file->lineno= 1; |
9180 | } |
9181 | var_set_string("MYSQLTEST_FILE" , cur_file->file_name); |
9182 | init_re(); |
9183 | |
9184 | /* Cursor protocol implies ps protocol */ |
9185 | if (cursor_protocol) |
9186 | ps_protocol= 1; |
9187 | |
9188 | ps_protocol_enabled= ps_protocol; |
9189 | sp_protocol_enabled= sp_protocol; |
9190 | view_protocol_enabled= view_protocol; |
9191 | cursor_protocol_enabled= cursor_protocol; |
9192 | |
9193 | st_connection *con= connections; |
9194 | init_connection_thd(con); |
9195 | if (! (con->mysql= mysql_init(0))) |
9196 | die("Failed in mysql_init()" ); |
9197 | if (opt_connect_timeout) |
9198 | mysql_options(con->mysql, MYSQL_OPT_CONNECT_TIMEOUT, |
9199 | (void *) &opt_connect_timeout); |
9200 | if (opt_compress) |
9201 | mysql_options(con->mysql,MYSQL_OPT_COMPRESS,NullS); |
9202 | mysql_options(con->mysql, MYSQL_OPT_LOCAL_INFILE, 0); |
9203 | mysql_options(con->mysql, MYSQL_SET_CHARSET_NAME, |
9204 | charset_info->csname); |
9205 | if (opt_charsets_dir) |
9206 | mysql_options(con->mysql, MYSQL_SET_CHARSET_DIR, |
9207 | opt_charsets_dir); |
9208 | |
9209 | if (opt_protocol) |
9210 | mysql_options(con->mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); |
9211 | |
9212 | if (opt_plugin_dir && *opt_plugin_dir) |
9213 | mysql_options(con->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); |
9214 | |
9215 | #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) |
9216 | |
9217 | if (opt_use_ssl) |
9218 | { |
9219 | mysql_ssl_set(con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, |
9220 | opt_ssl_capath, opt_ssl_cipher); |
9221 | mysql_options(con->mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); |
9222 | mysql_options(con->mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); |
9223 | #if MYSQL_VERSION_ID >= 50000 |
9224 | /* Turn on ssl_verify_server_cert only if host is "localhost" */ |
9225 | opt_ssl_verify_server_cert= opt_host && !strcmp(opt_host, "localhost" ); |
9226 | mysql_options(con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, |
9227 | &opt_ssl_verify_server_cert); |
9228 | #endif |
9229 | } |
9230 | #endif |
9231 | |
9232 | #ifdef HAVE_SMEM |
9233 | if (shared_memory_base_name) |
9234 | mysql_options(con->mysql,MYSQL_SHARED_MEMORY_BASE_NAME,shared_memory_base_name); |
9235 | #endif |
9236 | |
9237 | if (!(con->name = my_strdup("default" , MYF(MY_WME)))) |
9238 | die("Out of memory" ); |
9239 | mysql_options(con->mysql, MYSQL_OPT_NONBLOCK, 0); |
9240 | |
9241 | safe_connect(con->mysql, con->name, opt_host, opt_user, opt_pass, |
9242 | opt_db, opt_port, unix_sock); |
9243 | |
9244 | /* Use all time until exit if no explicit 'start_timer' */ |
9245 | timer_start= timer_now(); |
9246 | |
9247 | /* |
9248 | Initialize $mysql_errno with -1, so we can |
9249 | - distinguish it from valid values ( >= 0 ) and |
9250 | - detect if there was never a command sent to the server |
9251 | */ |
9252 | var_set_errno(-1); |
9253 | |
9254 | set_current_connection(con); |
9255 | |
9256 | if (opt_prologue) |
9257 | { |
9258 | open_file(opt_prologue); |
9259 | } |
9260 | |
9261 | verbose_msg("Start processing test commands from '%s' ..." , cur_file->file_name); |
9262 | while (!abort_flag && !read_command(&command)) |
9263 | { |
9264 | my_bool ok_to_do; |
9265 | int current_line_inc = 1, processed = 0; |
9266 | if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND) |
9267 | get_command_type(command); |
9268 | |
9269 | if (parsing_disabled && |
9270 | command->type != Q_ENABLE_PARSING && |
9271 | command->type != Q_DISABLE_PARSING) |
9272 | { |
9273 | /* Parsing is disabled, silently convert this line to a comment */ |
9274 | command->type= Q_COMMENT; |
9275 | } |
9276 | |
9277 | /* (Re-)set abort_on_error for this command */ |
9278 | command->abort_on_error= (command->expected_errors.count == 0 && |
9279 | abort_on_error); |
9280 | |
9281 | /* |
9282 | some commands need to be executed or at least parsed unconditionally, |
9283 | because they change the grammar. |
9284 | */ |
9285 | ok_to_do= cur_block->ok || command->type == Q_DELIMITER |
9286 | || command->type == Q_PERL; |
9287 | /* |
9288 | Some commands need to be "done" the first time if they may get |
9289 | re-iterated over in a true context. This can only happen if there's |
9290 | a while loop at some level above the current block. |
9291 | */ |
9292 | if (!ok_to_do) |
9293 | { |
9294 | if (command->type == Q_SOURCE || |
9295 | command->type == Q_ERROR || |
9296 | command->type == Q_WRITE_FILE || |
9297 | command->type == Q_APPEND_FILE) |
9298 | { |
9299 | for (struct st_block *stb= cur_block-1; stb >= block_stack; stb--) |
9300 | { |
9301 | if (stb->cmd == cmd_while) |
9302 | { |
9303 | ok_to_do= 1; |
9304 | break; |
9305 | } |
9306 | } |
9307 | } |
9308 | } |
9309 | |
9310 | if (ok_to_do) |
9311 | { |
9312 | command->last_argument= command->first_argument; |
9313 | processed = 1; |
9314 | /* Need to remember this for handle_error() */ |
9315 | curr_command= command; |
9316 | switch (command->type) { |
9317 | case Q_CONNECT: |
9318 | do_connect(command); |
9319 | break; |
9320 | case Q_CONNECTION: select_connection(command); break; |
9321 | case Q_DISCONNECT: |
9322 | case Q_DIRTY_CLOSE: |
9323 | do_close_connection(command); break; |
9324 | case Q_ENABLE_PREPARE_WARNINGS: prepare_warnings_enabled=1; break; |
9325 | case Q_DISABLE_PREPARE_WARNINGS: prepare_warnings_enabled=0; break; |
9326 | case Q_ENABLE_QUERY_LOG: |
9327 | set_property(command, P_QUERY, 0); |
9328 | break; |
9329 | case Q_DISABLE_QUERY_LOG: |
9330 | set_property(command, P_QUERY, 1); |
9331 | break; |
9332 | case Q_ENABLE_ABORT_ON_ERROR: |
9333 | set_property(command, P_ABORT, 1); |
9334 | break; |
9335 | case Q_DISABLE_ABORT_ON_ERROR: |
9336 | set_property(command, P_ABORT, 0); |
9337 | break; |
9338 | case Q_ENABLE_RESULT_LOG: |
9339 | set_property(command, P_RESULT, 0); |
9340 | break; |
9341 | case Q_DISABLE_RESULT_LOG: |
9342 | set_property(command, P_RESULT, 1); |
9343 | break; |
9344 | case Q_ENABLE_CONNECT_LOG: |
9345 | set_property(command, P_CONNECT, 0); |
9346 | break; |
9347 | case Q_DISABLE_CONNECT_LOG: |
9348 | set_property(command, P_CONNECT, 1); |
9349 | break; |
9350 | case Q_ENABLE_WARNINGS: |
9351 | set_property(command, P_WARN, 0); |
9352 | break; |
9353 | case Q_DISABLE_WARNINGS: |
9354 | set_property(command, P_WARN, 1); |
9355 | break; |
9356 | case Q_ENABLE_INFO: |
9357 | set_property(command, P_INFO, 0); |
9358 | break; |
9359 | case Q_DISABLE_INFO: |
9360 | set_property(command, P_INFO, 1); |
9361 | break; |
9362 | case Q_ENABLE_METADATA: |
9363 | set_property(command, P_META, 1); |
9364 | break; |
9365 | case Q_DISABLE_METADATA: |
9366 | set_property(command, P_META, 0); |
9367 | break; |
9368 | case Q_ENABLE_COLUMN_NAMES: |
9369 | disable_column_names= 0; |
9370 | var_set_int("$ENABLED_COLUMN_NAMES" , 0); |
9371 | break; |
9372 | case Q_DISABLE_COLUMN_NAMES: |
9373 | disable_column_names= 1; |
9374 | var_set_int("$ENABLED_COLUMN_NAMES" , 1); |
9375 | break; |
9376 | case Q_SOURCE: do_source(command); break; |
9377 | case Q_SLEEP: do_sleep(command, 0); break; |
9378 | case Q_REAL_SLEEP: do_sleep(command, 1); break; |
9379 | case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(command); break; |
9380 | case Q_INC: do_modify_var(command, DO_INC); break; |
9381 | case Q_DEC: do_modify_var(command, DO_DEC); break; |
9382 | case Q_ECHO: do_echo(command); command_executed++; break; |
9383 | case Q_SYSTEM: do_system(command); break; |
9384 | case Q_REMOVE_FILE: do_remove_file(command); break; |
9385 | case Q_REMOVE_FILES_WILDCARD: do_remove_files_wildcard(command); break; |
9386 | case Q_MKDIR: do_mkdir(command); break; |
9387 | case Q_RMDIR: do_rmdir(command); break; |
9388 | case Q_LIST_FILES: do_list_files(command); break; |
9389 | case Q_LIST_FILES_WRITE_FILE: |
9390 | do_list_files_write_file_command(command, FALSE); |
9391 | break; |
9392 | case Q_LIST_FILES_APPEND_FILE: |
9393 | do_list_files_write_file_command(command, TRUE); |
9394 | break; |
9395 | case Q_FILE_EXIST: do_file_exist(command); break; |
9396 | case Q_WRITE_FILE: do_write_file(command); break; |
9397 | case Q_APPEND_FILE: do_append_file(command); break; |
9398 | case Q_DIFF_FILES: do_diff_files(command); break; |
9399 | case Q_SEND_QUIT: do_send_quit(command); break; |
9400 | case Q_CHANGE_USER: do_change_user(command); break; |
9401 | case Q_CAT_FILE: do_cat_file(command); break; |
9402 | case Q_COPY_FILE: do_copy_file(command); break; |
9403 | case Q_MOVE_FILE: do_move_file(command); break; |
9404 | case Q_CHMOD_FILE: do_chmod_file(command); break; |
9405 | case Q_PERL: do_perl(command); break; |
9406 | case Q_RESULT_FORMAT_VERSION: do_result_format_version(command); break; |
9407 | case Q_DELIMITER: |
9408 | do_delimiter(command); |
9409 | break; |
9410 | case Q_DISPLAY_VERTICAL_RESULTS: |
9411 | display_result_vertically= TRUE; |
9412 | break; |
9413 | case Q_DISPLAY_HORIZONTAL_RESULTS: |
9414 | display_result_vertically= FALSE; |
9415 | break; |
9416 | case Q_SORTED_RESULT: |
9417 | /* |
9418 | Turn on sorting of result set, will be reset after next |
9419 | command |
9420 | */ |
9421 | display_result_sorted= TRUE; |
9422 | break; |
9423 | case Q_LOWERCASE: |
9424 | /* |
9425 | Turn on lowercasing of result, will be reset after next |
9426 | command |
9427 | */ |
9428 | display_result_lower= TRUE; |
9429 | break; |
9430 | case Q_LET: do_let(command); break; |
9431 | case Q_EVAL_RESULT: |
9432 | die("'eval_result' command is deprecated" ); |
9433 | case Q_EVAL: |
9434 | case Q_EVALP: |
9435 | case Q_QUERY_VERTICAL: |
9436 | case Q_QUERY_HORIZONTAL: |
9437 | if (command->query == command->query_buf) |
9438 | { |
9439 | /* Skip the first part of command, i.e query_xxx */ |
9440 | command->query= command->first_argument; |
9441 | command->first_word_len= 0; |
9442 | } |
9443 | /* fall through */ |
9444 | case Q_QUERY: |
9445 | case Q_REAP: |
9446 | { |
9447 | my_bool old_display_result_vertically= display_result_vertically; |
9448 | /* Default is full query, both reap and send */ |
9449 | int flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG; |
9450 | |
9451 | if (q_send_flag) |
9452 | { |
9453 | /* Last command was an empty 'send' */ |
9454 | flags= QUERY_SEND_FLAG; |
9455 | q_send_flag= 0; |
9456 | } |
9457 | else if (command->type == Q_REAP) |
9458 | { |
9459 | flags= QUERY_REAP_FLAG; |
9460 | } |
9461 | |
9462 | if (command->type == Q_EVALP) |
9463 | flags |= QUERY_PRINT_ORIGINAL_FLAG; |
9464 | |
9465 | /* Check for special property for this query */ |
9466 | display_result_vertically|= (command->type == Q_QUERY_VERTICAL); |
9467 | |
9468 | if (save_file[0]) |
9469 | { |
9470 | if (!(command->require_file= strdup_root(&require_file_root, |
9471 | save_file))) |
9472 | die("out of memory for require_file" ); |
9473 | save_file[0]= 0; |
9474 | } |
9475 | run_query(cur_con, command, flags); |
9476 | command_executed++; |
9477 | command->last_argument= command->end; |
9478 | |
9479 | /* Restore settings */ |
9480 | display_result_vertically= old_display_result_vertically; |
9481 | |
9482 | break; |
9483 | } |
9484 | case Q_SEND: |
9485 | case Q_SEND_EVAL: |
9486 | if (!*command->first_argument) |
9487 | { |
9488 | /* |
9489 | This is a send without arguments, it indicates that _next_ query |
9490 | should be send only |
9491 | */ |
9492 | q_send_flag= 1; |
9493 | break; |
9494 | } |
9495 | |
9496 | /* Remove "send" if this is first iteration */ |
9497 | if (command->query == command->query_buf) |
9498 | command->query= command->first_argument; |
9499 | |
9500 | /* |
9501 | run_query() can execute a query partially, depending on the flags. |
9502 | QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send |
9503 | the query and read the result some time later when reap instruction |
9504 | is given on this connection. |
9505 | */ |
9506 | run_query(cur_con, command, QUERY_SEND_FLAG); |
9507 | command_executed++; |
9508 | command->last_argument= command->end; |
9509 | break; |
9510 | case Q_REQUIRE: |
9511 | do_get_file_name(command, save_file, sizeof(save_file)); |
9512 | break; |
9513 | case Q_ERROR: |
9514 | do_get_errcodes(command); |
9515 | break; |
9516 | case Q_REPLACE: |
9517 | do_get_replace(command); |
9518 | break; |
9519 | case Q_REPLACE_REGEX: |
9520 | do_get_replace_regex(command); |
9521 | break; |
9522 | case Q_REPLACE_COLUMN: |
9523 | do_get_replace_column(command); |
9524 | break; |
9525 | case Q_SAVE_MASTER_POS: do_save_master_pos(); break; |
9526 | case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break; |
9527 | case Q_SYNC_SLAVE_WITH_MASTER: |
9528 | { |
9529 | do_save_master_pos(); |
9530 | if (*command->first_argument) |
9531 | select_connection(command); |
9532 | else |
9533 | select_connection_name("slave" ); |
9534 | do_sync_with_master2(command, 0, "" ); |
9535 | break; |
9536 | } |
9537 | case Q_COMMENT: |
9538 | { |
9539 | command->last_argument= command->end; |
9540 | |
9541 | /* Don't output comments in v1 */ |
9542 | if (opt_result_format_version == 1) |
9543 | break; |
9544 | |
9545 | /* Don't output comments if query logging is off */ |
9546 | if (disable_query_log) |
9547 | break; |
9548 | |
9549 | /* Write comment's with two starting #'s to result file */ |
9550 | const char* p= command->query; |
9551 | if (p && *p == '#' && *(p+1) == '#') |
9552 | { |
9553 | dynstr_append_mem(&ds_res, command->query, command->query_len); |
9554 | dynstr_append(&ds_res, "\n" ); |
9555 | } |
9556 | break; |
9557 | } |
9558 | case Q_EMPTY_LINE: |
9559 | /* Don't output newline in v1 */ |
9560 | if (opt_result_format_version == 1) |
9561 | break; |
9562 | |
9563 | /* Don't output newline if query logging is off */ |
9564 | if (disable_query_log) |
9565 | break; |
9566 | |
9567 | dynstr_append(&ds_res, "\n" ); |
9568 | break; |
9569 | case Q_PING: |
9570 | handle_command_error(command, mysql_ping(cur_con->mysql), -1); |
9571 | break; |
9572 | case Q_SEND_SHUTDOWN: |
9573 | handle_command_error(command, |
9574 | mysql_shutdown(cur_con->mysql, |
9575 | SHUTDOWN_DEFAULT), -1); |
9576 | break; |
9577 | case Q_SHUTDOWN_SERVER: |
9578 | do_shutdown_server(command); |
9579 | break; |
9580 | case Q_EXEC: |
9581 | do_exec(command); |
9582 | command_executed++; |
9583 | break; |
9584 | case Q_START_TIMER: |
9585 | /* Overwrite possible earlier start of timer */ |
9586 | timer_start= timer_now(); |
9587 | break; |
9588 | case Q_END_TIMER: |
9589 | /* End timer before ending mysqltest */ |
9590 | timer_output(); |
9591 | break; |
9592 | case Q_CHARACTER_SET: |
9593 | do_set_charset(command); |
9594 | break; |
9595 | case Q_DISABLE_PS_PROTOCOL: |
9596 | set_property(command, P_PS, 0); |
9597 | /* Close any open statements */ |
9598 | close_statements(); |
9599 | break; |
9600 | case Q_ENABLE_PS_PROTOCOL: |
9601 | set_property(command, P_PS, ps_protocol); |
9602 | break; |
9603 | case Q_DISABLE_NON_BLOCKING_API: |
9604 | non_blocking_api_enabled= 0; |
9605 | break; |
9606 | case Q_ENABLE_NON_BLOCKING_API: |
9607 | non_blocking_api_enabled= 1; |
9608 | break; |
9609 | case Q_DISABLE_RECONNECT: |
9610 | mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_false); |
9611 | break; |
9612 | case Q_ENABLE_RECONNECT: |
9613 | mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_true); |
9614 | /* Close any open statements - no reconnect, need new prepare */ |
9615 | close_statements(); |
9616 | break; |
9617 | case Q_DISABLE_PARSING: |
9618 | if (parsing_disabled == 0) |
9619 | parsing_disabled= 1; |
9620 | else |
9621 | report_or_die("Parsing is already disabled" ); |
9622 | break; |
9623 | case Q_ENABLE_PARSING: |
9624 | /* |
9625 | Ensure we don't get parsing_disabled < 0 as this would accidentally |
9626 | disable code we don't want to have disabled |
9627 | */ |
9628 | if (parsing_disabled == 1) |
9629 | parsing_disabled= 0; |
9630 | else |
9631 | report_or_die("Parsing is already enabled" ); |
9632 | break; |
9633 | case Q_DIE: |
9634 | /* Abort test with error code and error message */ |
9635 | die("%s" , command->first_argument); |
9636 | break; |
9637 | case Q_EXIT: |
9638 | /* Stop processing any more commands */ |
9639 | abort_flag= 1; |
9640 | break; |
9641 | case Q_SKIP: |
9642 | /* Eval the query, thus replacing all environment variables */ |
9643 | dynstr_set(&ds_res, 0); |
9644 | do_eval(&ds_res, command->first_argument, command->end, FALSE); |
9645 | abort_not_supported_test("%s" ,ds_res.str); |
9646 | break; |
9647 | case Q_RESULT: |
9648 | die("result, deprecated command" ); |
9649 | break; |
9650 | default: |
9651 | processed= 0; |
9652 | break; |
9653 | } |
9654 | } |
9655 | |
9656 | if (!processed) |
9657 | { |
9658 | current_line_inc= 0; |
9659 | switch (command->type) { |
9660 | case Q_WHILE: do_block(cmd_while, command); break; |
9661 | case Q_IF: do_block(cmd_if, command); break; |
9662 | case Q_END_BLOCK: do_done(command); break; |
9663 | default: current_line_inc = 1; break; |
9664 | } |
9665 | } |
9666 | else |
9667 | check_eol_junk(command->last_argument); |
9668 | |
9669 | if (command->type != Q_ERROR && |
9670 | command->type != Q_COMMENT) |
9671 | { |
9672 | /* |
9673 | As soon as any non "error" command or comment has been executed, |
9674 | the array with expected errors should be cleared |
9675 | */ |
9676 | memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); |
9677 | } |
9678 | |
9679 | if (command_executed != last_command_executed || command->used_replace) |
9680 | { |
9681 | /* |
9682 | As soon as any command has been executed, |
9683 | the replace structures should be cleared |
9684 | */ |
9685 | free_all_replace(); |
9686 | |
9687 | /* Also reset "sorted_result" and "lowercase"*/ |
9688 | display_result_sorted= FALSE; |
9689 | display_result_lower= FALSE; |
9690 | } |
9691 | last_command_executed= command_executed; |
9692 | |
9693 | parser.current_line += current_line_inc; |
9694 | if ( opt_mark_progress ) |
9695 | mark_progress(command, parser.current_line); |
9696 | |
9697 | /* Write result from command to log file immediately */ |
9698 | log_file.write(&ds_res); |
9699 | log_file.flush(); |
9700 | dynstr_set(&ds_res, 0); |
9701 | } |
9702 | |
9703 | log_file.close(); |
9704 | |
9705 | start_lineno= 0; |
9706 | verbose_msg("... Done processing test commands." ); |
9707 | |
9708 | if (parsing_disabled) |
9709 | die("Test ended with parsing disabled" ); |
9710 | |
9711 | /* |
9712 | The whole test has been executed _successfully_. |
9713 | Time to compare result or save it to record file. |
9714 | The entire output from test is in the log file |
9715 | */ |
9716 | if (log_file.bytes_written()) |
9717 | { |
9718 | if (result_file_name) |
9719 | { |
9720 | /* A result file has been specified */ |
9721 | |
9722 | if (record) |
9723 | { |
9724 | /* Recording */ |
9725 | |
9726 | /* save a copy of the log to result file */ |
9727 | if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0) |
9728 | die("Failed to copy '%s' to '%s', errno: %d" , |
9729 | log_file.file_name(), result_file_name, errno); |
9730 | |
9731 | } |
9732 | else |
9733 | { |
9734 | /* Check that the output from test is equal to result file */ |
9735 | check_result(); |
9736 | } |
9737 | } |
9738 | } |
9739 | else |
9740 | { |
9741 | /* Empty output is an error *unless* we also have an empty result file */ |
9742 | if (! result_file_name || record || |
9743 | compare_files (log_file.file_name(), result_file_name)) |
9744 | { |
9745 | die("The test didn't produce any output" ); |
9746 | } |
9747 | else |
9748 | { |
9749 | empty_result= TRUE; /* Meaning empty was expected */ |
9750 | } |
9751 | } |
9752 | |
9753 | if (!command_executed && result_file_name && !empty_result) |
9754 | die("No queries executed but non-empty result file found!" ); |
9755 | |
9756 | verbose_msg("Test has succeeded!" ); |
9757 | timer_output(); |
9758 | /* Yes, if we got this far the test has succeeded! Sakila smiles */ |
9759 | cleanup_and_exit(0); |
9760 | return 0; /* Keep compiler happy too */ |
9761 | } |
9762 | |
9763 | |
9764 | /* |
9765 | A primitive timer that give results in milliseconds if the |
9766 | --timer-file=<filename> is given. The timer result is written |
9767 | to that file when the result is available. To not confuse |
9768 | mysql-test-run with an old obsolete result, we remove the file |
9769 | before executing any commands. The time we measure is |
9770 | |
9771 | - If no explicit 'start_timer' or 'end_timer' is given in the |
9772 | test case, the timer measure how long we execute in mysqltest. |
9773 | |
9774 | - If only 'start_timer' is given we measure how long we execute |
9775 | from that point until we terminate mysqltest. |
9776 | |
9777 | - If only 'end_timer' is given we measure how long we execute |
9778 | from that we enter mysqltest to the 'end_timer' is command is |
9779 | executed. |
9780 | |
9781 | - If both 'start_timer' and 'end_timer' are given we measure |
9782 | the time between executing the two commands. |
9783 | */ |
9784 | |
9785 | void timer_output(void) |
9786 | { |
9787 | if (timer_file) |
9788 | { |
9789 | char buf[32], *end; |
9790 | ulonglong timer= timer_now() - timer_start; |
9791 | end= longlong10_to_str(timer, buf, 10); |
9792 | str_to_file(timer_file,buf, (int) (end-buf)); |
9793 | /* Timer has been written to the file, don't use it anymore */ |
9794 | timer_file= 0; |
9795 | } |
9796 | } |
9797 | |
9798 | |
9799 | ulonglong timer_now(void) |
9800 | { |
9801 | return my_interval_timer() / 1000000; |
9802 | } |
9803 | |
9804 | |
9805 | /* |
9806 | Get arguments for replace_columns. The syntax is: |
9807 | replace-column column_number to_string [column_number to_string ...] |
9808 | Where each argument may be quoted with ' or " |
9809 | A argument may also be a variable, in which case the value of the |
9810 | variable is replaced. |
9811 | */ |
9812 | |
9813 | void do_get_replace_column(struct st_command *command) |
9814 | { |
9815 | char *from= command->first_argument; |
9816 | char *buff, *start; |
9817 | DBUG_ENTER("get_replace_columns" ); |
9818 | |
9819 | free_replace_column(); |
9820 | if (!*from) |
9821 | die("Missing argument in %s" , command->query); |
9822 | |
9823 | /* Allocate a buffer for results */ |
9824 | start= buff= (char*)my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); |
9825 | while (*from) |
9826 | { |
9827 | char *to; |
9828 | uint column_number; |
9829 | to= get_string(&buff, &from, command); |
9830 | if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS) |
9831 | die("Wrong column number to replace_column in '%s'" , command->query); |
9832 | if (!*from) |
9833 | die("Wrong number of arguments to replace_column in '%s'" , |
9834 | command->query); |
9835 | to= get_string(&buff, &from, command); |
9836 | my_free(replace_column[column_number-1]); |
9837 | replace_column[column_number-1]= my_strdup(to, MYF(MY_WME | MY_FAE)); |
9838 | set_if_bigger(max_replace_column, column_number); |
9839 | } |
9840 | my_free(start); |
9841 | command->last_argument= command->end; |
9842 | |
9843 | DBUG_VOID_RETURN; |
9844 | } |
9845 | |
9846 | |
9847 | void free_replace_column() |
9848 | { |
9849 | uint i; |
9850 | for (i=0 ; i < max_replace_column ; i++) |
9851 | { |
9852 | if (replace_column[i]) |
9853 | { |
9854 | my_free(replace_column[i]); |
9855 | replace_column[i]= 0; |
9856 | } |
9857 | } |
9858 | max_replace_column= 0; |
9859 | } |
9860 | |
9861 | |
9862 | /****************************************************************************/ |
9863 | /* |
9864 | Replace functions |
9865 | */ |
9866 | |
9867 | /* Definitions for replace result */ |
9868 | |
9869 | typedef struct st_pointer_array { /* when using array-strings */ |
9870 | TYPELIB typelib; /* Pointer to strings */ |
9871 | uchar *str; /* Strings is here */ |
9872 | uint8 *flag; /* Flag about each var. */ |
9873 | uint array_allocs,max_count,length,max_length; |
9874 | } POINTER_ARRAY; |
9875 | |
9876 | struct st_replace *init_replace(char * *from, char * *to, uint count, |
9877 | char * word_end_chars); |
9878 | int insert_pointer_name(POINTER_ARRAY *pa,char * name); |
9879 | void free_pointer_array(POINTER_ARRAY *pa); |
9880 | |
9881 | /* |
9882 | Get arguments for replace. The syntax is: |
9883 | replace from to [from to ...] |
9884 | Where each argument may be quoted with ' or " |
9885 | A argument may also be a variable, in which case the value of the |
9886 | variable is replaced. |
9887 | */ |
9888 | |
9889 | void do_get_replace(struct st_command *command) |
9890 | { |
9891 | uint i; |
9892 | char *from= command->first_argument; |
9893 | char *buff, *start; |
9894 | char word_end_chars[256], *pos; |
9895 | POINTER_ARRAY to_array, from_array; |
9896 | DBUG_ENTER("get_replace" ); |
9897 | |
9898 | free_replace(); |
9899 | |
9900 | bzero(&to_array,sizeof(to_array)); |
9901 | bzero(&from_array,sizeof(from_array)); |
9902 | if (!*from) |
9903 | die("Missing argument in %s" , command->query); |
9904 | start= buff= (char*)my_malloc(strlen(from)+1,MYF(MY_WME | MY_FAE)); |
9905 | while (*from) |
9906 | { |
9907 | char *to= buff; |
9908 | to= get_string(&buff, &from, command); |
9909 | if (!*from) |
9910 | die("Wrong number of arguments to replace_result in '%s'" , |
9911 | command->query); |
9912 | fix_win_paths(to, from - to); |
9913 | insert_pointer_name(&from_array,to); |
9914 | to= get_string(&buff, &from, command); |
9915 | insert_pointer_name(&to_array,to); |
9916 | } |
9917 | for (i= 1,pos= word_end_chars ; i < 256 ; i++) |
9918 | if (my_isspace(charset_info,i)) |
9919 | *pos++= i; |
9920 | *pos=0; /* End pointer */ |
9921 | if (!(glob_replace= init_replace((char**) from_array.typelib.type_names, |
9922 | (char**) to_array.typelib.type_names, |
9923 | (uint) from_array.typelib.count, |
9924 | word_end_chars))) |
9925 | die("Can't initialize replace from '%s'" , command->query); |
9926 | free_pointer_array(&from_array); |
9927 | free_pointer_array(&to_array); |
9928 | my_free(start); |
9929 | command->last_argument= command->end; |
9930 | DBUG_VOID_RETURN; |
9931 | } |
9932 | |
9933 | |
9934 | void free_replace() |
9935 | { |
9936 | DBUG_ENTER("free_replace" ); |
9937 | my_free(glob_replace); |
9938 | glob_replace= NULL; |
9939 | DBUG_VOID_RETURN; |
9940 | } |
9941 | |
9942 | |
9943 | typedef struct st_replace { |
9944 | int found; |
9945 | struct st_replace *next[256]; |
9946 | } REPLACE; |
9947 | |
9948 | typedef struct st_replace_found { |
9949 | int found; |
9950 | uint to_offset; |
9951 | int from_offset; |
9952 | char *replace_string; |
9953 | } REPLACE_STRING; |
9954 | |
9955 | |
9956 | void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, |
9957 | const char *str) |
9958 | { |
9959 | REPLACE *rep_pos; |
9960 | REPLACE_STRING *rep_str; |
9961 | const char *start, *from; |
9962 | DBUG_ENTER("replace_strings_append" ); |
9963 | |
9964 | start= from= str; |
9965 | rep_pos=rep+1; |
9966 | for (;;) |
9967 | { |
9968 | /* Loop through states */ |
9969 | DBUG_PRINT("info" , ("Looping through states" )); |
9970 | while (!rep_pos->found) |
9971 | rep_pos= rep_pos->next[(uchar) *from++]; |
9972 | |
9973 | /* Does this state contain a string to be replaced */ |
9974 | if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string) |
9975 | { |
9976 | /* No match found */ |
9977 | dynstr_append_mem(ds, start, from - start - 1); |
9978 | DBUG_PRINT("exit" , ("Found no more string to replace, appended: %s" , start)); |
9979 | DBUG_VOID_RETURN; |
9980 | } |
9981 | |
9982 | /* Found a string that needs to be replaced */ |
9983 | DBUG_PRINT("info" , ("found: %d, to_offset: %u, from_offset: %d, string: %s" , |
9984 | rep_str->found, rep_str->to_offset, |
9985 | rep_str->from_offset, rep_str->replace_string)); |
9986 | |
9987 | /* Append part of original string before replace string */ |
9988 | dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start); |
9989 | |
9990 | /* Append replace string */ |
9991 | dynstr_append_mem(ds, rep_str->replace_string, |
9992 | strlen(rep_str->replace_string)); |
9993 | |
9994 | if (!*(from-=rep_str->from_offset) && rep_pos->found != 2) |
9995 | { |
9996 | /* End of from string */ |
9997 | DBUG_PRINT("exit" , ("Found end of from string" )); |
9998 | DBUG_VOID_RETURN; |
9999 | } |
10000 | start= from; |
10001 | rep_pos=rep; |
10002 | } |
10003 | } |
10004 | |
10005 | |
10006 | /* |
10007 | Regex replace functions |
10008 | */ |
10009 | |
10010 | |
10011 | /* Stores regex substitutions */ |
10012 | |
10013 | struct st_regex |
10014 | { |
10015 | char* pattern; /* Pattern to be replaced */ |
10016 | char* replace; /* String or expression to replace the pattern with */ |
10017 | int icase; /* true if the match is case insensitive */ |
10018 | }; |
10019 | |
10020 | int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, |
10021 | char *string, int icase); |
10022 | |
10023 | bool parse_re_part(char *start_re, char *end_re, |
10024 | char **p, char *end, char **buf) |
10025 | { |
10026 | if (*start_re != *end_re) |
10027 | { |
10028 | switch ((*start_re= *(*p)++)) { |
10029 | case '(': *end_re= ')'; break; |
10030 | case '[': *end_re= ']'; break; |
10031 | case '{': *end_re= '}'; break; |
10032 | case '<': *end_re= '>'; break; |
10033 | default: *end_re= *start_re; |
10034 | } |
10035 | } |
10036 | |
10037 | while (*p < end && **p != *end_re) |
10038 | { |
10039 | if ((*p)[0] == '\\' && *p + 1 < end && (*p)[1] == *end_re) |
10040 | (*p)++; |
10041 | |
10042 | *(*buf)++= *(*p)++; |
10043 | } |
10044 | *(*buf)++= 0; |
10045 | |
10046 | (*p)++; |
10047 | |
10048 | return *p > end; |
10049 | } |
10050 | |
10051 | /* |
10052 | Initializes the regular substitution expression to be used in the |
10053 | result output of test. |
10054 | |
10055 | Returns: st_replace_regex struct with pairs of substitutions |
10056 | */ |
10057 | void append_replace_regex(char*, char*, struct st_replace_regex*, char**); |
10058 | |
10059 | struct st_replace_regex* init_replace_regex(char* expr) |
10060 | { |
10061 | char *expr_end, *buf_p; |
10062 | struct st_replace_regex* res; |
10063 | size_t expr_len= strlen(expr); |
10064 | |
10065 | /* my_malloc() will die on fail with MY_FAE */ |
10066 | res=(struct st_replace_regex*)my_malloc( |
10067 | sizeof(*res)+8192 ,MYF(MY_FAE+MY_WME)); |
10068 | my_init_dynamic_array(&res->regex_arr,sizeof(struct st_regex), 128, 128, MYF(0)); |
10069 | |
10070 | expr_end= expr + expr_len; |
10071 | buf_p= (char*)res + sizeof(*res); |
10072 | append_replace_regex(expr, expr_end, res, &buf_p); |
10073 | |
10074 | res->odd_buf_len= res->even_buf_len= 8192; |
10075 | res->even_buf= (char*)my_malloc(res->even_buf_len,MYF(MY_WME+MY_FAE)); |
10076 | res->odd_buf= (char*)my_malloc(res->odd_buf_len,MYF(MY_WME+MY_FAE)); |
10077 | res->buf= res->even_buf; |
10078 | |
10079 | return res; |
10080 | } |
10081 | |
10082 | |
10083 | void append_replace_regex(char* expr, char *expr_end, struct st_replace_regex* res, |
10084 | char **buf_p) |
10085 | { |
10086 | char* p, start_re, end_re= 1; |
10087 | struct st_regex reg; |
10088 | |
10089 | p= expr; |
10090 | |
10091 | /* for each regexp substitution statement */ |
10092 | while (p < expr_end) |
10093 | { |
10094 | bzero(®,sizeof(reg)); |
10095 | /* find the start of the statement */ |
10096 | while (my_isspace(charset_info, *p) && p < expr_end) |
10097 | p++; |
10098 | |
10099 | if (p >= expr_end) |
10100 | { |
10101 | if (res->regex_arr.elements) |
10102 | break; |
10103 | else |
10104 | goto err; |
10105 | } |
10106 | |
10107 | start_re= 0; |
10108 | reg.pattern= *buf_p; |
10109 | |
10110 | /* Allow variable for the *entire* list of replacements */ |
10111 | if (*p == '$') |
10112 | { |
10113 | const char *v_end; |
10114 | VAR *val= var_get(p, &v_end, 0, 1); |
10115 | |
10116 | if (val) |
10117 | { |
10118 | char *expr, *expr_end; |
10119 | expr= val->str_val; |
10120 | expr_end= expr + val->str_val_len; |
10121 | append_replace_regex(expr, expr_end, res, buf_p); |
10122 | } |
10123 | |
10124 | p= (char *) v_end + 1; |
10125 | continue; |
10126 | } |
10127 | else |
10128 | { |
10129 | if (parse_re_part(&start_re, &end_re, &p, expr_end, buf_p)) |
10130 | goto err; |
10131 | |
10132 | reg.replace= *buf_p; |
10133 | if (parse_re_part(&start_re, &end_re, &p, expr_end, buf_p)) |
10134 | goto err; |
10135 | } |
10136 | |
10137 | /* Check if we should do matching case insensitive */ |
10138 | if (p < expr_end && *p == 'i') |
10139 | { |
10140 | p++; |
10141 | reg.icase= 1; |
10142 | } |
10143 | |
10144 | /* done parsing the statement, now place it in regex_arr */ |
10145 | if (insert_dynamic(&res->regex_arr, ®)) |
10146 | die("Out of memory" ); |
10147 | } |
10148 | |
10149 | return; |
10150 | |
10151 | err: |
10152 | my_free(res); |
10153 | die("Error parsing replace_regex \"%s\"" , expr); |
10154 | } |
10155 | |
10156 | /* |
10157 | Execute all substitutions on val. |
10158 | |
10159 | Returns: true if substituition was made, false otherwise |
10160 | Side-effect: Sets r->buf to be the buffer with all substitutions done. |
10161 | |
10162 | IN: |
10163 | struct st_replace_regex* r |
10164 | char* val |
10165 | Out: |
10166 | struct st_replace_regex* r |
10167 | r->buf points at the resulting buffer |
10168 | r->even_buf and r->odd_buf might have been reallocated |
10169 | r->even_buf_len and r->odd_buf_len might have been changed |
10170 | |
10171 | TODO: at some point figure out if there is a way to do everything |
10172 | in one pass |
10173 | */ |
10174 | |
10175 | int multi_reg_replace(struct st_replace_regex* r,char* val) |
10176 | { |
10177 | uint i; |
10178 | char* in_buf, *out_buf; |
10179 | int* buf_len_p; |
10180 | |
10181 | in_buf= val; |
10182 | out_buf= r->even_buf; |
10183 | buf_len_p= &r->even_buf_len; |
10184 | r->buf= 0; |
10185 | |
10186 | /* For each substitution, do the replace */ |
10187 | for (i= 0; i < r->regex_arr.elements; i++) |
10188 | { |
10189 | struct st_regex re; |
10190 | char* save_out_buf= out_buf; |
10191 | |
10192 | get_dynamic(&r->regex_arr, &re, i); |
10193 | |
10194 | if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace, |
10195 | in_buf, re.icase)) |
10196 | { |
10197 | /* if the buffer has been reallocated, make adjustements */ |
10198 | if (save_out_buf != out_buf) |
10199 | { |
10200 | if (save_out_buf == r->even_buf) |
10201 | r->even_buf= out_buf; |
10202 | else |
10203 | r->odd_buf= out_buf; |
10204 | } |
10205 | |
10206 | r->buf= out_buf; |
10207 | if (in_buf == val) |
10208 | in_buf= r->odd_buf; |
10209 | |
10210 | swap_variables(char*,in_buf,out_buf); |
10211 | |
10212 | buf_len_p= (out_buf == r->even_buf) ? &r->even_buf_len : |
10213 | &r->odd_buf_len; |
10214 | } |
10215 | } |
10216 | |
10217 | return (r->buf == 0); |
10218 | } |
10219 | |
10220 | /* |
10221 | Parse the regular expression to be used in all result files |
10222 | from now on. |
10223 | |
10224 | The syntax is --replace_regex /from/to/i /from/to/i ... |
10225 | i means case-insensitive match. If omitted, the match is |
10226 | case-sensitive |
10227 | |
10228 | */ |
10229 | void do_get_replace_regex(struct st_command *command) |
10230 | { |
10231 | char *expr= command->first_argument; |
10232 | free_replace_regex(); |
10233 | if (expr && *expr && !(glob_replace_regex=init_replace_regex(expr))) |
10234 | die("Could not init replace_regex" ); |
10235 | command->last_argument= command->end; |
10236 | } |
10237 | |
10238 | void free_replace_regex() |
10239 | { |
10240 | if (glob_replace_regex) |
10241 | { |
10242 | delete_dynamic(&glob_replace_regex->regex_arr); |
10243 | my_free(glob_replace_regex->even_buf); |
10244 | my_free(glob_replace_regex->odd_buf); |
10245 | my_free(glob_replace_regex); |
10246 | glob_replace_regex=0; |
10247 | } |
10248 | } |
10249 | |
10250 | |
10251 | |
10252 | /* |
10253 | auxiluary macro used by reg_replace |
10254 | makes sure the result buffer has sufficient length |
10255 | */ |
10256 | #define SECURE_REG_BUF if (buf_len < need_buf_len) \ |
10257 | { \ |
10258 | ssize_t off= res_p - buf; \ |
10259 | buf= (char*)my_realloc(buf,need_buf_len,MYF(MY_WME+MY_FAE)); \ |
10260 | res_p= buf + off; \ |
10261 | buf_len= need_buf_len; \ |
10262 | } \ |
10263 | \ |
10264 | /* |
10265 | Performs a regex substitution |
10266 | |
10267 | IN: |
10268 | |
10269 | buf_p - result buffer pointer. Will change if reallocated |
10270 | buf_len_p - result buffer length. Will change if the buffer is reallocated |
10271 | pattern - regexp pattern to match |
10272 | replace - replacement expression |
10273 | string - the string to perform substitutions in |
10274 | icase - flag, if set to 1 the match is case insensitive |
10275 | */ |
10276 | int reg_replace(char** buf_p, int* buf_len_p, char *pattern, |
10277 | char *replace, char *string, int icase) |
10278 | { |
10279 | regex_t r; |
10280 | regmatch_t *subs; |
10281 | char *replace_end; |
10282 | char *buf= *buf_p; |
10283 | size_t len; |
10284 | size_t buf_len, need_buf_len; |
10285 | int cflags= REG_EXTENDED | REG_DOTALL; |
10286 | int err_code; |
10287 | char *res_p,*str_p,*str_end; |
10288 | |
10289 | DBUG_ASSERT(*buf_len_p > 0); |
10290 | |
10291 | buf_len= (size_t)*buf_len_p; |
10292 | len= strlen(string); |
10293 | str_end= string + len; |
10294 | |
10295 | /* start with a buffer of a reasonable size that hopefully will not |
10296 | need to be reallocated |
10297 | */ |
10298 | need_buf_len= len * 2 + 1; |
10299 | res_p= buf; |
10300 | |
10301 | SECURE_REG_BUF |
10302 | |
10303 | if (icase) |
10304 | cflags|= REG_ICASE; |
10305 | |
10306 | if ((err_code= regcomp(&r,pattern,cflags))) |
10307 | { |
10308 | check_regerr(&r,err_code); |
10309 | return 1; |
10310 | } |
10311 | |
10312 | subs= (regmatch_t*)my_malloc(sizeof(regmatch_t) * (r.re_nsub+1), |
10313 | MYF(MY_WME+MY_FAE)); |
10314 | |
10315 | *res_p= 0; |
10316 | str_p= string; |
10317 | replace_end= replace + strlen(replace); |
10318 | |
10319 | /* for each pattern match instance perform a replacement */ |
10320 | while (!err_code) |
10321 | { |
10322 | /* find the match */ |
10323 | err_code= regexec(&r,str_p, r.re_nsub+1, subs, |
10324 | (str_p == string) ? 0 : REG_NOTBOL); |
10325 | |
10326 | /* if regular expression error (eg. bad syntax, or out of memory) */ |
10327 | if (err_code && err_code != REG_NOMATCH) |
10328 | { |
10329 | check_regerr(&r,err_code); |
10330 | regfree(&r); |
10331 | return 1; |
10332 | } |
10333 | |
10334 | /* if match found */ |
10335 | if (!err_code) |
10336 | { |
10337 | char* expr_p= replace; |
10338 | int c; |
10339 | |
10340 | /* |
10341 | we need at least what we have so far in the buffer + the part |
10342 | before this match |
10343 | */ |
10344 | need_buf_len= (res_p - buf) + (int) subs[0].rm_so; |
10345 | |
10346 | /* on this pass, calculate the memory for the result buffer */ |
10347 | while (expr_p < replace_end) |
10348 | { |
10349 | int back_ref_num= -1; |
10350 | c= *expr_p; |
10351 | |
10352 | if (c == '\\' && expr_p + 1 < replace_end) |
10353 | { |
10354 | back_ref_num= (int) (expr_p[1] - '0'); |
10355 | } |
10356 | |
10357 | /* found a valid back_ref (eg. \1)*/ |
10358 | if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) |
10359 | { |
10360 | regoff_t start_off, end_off; |
10361 | if ((start_off=subs[back_ref_num].rm_so) > -1 && |
10362 | (end_off=subs[back_ref_num].rm_eo) > -1) |
10363 | { |
10364 | need_buf_len += (int) (end_off - start_off); |
10365 | } |
10366 | expr_p += 2; |
10367 | } |
10368 | else |
10369 | { |
10370 | expr_p++; |
10371 | need_buf_len++; |
10372 | } |
10373 | } |
10374 | need_buf_len++; |
10375 | /* |
10376 | now that we know the size of the buffer, |
10377 | make sure it is big enough |
10378 | */ |
10379 | SECURE_REG_BUF |
10380 | |
10381 | /* copy the pre-match part */ |
10382 | if (subs[0].rm_so) |
10383 | { |
10384 | memcpy(res_p, str_p, (size_t) subs[0].rm_so); |
10385 | res_p+= subs[0].rm_so; |
10386 | } |
10387 | |
10388 | expr_p= replace; |
10389 | |
10390 | /* copy the match and expand back_refs */ |
10391 | while (expr_p < replace_end) |
10392 | { |
10393 | int back_ref_num= -1; |
10394 | c= *expr_p; |
10395 | |
10396 | if (c == '\\' && expr_p + 1 < replace_end) |
10397 | { |
10398 | back_ref_num= expr_p[1] - '0'; |
10399 | } |
10400 | |
10401 | if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) |
10402 | { |
10403 | regoff_t start_off, end_off; |
10404 | if ((start_off=subs[back_ref_num].rm_so) > -1 && |
10405 | (end_off=subs[back_ref_num].rm_eo) > -1) |
10406 | { |
10407 | int block_len= (int) (end_off - start_off); |
10408 | memcpy(res_p,str_p + start_off, block_len); |
10409 | res_p += block_len; |
10410 | } |
10411 | expr_p += 2; |
10412 | } |
10413 | else |
10414 | { |
10415 | *res_p++ = *expr_p++; |
10416 | } |
10417 | } |
10418 | |
10419 | /* handle the post-match part */ |
10420 | if (subs[0].rm_so == subs[0].rm_eo) |
10421 | { |
10422 | if (str_p + subs[0].rm_so >= str_end) |
10423 | break; |
10424 | str_p += subs[0].rm_eo ; |
10425 | *res_p++ = *str_p++; |
10426 | } |
10427 | else |
10428 | { |
10429 | str_p += subs[0].rm_eo; |
10430 | } |
10431 | } |
10432 | else /* no match this time, just copy the string as is */ |
10433 | { |
10434 | size_t left_in_str= str_end-str_p; |
10435 | need_buf_len= (res_p-buf) + left_in_str; |
10436 | SECURE_REG_BUF |
10437 | memcpy(res_p,str_p,left_in_str); |
10438 | res_p += left_in_str; |
10439 | str_p= str_end; |
10440 | } |
10441 | } |
10442 | my_free(subs); |
10443 | regfree(&r); |
10444 | *res_p= 0; |
10445 | *buf_p= buf; |
10446 | *buf_len_p= (int)buf_len; |
10447 | return 0; |
10448 | } |
10449 | |
10450 | |
10451 | #ifndef WORD_BIT |
10452 | #define WORD_BIT (8*sizeof(uint)) |
10453 | #endif |
10454 | |
10455 | #define SET_MALLOC_HUNC 64 |
10456 | #define LAST_CHAR_CODE 259 |
10457 | |
10458 | typedef struct st_rep_set { |
10459 | uint *bits; /* Pointer to used sets */ |
10460 | short next[LAST_CHAR_CODE]; /* Pointer to next sets */ |
10461 | uint found_len; /* Best match to date */ |
10462 | int found_offset; |
10463 | uint table_offset; |
10464 | uint size_of_bits; /* For convinience */ |
10465 | } REP_SET; |
10466 | |
10467 | typedef struct st_rep_sets { |
10468 | uint count; /* Number of sets */ |
10469 | uint ; /* Extra sets in buffer */ |
10470 | uint invisible; /* Sets not chown */ |
10471 | uint size_of_bits; |
10472 | REP_SET *set,*set_buffer; |
10473 | uint *bit_buffer; |
10474 | } REP_SETS; |
10475 | |
10476 | typedef struct st_found_set { |
10477 | uint table_offset; |
10478 | int found_offset; |
10479 | } FOUND_SET; |
10480 | |
10481 | typedef struct st_follow { |
10482 | int chr; |
10483 | uint table_offset; |
10484 | uint len; |
10485 | } FOLLOWS; |
10486 | |
10487 | |
10488 | int init_sets(REP_SETS *sets,uint states); |
10489 | REP_SET *make_new_set(REP_SETS *sets); |
10490 | void make_sets_invisible(REP_SETS *sets); |
10491 | void free_last_set(REP_SETS *sets); |
10492 | void free_sets(REP_SETS *sets); |
10493 | void internal_set_bit(REP_SET *set, uint bit); |
10494 | void internal_clear_bit(REP_SET *set, uint bit); |
10495 | void or_bits(REP_SET *to,REP_SET *from); |
10496 | void copy_bits(REP_SET *to,REP_SET *from); |
10497 | int cmp_bits(REP_SET *set1,REP_SET *set2); |
10498 | int get_next_bit(REP_SET *set,uint lastpos); |
10499 | int find_set(REP_SETS *sets,REP_SET *find); |
10500 | int find_found(FOUND_SET *found_set,uint table_offset, |
10501 | int found_offset); |
10502 | uint start_at_word(char * pos); |
10503 | uint end_of_word(char * pos); |
10504 | |
10505 | static uint found_sets=0; |
10506 | |
10507 | |
10508 | uint replace_len(char * str) |
10509 | { |
10510 | uint len=0; |
10511 | while (*str) |
10512 | { |
10513 | str++; |
10514 | len++; |
10515 | } |
10516 | return len; |
10517 | } |
10518 | |
10519 | /* Init a replace structure for further calls */ |
10520 | |
10521 | REPLACE *init_replace(char * *from, char * *to,uint count, |
10522 | char * word_end_chars) |
10523 | { |
10524 | static const int SPACE_CHAR= 256; |
10525 | static const int END_OF_LINE= 258; |
10526 | |
10527 | uint i,j,states,set_nr,len,result_len,max_length,found_end,bits_set,bit_nr; |
10528 | int used_sets,chr,default_state; |
10529 | char used_chars[LAST_CHAR_CODE],is_word_end[256]; |
10530 | char * pos, *to_pos, **to_array; |
10531 | REP_SETS sets; |
10532 | REP_SET *set,*start_states,*word_states,*new_set; |
10533 | FOLLOWS *follow,*follow_ptr; |
10534 | REPLACE *replace; |
10535 | FOUND_SET *found_set; |
10536 | REPLACE_STRING *rep_str; |
10537 | DBUG_ENTER("init_replace" ); |
10538 | |
10539 | /* Count number of states */ |
10540 | for (i=result_len=max_length=0 , states=2 ; i < count ; i++) |
10541 | { |
10542 | len=replace_len(from[i]); |
10543 | if (!len) |
10544 | { |
10545 | errno=EINVAL; |
10546 | DBUG_RETURN(0); |
10547 | } |
10548 | states+=len+1; |
10549 | result_len+=(uint) strlen(to[i])+1; |
10550 | if (len > max_length) |
10551 | max_length=len; |
10552 | } |
10553 | bzero(is_word_end, sizeof(is_word_end)); |
10554 | for (i=0 ; word_end_chars[i] ; i++) |
10555 | is_word_end[(uchar) word_end_chars[i]]=1; |
10556 | |
10557 | if (init_sets(&sets,states)) |
10558 | DBUG_RETURN(0); |
10559 | found_sets=0; |
10560 | if (!(found_set= (FOUND_SET*) my_malloc(sizeof(FOUND_SET)*max_length*count, |
10561 | MYF(MY_WME)))) |
10562 | { |
10563 | free_sets(&sets); |
10564 | DBUG_RETURN(0); |
10565 | } |
10566 | (void) make_new_set(&sets); /* Set starting set */ |
10567 | make_sets_invisible(&sets); /* Hide previus sets */ |
10568 | used_sets=-1; |
10569 | word_states=make_new_set(&sets); /* Start of new word */ |
10570 | start_states=make_new_set(&sets); /* This is first state */ |
10571 | if (!(follow=(FOLLOWS*) my_malloc((states+2)*sizeof(FOLLOWS),MYF(MY_WME)))) |
10572 | { |
10573 | free_sets(&sets); |
10574 | my_free(found_set); |
10575 | DBUG_RETURN(0); |
10576 | } |
10577 | |
10578 | /* Init follow_ptr[] */ |
10579 | for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++) |
10580 | { |
10581 | if (from[i][0] == '\\' && from[i][1] == '^') |
10582 | { |
10583 | internal_set_bit(start_states,states+1); |
10584 | if (!from[i][2]) |
10585 | { |
10586 | start_states->table_offset=i; |
10587 | start_states->found_offset=1; |
10588 | } |
10589 | } |
10590 | else if (from[i][0] == '\\' && from[i][1] == '$') |
10591 | { |
10592 | internal_set_bit(start_states,states); |
10593 | internal_set_bit(word_states,states); |
10594 | if (!from[i][2] && start_states->table_offset == (uint) ~0) |
10595 | { |
10596 | start_states->table_offset=i; |
10597 | start_states->found_offset=0; |
10598 | } |
10599 | } |
10600 | else |
10601 | { |
10602 | internal_set_bit(word_states,states); |
10603 | if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2])) |
10604 | internal_set_bit(start_states,states+1); |
10605 | else |
10606 | internal_set_bit(start_states,states); |
10607 | } |
10608 | for (pos=from[i], len=0; *pos ; pos++) |
10609 | { |
10610 | follow_ptr->chr= (uchar) *pos; |
10611 | follow_ptr->table_offset=i; |
10612 | follow_ptr->len= ++len; |
10613 | follow_ptr++; |
10614 | } |
10615 | follow_ptr->chr=0; |
10616 | follow_ptr->table_offset=i; |
10617 | follow_ptr->len=len; |
10618 | follow_ptr++; |
10619 | states+=(uint) len+1; |
10620 | } |
10621 | |
10622 | |
10623 | for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++) |
10624 | { |
10625 | set=sets.set+set_nr; |
10626 | default_state= 0; /* Start from beginning */ |
10627 | |
10628 | /* If end of found-string not found or start-set with current set */ |
10629 | |
10630 | for (i= (uint) ~0; (i=get_next_bit(set,i)) ;) |
10631 | { |
10632 | if (!follow[i].chr) |
10633 | { |
10634 | if (! default_state) |
10635 | default_state= find_found(found_set,set->table_offset, |
10636 | set->found_offset+1); |
10637 | } |
10638 | } |
10639 | copy_bits(sets.set+used_sets,set); /* Save set for changes */ |
10640 | if (!default_state) |
10641 | or_bits(sets.set+used_sets,sets.set); /* Can restart from start */ |
10642 | |
10643 | /* Find all chars that follows current sets */ |
10644 | bzero(used_chars, sizeof(used_chars)); |
10645 | for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;) |
10646 | { |
10647 | used_chars[follow[i].chr]=1; |
10648 | if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr && |
10649 | follow[i].len > 1) || follow[i].chr == END_OF_LINE) |
10650 | used_chars[0]=1; |
10651 | } |
10652 | |
10653 | /* Mark word_chars used if \b is in state */ |
10654 | if (used_chars[SPACE_CHAR]) |
10655 | for (pos= word_end_chars ; *pos ; pos++) |
10656 | used_chars[(int) (uchar) *pos] = 1; |
10657 | |
10658 | /* Handle other used characters */ |
10659 | for (chr= 0 ; chr < 256 ; chr++) |
10660 | { |
10661 | if (! used_chars[chr]) |
10662 | set->next[chr]= chr ? default_state : -1; |
10663 | else |
10664 | { |
10665 | new_set=make_new_set(&sets); |
10666 | set=sets.set+set_nr; /* if realloc */ |
10667 | new_set->table_offset=set->table_offset; |
10668 | new_set->found_len=set->found_len; |
10669 | new_set->found_offset=set->found_offset+1; |
10670 | found_end=0; |
10671 | |
10672 | for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; ) |
10673 | { |
10674 | if (!follow[i].chr || follow[i].chr == chr || |
10675 | (follow[i].chr == SPACE_CHAR && |
10676 | (is_word_end[chr] || |
10677 | (!chr && follow[i].len > 1 && ! follow[i+1].chr))) || |
10678 | (follow[i].chr == END_OF_LINE && ! chr)) |
10679 | { |
10680 | if ((! chr || (follow[i].chr && !follow[i+1].chr)) && |
10681 | follow[i].len > found_end) |
10682 | found_end=follow[i].len; |
10683 | if (chr && follow[i].chr) |
10684 | internal_set_bit(new_set,i+1); /* To next set */ |
10685 | else |
10686 | internal_set_bit(new_set,i); |
10687 | } |
10688 | } |
10689 | if (found_end) |
10690 | { |
10691 | new_set->found_len=0; /* Set for testing if first */ |
10692 | bits_set=0; |
10693 | for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;) |
10694 | { |
10695 | if ((follow[i].chr == SPACE_CHAR || |
10696 | follow[i].chr == END_OF_LINE) && ! chr) |
10697 | bit_nr=i+1; |
10698 | else |
10699 | bit_nr=i; |
10700 | if (follow[bit_nr-1].len < found_end || |
10701 | (new_set->found_len && |
10702 | (chr == 0 || !follow[bit_nr].chr))) |
10703 | internal_clear_bit(new_set,i); |
10704 | else |
10705 | { |
10706 | if (chr == 0 || !follow[bit_nr].chr) |
10707 | { /* best match */ |
10708 | new_set->table_offset=follow[bit_nr].table_offset; |
10709 | if (chr || (follow[i].chr == SPACE_CHAR || |
10710 | follow[i].chr == END_OF_LINE)) |
10711 | new_set->found_offset=found_end; /* New match */ |
10712 | new_set->found_len=found_end; |
10713 | } |
10714 | bits_set++; |
10715 | } |
10716 | } |
10717 | if (bits_set == 1) |
10718 | { |
10719 | set->next[chr] = find_found(found_set, |
10720 | new_set->table_offset, |
10721 | new_set->found_offset); |
10722 | free_last_set(&sets); |
10723 | } |
10724 | else |
10725 | set->next[chr] = find_set(&sets,new_set); |
10726 | } |
10727 | else |
10728 | set->next[chr] = find_set(&sets,new_set); |
10729 | } |
10730 | } |
10731 | } |
10732 | |
10733 | /* Alloc replace structure for the replace-state-machine */ |
10734 | |
10735 | if ((replace=(REPLACE*) my_malloc(sizeof(REPLACE)*(sets.count)+ |
10736 | sizeof(REPLACE_STRING)*(found_sets+1)+ |
10737 | sizeof(char *)*count+result_len, |
10738 | MYF(MY_WME | MY_ZEROFILL)))) |
10739 | { |
10740 | rep_str=(REPLACE_STRING*) (replace+sets.count); |
10741 | to_array= (char **) (rep_str+found_sets+1); |
10742 | to_pos=(char *) (to_array+count); |
10743 | for (i=0 ; i < count ; i++) |
10744 | { |
10745 | to_array[i]=to_pos; |
10746 | to_pos=strmov(to_pos,to[i])+1; |
10747 | } |
10748 | rep_str[0].found=1; |
10749 | rep_str[0].replace_string=0; |
10750 | for (i=1 ; i <= found_sets ; i++) |
10751 | { |
10752 | pos=from[found_set[i-1].table_offset]; |
10753 | rep_str[i].found= !memcmp(pos, "\\^" , 3) ? 2 : 1; |
10754 | rep_str[i].replace_string=to_array[found_set[i-1].table_offset]; |
10755 | rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos); |
10756 | rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+ |
10757 | end_of_word(pos); |
10758 | } |
10759 | for (i=0 ; i < sets.count ; i++) |
10760 | { |
10761 | for (j=0 ; j < 256 ; j++) |
10762 | if (sets.set[i].next[j] >= 0) |
10763 | replace[i].next[j]=replace+sets.set[i].next[j]; |
10764 | else |
10765 | replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1)); |
10766 | } |
10767 | } |
10768 | my_free(follow); |
10769 | free_sets(&sets); |
10770 | my_free(found_set); |
10771 | DBUG_PRINT("exit" ,("Replace table has %d states" ,sets.count)); |
10772 | DBUG_RETURN(replace); |
10773 | } |
10774 | |
10775 | |
10776 | int init_sets(REP_SETS *sets,uint states) |
10777 | { |
10778 | bzero(sets, sizeof(*sets)); |
10779 | sets->size_of_bits=((states+7)/8); |
10780 | if (!(sets->set_buffer=(REP_SET*) my_malloc(sizeof(REP_SET)*SET_MALLOC_HUNC, |
10781 | MYF(MY_WME)))) |
10782 | return 1; |
10783 | if (!(sets->bit_buffer=(uint*) my_malloc(sizeof(uint)*sets->size_of_bits* |
10784 | SET_MALLOC_HUNC,MYF(MY_WME)))) |
10785 | { |
10786 | my_free(sets->set); |
10787 | return 1; |
10788 | } |
10789 | return 0; |
10790 | } |
10791 | |
10792 | /* Make help sets invisible for nicer codeing */ |
10793 | |
10794 | void make_sets_invisible(REP_SETS *sets) |
10795 | { |
10796 | sets->invisible=sets->count; |
10797 | sets->set+=sets->count; |
10798 | sets->count=0; |
10799 | } |
10800 | |
10801 | REP_SET *make_new_set(REP_SETS *sets) |
10802 | { |
10803 | uint i,count,*bit_buffer; |
10804 | REP_SET *set; |
10805 | if (sets->extra) |
10806 | { |
10807 | sets->extra--; |
10808 | set=sets->set+ sets->count++; |
10809 | bzero(set->bits, sizeof(uint) * sets->size_of_bits); |
10810 | bzero(&set->next[0], sizeof(set->next[0]) * LAST_CHAR_CODE); |
10811 | set->found_offset=0; |
10812 | set->found_len=0; |
10813 | set->table_offset= (uint) ~0; |
10814 | set->size_of_bits=sets->size_of_bits; |
10815 | return set; |
10816 | } |
10817 | count=sets->count+sets->invisible+SET_MALLOC_HUNC; |
10818 | if (!(set=(REP_SET*) my_realloc(sets->set_buffer, sizeof(REP_SET)*count, |
10819 | MYF(MY_WME)))) |
10820 | return 0; |
10821 | sets->set_buffer=set; |
10822 | sets->set=set+sets->invisible; |
10823 | if (!(bit_buffer=(uint*) my_realloc(sets->bit_buffer, |
10824 | (sizeof(uint)*sets->size_of_bits)*count, |
10825 | MYF(MY_WME)))) |
10826 | return 0; |
10827 | sets->bit_buffer=bit_buffer; |
10828 | for (i=0 ; i < count ; i++) |
10829 | { |
10830 | sets->set_buffer[i].bits=bit_buffer; |
10831 | bit_buffer+=sets->size_of_bits; |
10832 | } |
10833 | sets->extra=SET_MALLOC_HUNC; |
10834 | return make_new_set(sets); |
10835 | } |
10836 | |
10837 | void free_last_set(REP_SETS *sets) |
10838 | { |
10839 | sets->count--; |
10840 | sets->extra++; |
10841 | return; |
10842 | } |
10843 | |
10844 | void free_sets(REP_SETS *sets) |
10845 | { |
10846 | my_free(sets->set_buffer); |
10847 | my_free(sets->bit_buffer); |
10848 | return; |
10849 | } |
10850 | |
10851 | void internal_set_bit(REP_SET *set, uint bit) |
10852 | { |
10853 | set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT); |
10854 | return; |
10855 | } |
10856 | |
10857 | void internal_clear_bit(REP_SET *set, uint bit) |
10858 | { |
10859 | set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT)); |
10860 | return; |
10861 | } |
10862 | |
10863 | |
10864 | void or_bits(REP_SET *to,REP_SET *from) |
10865 | { |
10866 | uint i; |
10867 | for (i=0 ; i < to->size_of_bits ; i++) |
10868 | to->bits[i]|=from->bits[i]; |
10869 | return; |
10870 | } |
10871 | |
10872 | void copy_bits(REP_SET *to,REP_SET *from) |
10873 | { |
10874 | memcpy(to->bits, from->bits, |
10875 | (size_t) (sizeof(uint) * to->size_of_bits)); |
10876 | } |
10877 | |
10878 | int cmp_bits(REP_SET *set1,REP_SET *set2) |
10879 | { |
10880 | return memcmp(set1->bits, set2->bits, |
10881 | sizeof(uint) * set1->size_of_bits); |
10882 | } |
10883 | |
10884 | |
10885 | /* Get next set bit from set. */ |
10886 | |
10887 | int get_next_bit(REP_SET *set,uint lastpos) |
10888 | { |
10889 | uint pos,*start,*end,bits; |
10890 | |
10891 | start=set->bits+ ((lastpos+1) / WORD_BIT); |
10892 | end=set->bits + set->size_of_bits; |
10893 | bits=start[0] & ~((1 << ((lastpos+1) % WORD_BIT)) -1); |
10894 | |
10895 | while (! bits && ++start < end) |
10896 | bits=start[0]; |
10897 | if (!bits) |
10898 | return 0; |
10899 | pos=(uint) (start-set->bits)*WORD_BIT; |
10900 | while (! (bits & 1)) |
10901 | { |
10902 | bits>>=1; |
10903 | pos++; |
10904 | } |
10905 | return pos; |
10906 | } |
10907 | |
10908 | /* find if there is a same set in sets. If there is, use it and |
10909 | free given set, else put in given set in sets and return its |
10910 | position */ |
10911 | |
10912 | int find_set(REP_SETS *sets,REP_SET *find) |
10913 | { |
10914 | uint i; |
10915 | for (i=0 ; i < sets->count-1 ; i++) |
10916 | { |
10917 | if (!cmp_bits(sets->set+i,find)) |
10918 | { |
10919 | free_last_set(sets); |
10920 | return i; |
10921 | } |
10922 | } |
10923 | return i; /* return new position */ |
10924 | } |
10925 | |
10926 | /* find if there is a found_set with same table_offset & found_offset |
10927 | If there is return offset to it, else add new offset and return pos. |
10928 | Pos returned is -offset-2 in found_set_structure because it is |
10929 | saved in set->next and set->next[] >= 0 points to next set and |
10930 | set->next[] == -1 is reserved for end without replaces. |
10931 | */ |
10932 | |
10933 | int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) |
10934 | { |
10935 | int i; |
10936 | for (i=0 ; (uint) i < found_sets ; i++) |
10937 | if (found_set[i].table_offset == table_offset && |
10938 | found_set[i].found_offset == found_offset) |
10939 | return -i-2; |
10940 | found_set[i].table_offset=table_offset; |
10941 | found_set[i].found_offset=found_offset; |
10942 | found_sets++; |
10943 | return -i-2; /* return new position */ |
10944 | } |
10945 | |
10946 | /* Return 1 if regexp starts with \b or ends with \b*/ |
10947 | |
10948 | uint start_at_word(char * pos) |
10949 | { |
10950 | return (((!memcmp(pos, "\\b" ,2) && pos[2]) || |
10951 | !memcmp(pos, "\\^" , 2)) ? 1 : 0); |
10952 | } |
10953 | |
10954 | uint end_of_word(char * pos) |
10955 | { |
10956 | char * end=strend(pos); |
10957 | return ((end > pos+2 && !memcmp(end-2, "\\b" , 2)) || |
10958 | (end >= pos+2 && !memcmp(end-2, "\\$" ,2))) ? 1 : 0; |
10959 | } |
10960 | |
10961 | /**************************************************************************** |
10962 | * Handle replacement of strings |
10963 | ****************************************************************************/ |
10964 | |
10965 | #define PC_MALLOC 256 /* Bytes for pointers */ |
10966 | #define PS_MALLOC 512 /* Bytes for data */ |
10967 | |
10968 | int insert_pointer_name(POINTER_ARRAY *pa,char * name) |
10969 | { |
10970 | uint i,length,old_count; |
10971 | uchar *new_pos; |
10972 | const char **new_array; |
10973 | DBUG_ENTER("insert_pointer_name" ); |
10974 | |
10975 | if (! pa->typelib.count) |
10976 | { |
10977 | if (!(pa->typelib.type_names=(const char **) |
10978 | my_malloc(((PC_MALLOC-MALLOC_OVERHEAD)/ |
10979 | (sizeof(char *)+sizeof(*pa->flag))* |
10980 | (sizeof(char *)+sizeof(*pa->flag))),MYF(MY_WME)))) |
10981 | DBUG_RETURN(-1); |
10982 | if (!(pa->str= (uchar*) my_malloc(PS_MALLOC - MALLOC_OVERHEAD, |
10983 | MYF(MY_WME)))) |
10984 | { |
10985 | my_free(pa->typelib.type_names); |
10986 | DBUG_RETURN (-1); |
10987 | } |
10988 | pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(uchar*)+ |
10989 | sizeof(*pa->flag)); |
10990 | pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count); |
10991 | pa->length=0; |
10992 | pa->max_length=PS_MALLOC-MALLOC_OVERHEAD; |
10993 | pa->array_allocs=1; |
10994 | } |
10995 | length=(uint) strlen(name)+1; |
10996 | if (pa->length+length >= pa->max_length) |
10997 | { |
10998 | if (!(new_pos= (uchar*) my_realloc(pa->str, pa->length + length + PS_MALLOC, |
10999 | MYF(MY_WME)))) |
11000 | DBUG_RETURN(1); |
11001 | if (new_pos != pa->str) |
11002 | { |
11003 | my_ptrdiff_t diff=PTR_BYTE_DIFF(new_pos,pa->str); |
11004 | for (i=0 ; i < pa->typelib.count ; i++) |
11005 | pa->typelib.type_names[i]= ADD_TO_PTR(pa->typelib.type_names[i],diff, |
11006 | char*); |
11007 | pa->str=new_pos; |
11008 | } |
11009 | pa->max_length= pa->length+length+PS_MALLOC; |
11010 | } |
11011 | if (pa->typelib.count >= pa->max_count-1) |
11012 | { |
11013 | int len; |
11014 | pa->array_allocs++; |
11015 | len=(PC_MALLOC*pa->array_allocs - MALLOC_OVERHEAD); |
11016 | if (!(new_array=(const char **) my_realloc(pa->typelib.type_names, |
11017 | len/ |
11018 | (sizeof(uchar*)+sizeof(*pa->flag))* |
11019 | (sizeof(uchar*)+sizeof(*pa->flag)), |
11020 | MYF(MY_WME)))) |
11021 | DBUG_RETURN(1); |
11022 | pa->typelib.type_names=new_array; |
11023 | old_count=pa->max_count; |
11024 | pa->max_count=len/(sizeof(uchar*) + sizeof(*pa->flag)); |
11025 | pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count); |
11026 | memcpy(pa->flag, (pa->typelib.type_names +old_count), |
11027 | old_count*sizeof(*pa->flag)); |
11028 | } |
11029 | pa->flag[pa->typelib.count]=0; /* Reset flag */ |
11030 | pa->typelib.type_names[pa->typelib.count++]= (char*) pa->str+pa->length; |
11031 | pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ |
11032 | (void) strmov((char*) pa->str + pa->length,name); |
11033 | pa->length+=length; |
11034 | DBUG_RETURN(0); |
11035 | } /* insert_pointer_name */ |
11036 | |
11037 | |
11038 | /* free pointer array */ |
11039 | |
11040 | void free_pointer_array(POINTER_ARRAY *pa) |
11041 | { |
11042 | if (pa->typelib.count) |
11043 | { |
11044 | pa->typelib.count=0; |
11045 | my_free(pa->typelib.type_names); |
11046 | pa->typelib.type_names=0; |
11047 | my_free(pa->str); |
11048 | } |
11049 | } /* free_pointer_array */ |
11050 | |
11051 | |
11052 | /* Functions that uses replace and replace_regex */ |
11053 | |
11054 | /* Append the string to ds, with optional replace */ |
11055 | void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len) |
11056 | { |
11057 | char lower[1024]; |
11058 | |
11059 | if (len < sizeof(lower) - 1) |
11060 | { |
11061 | if (display_result_lower) |
11062 | { |
11063 | /* Convert to lower case, and do this first */ |
11064 | char *c= lower; |
11065 | for (const char *v= val; *v; v++) |
11066 | *c++= my_tolower(charset_info, *v); |
11067 | *c= '\0'; |
11068 | /* Copy from this buffer instead */ |
11069 | } |
11070 | else |
11071 | memcpy(lower, val, len+1); |
11072 | fix_win_paths(lower, len); |
11073 | val= lower; |
11074 | } |
11075 | |
11076 | if (glob_replace_regex) |
11077 | { |
11078 | /* Regex replace */ |
11079 | if (!multi_reg_replace(glob_replace_regex, (char*)val)) |
11080 | { |
11081 | val= glob_replace_regex->buf; |
11082 | len= strlen(val); |
11083 | } |
11084 | } |
11085 | |
11086 | if (glob_replace) |
11087 | { |
11088 | /* Normal replace */ |
11089 | replace_strings_append(glob_replace, ds, val); |
11090 | } |
11091 | else |
11092 | dynstr_append_mem(ds, val, len); |
11093 | } |
11094 | |
11095 | |
11096 | /* Append zero-terminated string to ds, with optional replace */ |
11097 | void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) |
11098 | { |
11099 | replace_dynstr_append_mem(ds, val, strlen(val)); |
11100 | } |
11101 | |
11102 | /* Append uint to ds, with optional replace */ |
11103 | void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) |
11104 | { |
11105 | char buff[22]; /* This should be enough for any int */ |
11106 | char *end= longlong10_to_str(val, buff, 10); |
11107 | replace_dynstr_append_mem(ds, buff, end - buff); |
11108 | } |
11109 | |
11110 | |
11111 | /* |
11112 | Build a list of pointer to each line in ds_input, sort |
11113 | the list and use the sorted list to append the strings |
11114 | sorted to the output ds |
11115 | |
11116 | SYNOPSIS |
11117 | dynstr_append_sorted() |
11118 | ds string where the sorted output will be appended |
11119 | ds_input string to be sorted |
11120 | keep_header If header should not be sorted |
11121 | */ |
11122 | |
11123 | static int comp_lines(const char **a, const char **b) |
11124 | { |
11125 | return (strcmp(*a,*b)); |
11126 | } |
11127 | |
11128 | void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING *ds_input, |
11129 | bool ) |
11130 | { |
11131 | unsigned i; |
11132 | char *start= ds_input->str; |
11133 | DYNAMIC_ARRAY lines; |
11134 | DBUG_ENTER("dynstr_append_sorted" ); |
11135 | |
11136 | if (!*start) |
11137 | DBUG_VOID_RETURN; /* No input */ |
11138 | |
11139 | my_init_dynamic_array(&lines, sizeof(const char*), 32, 32, MYF(0)); |
11140 | |
11141 | if (keep_header) |
11142 | { |
11143 | /* First line is result header, skip past it */ |
11144 | while (*start && *start != '\n') |
11145 | start++; |
11146 | start++; /* Skip past \n */ |
11147 | dynstr_append_mem(ds, ds_input->str, start - ds_input->str); |
11148 | } |
11149 | |
11150 | /* Insert line(s) in array */ |
11151 | while (*start) |
11152 | { |
11153 | char* line_end= (char*)start; |
11154 | |
11155 | /* Find end of line */ |
11156 | while (*line_end && *line_end != '\n') |
11157 | line_end++; |
11158 | *line_end= 0; |
11159 | |
11160 | /* Insert pointer to the line in array */ |
11161 | if (insert_dynamic(&lines, &start)) |
11162 | die("Out of memory inserting lines to sort" ); |
11163 | |
11164 | start= line_end+1; |
11165 | } |
11166 | |
11167 | /* Sort array */ |
11168 | qsort(lines.buffer, lines.elements, |
11169 | sizeof(char**), (qsort_cmp)comp_lines); |
11170 | |
11171 | /* Create new result */ |
11172 | for (i= 0; i < lines.elements ; i++) |
11173 | { |
11174 | const char **line= dynamic_element(&lines, i, const char**); |
11175 | dynstr_append(ds, *line); |
11176 | dynstr_append(ds, "\n" ); |
11177 | } |
11178 | |
11179 | delete_dynamic(&lines); |
11180 | DBUG_VOID_RETURN; |
11181 | } |
11182 | |
11183 | #ifndef HAVE_SETENV |
11184 | static int setenv(const char *name, const char *value, int overwrite) |
11185 | { |
11186 | size_t buflen= strlen(name) + strlen(value) + 2; |
11187 | char *envvar= (char *)malloc(buflen); |
11188 | if(!envvar) |
11189 | return ENOMEM; |
11190 | strcpy(envvar, name); |
11191 | strcat(envvar, "=" ); |
11192 | strcat(envvar, value); |
11193 | putenv(envvar); |
11194 | return 0; |
11195 | } |
11196 | #endif |
11197 | |
11198 | /* |
11199 | for the purpose of testing (see dialog.test) |
11200 | we replace default mysql_authentication_dialog_ask function with the one, |
11201 | that always reads from stdin with explicit echo. |
11202 | |
11203 | */ |
11204 | MYSQL_PLUGIN_EXPORT |
11205 | char *mysql_authentication_dialog_ask(MYSQL *mysql, int type, |
11206 | const char *prompt, |
11207 | char *buf, int buf_len) |
11208 | { |
11209 | char *s=buf; |
11210 | |
11211 | fputs(prompt, stdout); |
11212 | fputs(" " , stdout); |
11213 | |
11214 | if (!fgets(buf, buf_len-1, stdin)) |
11215 | buf[0]= 0; |
11216 | else if (buf[0] && (s= strend(buf))[-1] == '\n') |
11217 | s[-1]= 0; |
11218 | |
11219 | for (s= buf; *s; s++) |
11220 | fputc(type == 2 ? '*' : *s, stdout); |
11221 | |
11222 | fputc('\n', stdout); |
11223 | |
11224 | return buf; |
11225 | } |
11226 | |