1 // Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License, version 2.0,
5 // as published by the Free Software Foundation.
6 //
7 // This program is also distributed with certain software (including
8 // but not limited to OpenSSL) that is licensed under separate terms,
9 // as designated in a particular file or component or in included license
10 // documentation. The authors of MySQL hereby grant you an additional
11 // permission to link the program and your derivative works with the
12 // separately licensed software that they have included with MySQL.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License, version 2.0, for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22
23 /// @file
24 ///
25 /// mysqltest client - Tool used for executing a .test file.
26 ///
27 /// See @ref PAGE_MYSQL_TEST_RUN "The MySQL Test Framework" for more
28 /// information.
29
30 #include "client/mysqltest/error_names.h"
31 #include "client/mysqltest/expected_errors.h"
32 #include "client/mysqltest/expected_warnings.h"
33 #include "client/mysqltest/logfile.h"
34 #include "client/mysqltest/regular_expressions.h"
35 #include "client/mysqltest/secondary_engine.h"
36 #include "client/mysqltest/utils.h"
37 #include "compression.h"
38
39 #include <algorithm>
40 #include <chrono>
41 #include <cmath> // std::isinf
42 #include <limits>
43 #include <new>
44 #include <sstream>
45 #ifdef _WIN32
46 #include <thread> // std::thread
47 #endif
48
49 #include <assert.h>
50 #if defined MY_MSCRT_DEBUG || defined _WIN32
51 #include <crtdbg.h>
52 #endif
53 #ifdef _WIN32
54 #include <direct.h>
55 #endif
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <limits.h>
59 #include <mysql_async.h>
60 #include <mysql_version.h>
61 #include <mysqld_error.h>
62 #include <signal.h>
63 #include <stdarg.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <sys/types.h>
67 #ifndef _WIN32
68 #include <poll.h>
69 #include <sys/time.h>
70 #include <sys/wait.h>
71 #endif
72 #ifdef _WIN32
73 #include <windows.h>
74 #endif
75
76 #include "caching_sha2_passwordopt-vars.h"
77 #include "client/client_priv.h"
78 #include "m_ctype.h"
79 #include "map_helpers.h"
80 #include "mf_wcomp.h" // wild_compare
81 #include "my_compiler.h"
82 #include "my_config.h"
83 #include "my_dbug.h"
84 #include "my_default.h"
85 #include "my_dir.h"
86 #include "my_inttypes.h"
87 #include "my_macros.h"
88 #include "my_pointer_arithmetic.h"
89 #include "my_stacktrace.h"
90 #include "my_systime.h" // my_sleep()
91 #include "my_thread_local.h"
92 #include "prealloced_array.h"
93 #include "print_version.h"
94 #include "sql_common.h"
95 #include "template_utils.h"
96 #include "typelib.h"
97 #include "violite.h"
98 #include "welcome_copyright_notice.h" // ORACLE_WELCOME_COPYRIGHT_NOTICE
99
100 #ifdef _WIN32
101 #define SIGNAL_FMT "exception 0x%x"
102 #else
103 #define SIGNAL_FMT "signal %d"
104 #endif
105
106 #ifdef _WIN32
107 #define setenv(a, b, c) _putenv_s(a, b)
108 #define popen _popen
109 #define pclose _pclose
110 #endif
111
112 #define MAX_VAR_NAME_LENGTH 256
113 #define MAX_COLUMNS 256
114 #define MAX_DELIMITER_LENGTH 16
115 #define DEFAULT_MAX_CONN 128
116 #define REPLACE_ROUND_MAX 16
117
118 /* Flags controlling send and reap */
119 #define QUERY_SEND_FLAG 1
120 #define QUERY_REAP_FLAG 2
121
122 #define APPEND_TYPE(type) \
123 { \
124 dynstr_append(ds, "-- "); \
125 switch (type) { \
126 case SESSION_TRACK_SYSTEM_VARIABLES: \
127 dynstr_append(ds, "Tracker : SESSION_TRACK_SYSTEM_VARIABLES\n"); \
128 break; \
129 case SESSION_TRACK_SCHEMA: \
130 dynstr_append(ds, "Tracker : SESSION_TRACK_SCHEMA\n"); \
131 break; \
132 case SESSION_TRACK_STATE_CHANGE: \
133 dynstr_append(ds, "Tracker : SESSION_TRACK_STATE_CHANGE\n"); \
134 break; \
135 case SESSION_TRACK_GTIDS: \
136 dynstr_append(ds, "Tracker : SESSION_TRACK_GTIDS\n"); \
137 break; \
138 case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: \
139 dynstr_append( \
140 ds, "Tracker : SESSION_TRACK_TRANSACTION_CHARACTERISTICS\n"); \
141 break; \
142 case SESSION_TRACK_TRANSACTION_STATE: \
143 dynstr_append(ds, "Tracker : SESSION_TRACK_TRANSACTION_STATE\n"); \
144 break; \
145 default: \
146 dynstr_append(ds, "\n"); \
147 } \
148 }
149
150 extern CHARSET_INFO my_charset_utf16le_bin;
151
152 // List of error codes specified with 'error' command.
153 Expected_errors *expected_errors = new Expected_errors();
154
155 // List of warnings disabled with 'disable_warnings' command.
156 Expected_warnings *disabled_warnings = new Expected_warnings();
157
158 // List of warnings enabled with 'enable_warnings' command.
159 Expected_warnings *enabled_warnings = new Expected_warnings();
160
161 enum {
162 OPT_COLORED_DIFF = OPT_MAX_CLIENT_OPTION,
163 OPT_CURSOR_PROTOCOL,
164 OPT_EXPLAIN_PROTOCOL,
165 OPT_JSON_EXPLAIN_PROTOCOL,
166 OPT_LOG_DIR,
167 OPT_MARK_PROGRESS,
168 OPT_MAX_CONNECT_RETRIES,
169 OPT_MAX_CONNECTIONS,
170 OPT_NO_SKIP,
171 OPT_OFFLOAD_COUNT_FILE,
172 OPT_PS_PROTOCOL,
173 OPT_RESULT_FORMAT_VERSION,
174 #ifdef _WIN32
175 OPT_SAFEPROCESS_PID,
176 #endif
177 OPT_SP_PROTOCOL,
178 OPT_TAIL_LINES,
179 OPT_TRACE_EXEC,
180 OPT_TRACE_PROTOCOL,
181 OPT_VIEW_PROTOCOL,
182 };
183
184 static int record = 0;
185 static char *opt_db = nullptr, *opt_pass = nullptr;
186 const char *opt_user = nullptr, *opt_host = nullptr, *unix_sock = nullptr,
187 *opt_basedir = "./";
188 const char *excluded_string = nullptr;
189 static char *shared_memory_base_name = nullptr;
190 const char *opt_logdir = "";
191 const char *opt_include = nullptr, *opt_charsets_dir;
192 static int opt_port = 0;
193 static int opt_max_connect_retries;
194 static int opt_result_format_version;
195 static int opt_max_connections = DEFAULT_MAX_CONN;
196 static bool opt_colored_diff = false;
197 static bool opt_compress = false, silent = false, verbose = false,
198 trace_exec = false;
199 static bool debug_info_flag = false, debug_check_flag = false;
200 static bool tty_password = false;
201 static bool opt_mark_progress = false;
202 static bool ps_protocol = false, ps_protocol_enabled = false;
203 static bool sp_protocol = false, sp_protocol_enabled = false;
204 static bool no_skip = false;
205 static bool view_protocol = false, view_protocol_enabled = false;
206 static bool opt_trace_protocol = false, opt_trace_protocol_enabled = false;
207 static bool explain_protocol = false, explain_protocol_enabled = false;
208 static bool json_explain_protocol = false,
209 json_explain_protocol_enabled = false;
210 static bool cursor_protocol = false, cursor_protocol_enabled = false;
211 static bool testcase_disabled = false;
212 static bool display_result_vertically = false, display_result_lower = false,
213 display_metadata = false, display_result_sorted = false,
214 display_session_track_info = false;
215 static int start_sort_column = 0;
216 static bool disable_query_log = false, disable_result_log = false;
217 static bool disable_connect_log = true;
218 static bool disable_warnings = false;
219 static bool disable_info = true;
220 static bool abort_on_error = true;
221 static bool server_initialized = false;
222 static bool is_windows = false;
223 static MEM_ROOT argv_alloc{PSI_NOT_INSTRUMENTED, 512};
224 static const char *load_default_groups[] = {"mysqltest", "client", nullptr};
225 static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos = line_buffer;
226 static bool can_handle_expired_passwords = true;
227
228 /*
229 These variables control the behavior of the asynchronous operations for
230 mysqltest client. If --async-client is specified, use_async_client is true.
231 Each command checks enable_async_client (which can be forced off or
232 disabled for a single command) to decide the mode it uses to run.
233 */
234 static bool use_async_client = false;
235 static bool enable_async_client = false;
236
237 // Secondary engine options
238 static const char *opt_offload_count_file;
239
240 static Secondary_engine *secondary_engine = nullptr;
241
242 static uint opt_zstd_compress_level = default_zstd_compression_level;
243 static char *opt_compress_algorithm = nullptr;
244
245 #ifdef _WIN32
246 static DWORD opt_safe_process_pid;
247 static HANDLE mysqltest_thread;
248 // Event handle for stacktrace request event
249 static HANDLE stacktrace_request_event = NULL;
250 static std::thread wait_for_stacktrace_request_event_thread;
251 #endif
252
253 Logfile log_file;
254 // File to store the progress
255 Logfile progress_file;
256
257 /// Info on properties that can be set with '--disable_X' and
258 /// '--disable_X' commands.
259 struct Property {
260 bool *var; // Actual variable
261 bool set; // Has been set for ONCE command
262 bool old; // If set, thus is the old value
263 bool reverse; // Variable is true if disabled
264 const char *env_name; // Environment variable name
265 };
266
267 static struct Property prop_list[] = {
268 {&abort_on_error, false, true, false, "$ENABLE_ABORT_ON_ERROR"},
269 {&disable_connect_log, false, true, true, "$ENABLE_CONNECT_LOG"},
270 {&disable_info, false, true, true, "$ENABLE_INFO"},
271 {&display_session_track_info, false, true, true,
272 "$ENABLE_STATE_CHANGE_INFO"},
273 {&display_metadata, false, false, false, "$ENABLE_METADATA"},
274 {&ps_protocol_enabled, false, false, false, "$ENABLE_PS_PROTOCOL"},
275 {&disable_query_log, false, false, true, "$ENABLE_QUERY_LOG"},
276 {&disable_result_log, false, false, true, "$ENABLE_RESULT_LOG"},
277 {&disable_warnings, false, false, true, "$ENABLE_WARNINGS"},
278 {&enable_async_client, false, false, false, "$ENABLE_ASYNC_CLIENT"}};
279
280 static bool once_property = false;
281
282 enum enum_prop {
283 P_ABORT = 0,
284 P_CONNECT,
285 P_INFO,
286 P_SESSION_TRACK,
287 P_META,
288 P_PS,
289 P_QUERY,
290 P_RESULT,
291 P_WARN,
292 P_ASYNC,
293 P_MAX
294 };
295
296 static uint start_lineno = 0; /* Start line of current command */
297 static uint my_end_arg = 0;
298
299 /* Number of lines of the result to include in failure report */
300 static uint opt_tail_lines = 0;
301
302 static uint opt_connect_timeout = 0;
303
304 static char delimiter[MAX_DELIMITER_LENGTH] = ";";
305 static size_t delimiter_length = 1;
306
307 static char TMPDIR[FN_REFLEN];
308
309 /* Block stack */
310 enum block_cmd { cmd_none, cmd_if, cmd_while };
311
312 struct st_block {
313 int line; /* Start line of block */
314 bool ok; /* Should block be executed */
315 enum block_cmd cmd; /* Command owning the block */
316 char delim[MAX_DELIMITER_LENGTH]; /* Delimiter before block */
317 };
318
319 static struct st_block block_stack[32];
320 static struct st_block *cur_block, *block_stack_end;
321
322 /* Open file stack */
323 struct st_test_file {
324 FILE *file;
325 char *file_name;
326 uint lineno; /* Current line in file */
327 };
328
329 static struct st_test_file file_stack[16];
330 static struct st_test_file *cur_file;
331 static struct st_test_file *file_stack_end;
332
333 static const char *default_charset = MYSQL_DEFAULT_CHARSET_NAME;
334 CHARSET_INFO *charset_info =
335 &my_charset_utf8mb4_0900_ai_ci; /* Default charset */
336
337 /*
338 Timer related variables
339 See the timer_output() definition for details
340 */
341 static char *timer_file = nullptr;
342 static ulonglong timer_start;
343 static void timer_output(void);
344 static ulonglong timer_now(void);
345
346 static ulong connection_retry_sleep = 100000; /* Microseconds */
347
348 static char *opt_plugin_dir = nullptr;
349
350 /* To retrieve a filename from a filepath */
get_filename_from_path(const char * path)351 const char *get_filename_from_path(const char *path) {
352 const char *fname = nullptr;
353 if (is_windows)
354 fname = strrchr(path, '\\');
355 else
356 fname = strrchr(path, '/');
357 if (fname == nullptr)
358 return path;
359 else
360 return ++fname;
361 }
362
363 static uint opt_protocol = 0;
364
365 #if defined(_WIN32)
366 static uint opt_protocol_for_default_connection = MYSQL_PROTOCOL_PIPE;
367 #endif
368
369 struct st_command;
370 typedef Prealloced_array<st_command *, 1024> Q_lines;
371 Q_lines *q_lines;
372
373 #include "sslopt-vars.h"
374
375 struct Parser {
376 int read_lines, current_line;
377 } parser;
378
379 struct MasterPos {
380 char file[FN_REFLEN];
381 ulong pos;
382 } master_pos;
383
384 /* if set, all results are concated and compared against this file */
385 const char *result_file_name = nullptr;
386
387 typedef struct {
388 char *name;
389 size_t name_len;
390 char *str_val;
391 size_t str_val_len;
392 int int_val;
393 size_t alloced_len;
394 bool int_dirty; /* do not update string if int is updated until first read */
395 bool is_int;
396 bool alloced;
397 } VAR;
398
399 /*Perl/shell-like variable registers */
400 VAR var_reg[10];
401
402 struct var_free {
403 void operator()(VAR *var) const;
404 };
405
406 collation_unordered_map<std::string, std::unique_ptr<VAR, var_free>> *var_hash;
407
408 struct st_connection {
409 MYSQL mysql;
410 /* Used when creating views and sp, to avoid implicit commit */
411 MYSQL *util_mysql;
412 char *name;
413 size_t name_len;
414 MYSQL_STMT *stmt;
415 /* Set after send to disallow other queries before reap */
416 bool pending;
417 };
418
419 struct st_connection *connections = nullptr;
420 struct st_connection *cur_con = nullptr, *next_con, *connections_end;
421
422 /*
423 List of commands in mysqltest
424 Must match the "command_names" array
425 Add new commands before Q_UNKNOWN!
426 */
427 enum enum_commands {
428 Q_CONNECTION = 1,
429 Q_QUERY,
430 Q_CONNECT,
431 Q_SLEEP,
432 Q_INC,
433 Q_DEC,
434 Q_SOURCE,
435 Q_DISCONNECT,
436 Q_LET,
437 Q_ECHO,
438 Q_EXPR,
439 Q_WHILE,
440 Q_END_BLOCK,
441 Q_SAVE_MASTER_POS,
442 Q_SYNC_WITH_MASTER,
443 Q_SYNC_SLAVE_WITH_MASTER,
444 Q_ERROR,
445 Q_SEND,
446 Q_REAP,
447 Q_DIRTY_CLOSE,
448 Q_REPLACE,
449 Q_REPLACE_COLUMN,
450 Q_PING,
451 Q_EVAL,
452 Q_ENABLE_QUERY_LOG,
453 Q_DISABLE_QUERY_LOG,
454 Q_ENABLE_RESULT_LOG,
455 Q_DISABLE_RESULT_LOG,
456 Q_ENABLE_CONNECT_LOG,
457 Q_DISABLE_CONNECT_LOG,
458 Q_WAIT_FOR_SLAVE_TO_STOP,
459 Q_ENABLE_WARNINGS,
460 Q_DISABLE_WARNINGS,
461 Q_ENABLE_INFO,
462 Q_DISABLE_INFO,
463 Q_ENABLE_SESSION_TRACK_INFO,
464 Q_DISABLE_SESSION_TRACK_INFO,
465 Q_ENABLE_METADATA,
466 Q_DISABLE_METADATA,
467 Q_ENABLE_ASYNC_CLIENT,
468 Q_DISABLE_ASYNC_CLIENT,
469 Q_EXEC,
470 Q_EXECW,
471 Q_EXEC_BACKGROUND,
472 Q_DELIMITER,
473 Q_DISABLE_ABORT_ON_ERROR,
474 Q_ENABLE_ABORT_ON_ERROR,
475 Q_DISPLAY_VERTICAL_RESULTS,
476 Q_DISPLAY_HORIZONTAL_RESULTS,
477 Q_QUERY_VERTICAL,
478 Q_QUERY_HORIZONTAL,
479 Q_SORTED_RESULT,
480 Q_PARTIALLY_SORTED_RESULT,
481 Q_LOWERCASE,
482 Q_START_TIMER,
483 Q_END_TIMER,
484 Q_CHARACTER_SET,
485 Q_DISABLE_PS_PROTOCOL,
486 Q_ENABLE_PS_PROTOCOL,
487 Q_DISABLE_RECONNECT,
488 Q_ENABLE_RECONNECT,
489 Q_IF,
490 Q_DISABLE_TESTCASE,
491 Q_ENABLE_TESTCASE,
492 Q_REPLACE_REGEX,
493 Q_REPLACE_NUMERIC_ROUND,
494 Q_REMOVE_FILE,
495 Q_FILE_EXIST,
496 Q_WRITE_FILE,
497 Q_COPY_FILE,
498 Q_PERL,
499 Q_DIE,
500 Q_EXIT,
501 Q_SKIP,
502 Q_CHMOD_FILE,
503 Q_APPEND_FILE,
504 Q_CAT_FILE,
505 Q_DIFF_FILES,
506 Q_SEND_QUIT,
507 Q_CHANGE_USER,
508 Q_MKDIR,
509 Q_RMDIR,
510 Q_FORCE_RMDIR,
511 Q_FORCE_CPDIR,
512 Q_LIST_FILES,
513 Q_LIST_FILES_WRITE_FILE,
514 Q_LIST_FILES_APPEND_FILE,
515 Q_SEND_SHUTDOWN,
516 Q_SHUTDOWN_SERVER,
517 Q_RESULT_FORMAT_VERSION,
518 Q_MOVE_FILE,
519 Q_REMOVE_FILES_WILDCARD,
520 Q_COPY_FILES_WILDCARD,
521 Q_SEND_EVAL,
522 Q_OUTPUT, /* redirect output to a file */
523 Q_RESET_CONNECTION,
524 Q_UNKNOWN, /* Unknown command. */
525 Q_COMMENT, /* Comments, ignored. */
526 Q_COMMENT_WITH_COMMAND,
527 Q_EMPTY_LINE
528 };
529
530 const char *command_names[] = {
531 "connection", "query", "connect", "sleep", "inc", "dec", "source",
532 "disconnect", "let", "echo", "expr", "while", "end", "save_master_pos",
533 "sync_with_master", "sync_slave_with_master", "error", "send", "reap",
534 "dirty_close", "replace_result", "replace_column", "ping", "eval",
535 /* Enable/disable that the _query_ is logged to result file */
536 "enable_query_log", "disable_query_log",
537 /* Enable/disable that the _result_ from a query is logged to result file */
538 "enable_result_log", "disable_result_log", "enable_connect_log",
539 "disable_connect_log", "wait_for_slave_to_stop", "enable_warnings",
540 "disable_warnings", "enable_info", "disable_info",
541 "enable_session_track_info", "disable_session_track_info",
542 "enable_metadata", "disable_metadata", "enable_async_client",
543 "disable_async_client", "exec", "execw", "exec_in_background", "delimiter",
544 "disable_abort_on_error", "enable_abort_on_error", "vertical_results",
545 "horizontal_results", "query_vertical", "query_horizontal", "sorted_result",
546 "partially_sorted_result", "lowercase_result", "start_timer", "end_timer",
547 "character_set", "disable_ps_protocol", "enable_ps_protocol",
548 "disable_reconnect", "enable_reconnect", "if", "disable_testcase",
549 "enable_testcase", "replace_regex", "replace_numeric_round", "remove_file",
550 "file_exists", "write_file", "copy_file", "perl", "die",
551
552 /* Don't execute any more commands, compare result */
553 "exit", "skip", "chmod", "append_file", "cat_file", "diff_files",
554 "send_quit", "change_user", "mkdir", "rmdir", "force-rmdir", "force-cpdir",
555 "list_files", "list_files_write_file", "list_files_append_file",
556 "send_shutdown", "shutdown_server", "result_format", "move_file",
557 "remove_files_wildcard", "copy_files_wildcard", "send_eval", "output",
558 "reset_connection",
559
560 nullptr};
561
562 struct st_command {
563 char *query, *query_buf, *first_argument, *last_argument, *end;
564 DYNAMIC_STRING content;
565 size_t first_word_len, query_len;
566 bool abort_on_error, used_replace;
567 char output_file[FN_REFLEN];
568 enum enum_commands type;
569 // Line number of the command
570 uint lineno;
571 };
572
573 TYPELIB command_typelib = {array_elements(command_names), "", command_names,
574 nullptr};
575
576 DYNAMIC_STRING ds_res;
577 DYNAMIC_STRING ds_result;
578 /* Points to ds_warning in run_query, so it can be freed */
579 DYNAMIC_STRING *ds_warn = nullptr;
580 struct st_command *curr_command = nullptr;
581
582 char builtin_echo[FN_REFLEN];
583
584 /* Stores regex substitutions */
585
586 struct st_replace_regex *glob_replace_regex = nullptr;
587
588 struct REPLACE;
589 REPLACE *glob_replace = nullptr;
590 void replace_strings_append(REPLACE *rep, DYNAMIC_STRING *ds, const char *from,
591 size_t len);
592
593 static void cleanup_and_exit(int exit_code) MY_ATTRIBUTE((noreturn));
594
595 void die(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)))
596 MY_ATTRIBUTE((noreturn));
597 void abort_not_supported_test(const char *fmt, ...)
598 MY_ATTRIBUTE((format(printf, 1, 2))) MY_ATTRIBUTE((noreturn));
599 void verbose_msg(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)));
600 void log_msg(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)));
601
602 VAR *var_from_env(const char *, const char *);
603 VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val,
604 size_t val_len);
605 VAR *var_get(const char *var_name, const char **var_name_end, bool raw,
606 bool ignore_not_existing);
607 void eval_expr(VAR *v, const char *p, const char **p_end, bool open_end = false,
608 bool do_eval = true);
609 bool match_delimiter(int c, const char *delim, size_t length);
610
611 void do_eval(DYNAMIC_STRING *query_eval, const char *query,
612 const char *query_end, bool pass_through_escape_chars);
613 void str_to_file(const char *fname, char *str, size_t size);
614 void str_to_file2(const char *fname, char *str, size_t size, bool append);
615
616 void fix_win_paths(const char *val, size_t len);
617
618 #ifdef _WIN32
619 void free_win_path_patterns();
620 #endif
621
622 /* For replace_column */
623 static char *replace_column[MAX_COLUMNS];
624 static uint max_replace_column = 0;
625 void do_get_replace_column(struct st_command *);
626 void free_replace_column();
627
628 /* For replace */
629 void do_get_replace(struct st_command *command);
630 void free_replace();
631
632 /* For replace_regex */
633 void do_get_replace_regex(struct st_command *command);
634 void free_replace_regex();
635
636 /* For replace numeric round */
637 static int glob_replace_numeric_round = -1;
638 void do_get_replace_numeric_round(struct st_command *command);
639 void free_replace_numeric_round();
640 void replace_numeric_round_append(int round, DYNAMIC_STRING *ds,
641 const char *from, size_t len);
642
643 /* Used by sleep */
644 void check_eol_junk_line(const char *eol);
645
646 static void var_set(const char *var_name, const char *var_name_end,
647 const char *var_val, const char *var_val_end);
648
free_all_replace()649 static void free_all_replace() {
650 free_replace();
651 free_replace_regex();
652 free_replace_column();
653 free_replace_numeric_round();
654 }
655
656 /*
657 To run tests via the async API, invoke mysqltest with --async-client.
658 */
659 class AsyncTimer {
660 public:
AsyncTimer(std::string label)661 explicit AsyncTimer(std::string label)
662 : label_(label), time_(std::chrono::system_clock::now()), start_(time_) {}
663
~AsyncTimer()664 ~AsyncTimer() {
665 auto now = std::chrono::system_clock::now();
666 auto delta = now - start_;
667 ulonglong MY_ATTRIBUTE((unused)) micros =
668 std::chrono::duration_cast<std::chrono::microseconds>(delta).count();
669 DBUG_PRINT("async_timing",
670 ("%s total micros: %llu", label_.c_str(), micros));
671 }
672
check()673 void check() {
674 auto now = std::chrono::system_clock::now();
675 auto delta = now - time_;
676 time_ = now;
677 ulonglong MY_ATTRIBUTE((unused)) micros =
678 std::chrono::duration_cast<std::chrono::microseconds>(delta).count();
679 DBUG_PRINT("async_timing", ("%s op micros: %llu", label_.c_str(), micros));
680 }
681
682 private:
683 std::string label_;
684 std::chrono::system_clock::time_point time_;
685 std::chrono::system_clock::time_point start_;
686 };
687
688 #ifdef _WIN32
689 /*
690 Check if any data is available in the socket to be read or written.
691 */
socket_event_listen(my_socket fd)692 static int socket_event_listen(my_socket fd) {
693 int result;
694 fd_set readfds, writefds, exceptfds;
695
696 FD_ZERO(&readfds);
697 FD_ZERO(&writefds);
698 FD_ZERO(&exceptfds);
699
700 FD_SET(fd, &exceptfds);
701 FD_SET(fd, &readfds);
702 FD_SET(fd, &writefds);
703
704 result = select((int)(fd + 1), &readfds, &writefds, &exceptfds, NULL);
705 if (result < 0) {
706 DWORD error_code = WSAGetLastError();
707 verbose_msg("Cannot determine the status due to error :%lu\n", error_code);
708 }
709 return result;
710 }
711 #else
socket_event_listen(my_socket fd)712 static int socket_event_listen(my_socket fd) {
713 int result;
714 pollfd pfd;
715 pfd.fd = fd;
716 /*
717 Listen to both in/out because SSL can perform reads during writes (and
718 vice versa).
719 */
720 pfd.events = POLLIN | POLLOUT;
721 result = poll(&pfd, 1, -1);
722 if (result < 0) {
723 perror("poll");
724 }
725 return result;
726 }
727 #endif
728
729 /*
730 Below async_mysql_*_wrapper functions are used to measure how much time
731 each nonblocking call spends before completing the operations.
732 i*/
async_mysql_fetch_row_wrapper(MYSQL_RES * res)733 static MYSQL_ROW async_mysql_fetch_row_wrapper(MYSQL_RES *res) {
734 MYSQL_ROW row;
735 MYSQL *mysql = res->handle;
736 AsyncTimer t(__func__);
737 while (mysql_fetch_row_nonblocking(res, &row) == NET_ASYNC_NOT_READY) {
738 t.check();
739 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
740 if (result == -1) return nullptr;
741 }
742 return row;
743 }
744
async_mysql_store_result_wrapper(MYSQL * mysql)745 static MYSQL_RES *async_mysql_store_result_wrapper(MYSQL *mysql) {
746 MYSQL_RES *mysql_result;
747 AsyncTimer t(__func__);
748 while (mysql_store_result_nonblocking(mysql, &mysql_result) ==
749 NET_ASYNC_NOT_READY) {
750 t.check();
751 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
752 if (result == -1) return nullptr;
753 }
754 return mysql_result;
755 }
756
async_mysql_real_query_wrapper(MYSQL * mysql,const char * query,ulong length)757 static int async_mysql_real_query_wrapper(MYSQL *mysql, const char *query,
758 ulong length) {
759 net_async_status status;
760 AsyncTimer t(__func__);
761 while ((status = mysql_real_query_nonblocking(mysql, query, length)) ==
762 NET_ASYNC_NOT_READY) {
763 t.check();
764 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
765 if (result == -1) return 1;
766 }
767 if (status == NET_ASYNC_ERROR) {
768 return 1;
769 }
770 return 0;
771 }
772
async_mysql_send_query_wrapper(MYSQL * mysql,const char * query,ulong length)773 static int async_mysql_send_query_wrapper(MYSQL *mysql, const char *query,
774 ulong length) {
775 net_async_status status;
776 ASYNC_DATA(mysql)->async_query_length = length;
777 ASYNC_DATA(mysql)->async_query_state = QUERY_SENDING;
778
779 AsyncTimer t(__func__);
780 while ((status = mysql_send_query_nonblocking(mysql, query, length)) ==
781 NET_ASYNC_NOT_READY) {
782 t.check();
783 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
784 if (result == -1) return 1;
785 }
786 if (status == NET_ASYNC_ERROR) {
787 return 1;
788 }
789 return 0;
790 }
791
async_mysql_read_query_result_wrapper(MYSQL * mysql)792 static bool async_mysql_read_query_result_wrapper(MYSQL *mysql) {
793 net_async_status status;
794 AsyncTimer t(__func__);
795 while ((status = (*mysql->methods->read_query_result_nonblocking)(mysql)) ==
796 NET_ASYNC_NOT_READY) {
797 t.check();
798 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
799 if (result == -1) return true;
800 }
801 if (status == NET_ASYNC_ERROR) {
802 return true;
803 }
804 return false;
805 }
806
async_mysql_next_result_wrapper(MYSQL * mysql)807 static int async_mysql_next_result_wrapper(MYSQL *mysql) {
808 net_async_status status;
809 AsyncTimer t(__func__);
810 while ((status = mysql_next_result_nonblocking(mysql)) ==
811 NET_ASYNC_NOT_READY) {
812 t.check();
813 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
814 if (result == -1) return 1;
815 }
816 if (status == NET_ASYNC_ERROR)
817 return 1;
818 else if (status == NET_ASYNC_COMPLETE_NO_MORE_RESULTS)
819 return -1;
820 else
821 return 0;
822 }
823
async_mysql_real_connect_wrapper(MYSQL * mysql,const char * host,const char * user,const char * passwd,const char * db,uint port,const char * unix_socket,ulong client_flag)824 static MYSQL *async_mysql_real_connect_wrapper(
825 MYSQL *mysql, const char *host, const char *user, const char *passwd,
826 const char *db, uint port, const char *unix_socket, ulong client_flag) {
827 net_async_status status;
828 AsyncTimer t(__func__);
829
830 while ((status = mysql_real_connect_nonblocking(
831 mysql, host, user, passwd, db, port, unix_socket, client_flag)) ==
832 NET_ASYNC_NOT_READY) {
833 t.check();
834 }
835 if (status == NET_ASYNC_ERROR)
836 return nullptr;
837 else
838 return mysql;
839 }
840
async_mysql_query_wrapper(MYSQL * mysql,const char * query)841 static int async_mysql_query_wrapper(MYSQL *mysql, const char *query) {
842 net_async_status status;
843 AsyncTimer t(__func__);
844 while ((status = mysql_real_query_nonblocking(mysql, query, strlen(query))) ==
845 NET_ASYNC_NOT_READY) {
846 t.check();
847 int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
848 if (result == -1) return 1;
849 }
850 if (status == NET_ASYNC_ERROR) {
851 return 1;
852 }
853 return 0;
854 }
855
async_mysql_free_result_wrapper(MYSQL_RES * result)856 static void async_mysql_free_result_wrapper(MYSQL_RES *result) {
857 AsyncTimer t(__func__);
858 while (mysql_free_result_nonblocking(result) == NET_ASYNC_NOT_READY) {
859 t.check();
860 MYSQL *mysql = result->handle;
861 int listen_result = socket_event_listen(mysql_get_socket_descriptor(mysql));
862 if (listen_result == -1) return;
863 }
864 return;
865 }
866
867 /*
868 Below are the wrapper functions which are defined on top of standard C APIs
869 to make a decision on whether to call blocking or non blocking API based on
870 --async-client option is set or not.
871 */
mysql_fetch_row_wrapper(MYSQL_RES * res)872 static MYSQL_ROW mysql_fetch_row_wrapper(MYSQL_RES *res) {
873 if (enable_async_client)
874 return async_mysql_fetch_row_wrapper(res);
875 else
876 return mysql_fetch_row(res);
877 }
878
mysql_store_result_wrapper(MYSQL * mysql)879 static MYSQL_RES *mysql_store_result_wrapper(MYSQL *mysql) {
880 if (enable_async_client)
881 return async_mysql_store_result_wrapper(mysql);
882 else
883 return mysql_store_result(mysql);
884 }
885
mysql_real_query_wrapper(MYSQL * mysql,const char * query,ulong length)886 static int mysql_real_query_wrapper(MYSQL *mysql, const char *query,
887 ulong length) {
888 if (enable_async_client)
889 return async_mysql_real_query_wrapper(mysql, query, length);
890 else
891 return mysql_real_query(mysql, query, length);
892 }
893
mysql_send_query_wrapper(MYSQL * mysql,const char * query,ulong length)894 static int mysql_send_query_wrapper(MYSQL *mysql, const char *query,
895 ulong length) {
896 if (enable_async_client)
897 return async_mysql_send_query_wrapper(mysql, query, length);
898 else
899 return mysql_send_query(mysql, query, length);
900 }
901
mysql_read_query_result_wrapper(MYSQL * mysql)902 static bool mysql_read_query_result_wrapper(MYSQL *mysql) {
903 bool ret;
904 if (enable_async_client)
905 ret = async_mysql_read_query_result_wrapper(mysql);
906 else
907 ret = mysql_read_query_result(mysql);
908 return ret;
909 }
910
mysql_query_wrapper(MYSQL * mysql,const char * query)911 static int mysql_query_wrapper(MYSQL *mysql, const char *query) {
912 if (enable_async_client)
913 return async_mysql_query_wrapper(mysql, query);
914 else
915 return mysql_query(mysql, query);
916 }
917
mysql_next_result_wrapper(MYSQL * mysql)918 static int mysql_next_result_wrapper(MYSQL *mysql) {
919 if (enable_async_client)
920 return async_mysql_next_result_wrapper(mysql);
921 else
922 return mysql_next_result(mysql);
923 }
924
mysql_real_connect_wrapper(MYSQL * mysql,const char * host,const char * user,const char * passwd,const char * db,uint port,const char * unix_socket,ulong client_flag)925 static MYSQL *mysql_real_connect_wrapper(MYSQL *mysql, const char *host,
926 const char *user, const char *passwd,
927 const char *db, uint port,
928 const char *unix_socket,
929 ulong client_flag) {
930 if (enable_async_client)
931 return async_mysql_real_connect_wrapper(mysql, host, user, passwd, db, port,
932 unix_socket, client_flag);
933 else
934 return mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket,
935 client_flag);
936 }
937
mysql_free_result_wrapper(MYSQL_RES * result)938 static void mysql_free_result_wrapper(MYSQL_RES *result) {
939 if (enable_async_client)
940 return async_mysql_free_result_wrapper(result);
941 else
942 return mysql_free_result(result);
943 }
944
945 /* async client test code (end) */
946
947 void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len);
948 void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val);
949 void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val);
950 void dynstr_append_sorted(DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_input,
951 int start_sort_column);
952
953 void revert_properties();
954
do_eval(DYNAMIC_STRING * query_eval,const char * query,const char * query_end,bool pass_through_escape_chars)955 void do_eval(DYNAMIC_STRING *query_eval, const char *query,
956 const char *query_end, bool pass_through_escape_chars) {
957 const char *p;
958 char c, next_c;
959 int escaped = 0;
960 VAR *v;
961 DBUG_TRACE;
962
963 for (p = query; (c = *p) && p < query_end; ++p) {
964 next_c = *(p + 1);
965 switch (c) {
966 case '$':
967 if (escaped ||
968 // a JSON path expression
969 next_c == '.' || next_c == '[' || next_c == '\'' || next_c == '"') {
970 escaped = 0;
971 dynstr_append_mem(query_eval, p, 1);
972 } else {
973 if (!(v = var_get(p, &p, false, false))) die("Bad variable in eval");
974 dynstr_append_mem(query_eval, v->str_val, v->str_val_len);
975 }
976 break;
977 case '\\':
978 if (escaped) {
979 escaped = 0;
980 dynstr_append_mem(query_eval, p, 1);
981 } else if (next_c == '\\' || next_c == '$' || next_c == '"') {
982 /* Set escaped only if next char is \, " or $ */
983 escaped = 1;
984
985 if (pass_through_escape_chars) {
986 /* The escape char should be added to the output string. */
987 dynstr_append_mem(query_eval, p, 1);
988 }
989 } else
990 dynstr_append_mem(query_eval, p, 1);
991 break;
992 default:
993 escaped = 0;
994 dynstr_append_mem(query_eval, p, 1);
995 break;
996 }
997 }
998 #ifdef _WIN32
999 fix_win_paths(query_eval->str, query_eval->length);
1000 #endif
1001 }
1002
1003 /*
1004 Run query and dump the result to stderr in vertical format
1005
1006 NOTE! This function should be safe to call when an error
1007 has occurred and thus any further errors will be ignored(although logged)
1008
1009 SYNOPSIS
1010 show_query
1011 mysql - connection to use
1012 query - query to run
1013
1014 */
1015
show_query(MYSQL * mysql,const char * query)1016 static void show_query(MYSQL *mysql, const char *query) {
1017 MYSQL_RES *res;
1018 DBUG_TRACE;
1019
1020 if (!mysql) return;
1021
1022 if (mysql_query_wrapper(mysql, query)) {
1023 log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql),
1024 mysql_error(mysql));
1025 return;
1026 }
1027
1028 if ((res = mysql_store_result_wrapper(mysql)) == nullptr) {
1029 /* No result set returned */
1030 return;
1031 }
1032
1033 {
1034 MYSQL_ROW row;
1035 unsigned int i;
1036 unsigned int row_num = 0;
1037 unsigned int num_fields = mysql_num_fields(res);
1038 MYSQL_FIELD *fields = mysql_fetch_fields(res);
1039
1040 fprintf(stderr, "=== %s ===\n", query);
1041 while ((row = mysql_fetch_row_wrapper(res))) {
1042 unsigned long *lengths = mysql_fetch_lengths(res);
1043 row_num++;
1044
1045 fprintf(stderr, "---- %d. ----\n", row_num);
1046 for (i = 0; i < num_fields; i++) {
1047 /* looks ugly , but put here to convince parfait */
1048 assert(lengths);
1049 fprintf(stderr, "%s\t%.*s\n", fields[i].name, (int)lengths[i],
1050 row[i] ? row[i] : "NULL");
1051 }
1052 }
1053 for (i = 0; i < std::strlen(query) + 8; i++) fprintf(stderr, "=");
1054 fprintf(stderr, "\n\n");
1055 }
1056 mysql_free_result_wrapper(res);
1057 }
1058
1059 /*
1060 Show any warnings just before the error. Since the last error
1061 is added to the warning stack, only print @@warning_count-1 warnings.
1062
1063 NOTE! This function should be safe to call when an error
1064 has occurred and this any further errors will be ignored(although logged)
1065
1066 SYNOPSIS
1067 show_warnings_before_error
1068 mysql - connection to use
1069
1070 */
1071
show_warnings_before_error(MYSQL * mysql)1072 static void show_warnings_before_error(MYSQL *mysql) {
1073 MYSQL_RES *res;
1074 const char *query = "SHOW WARNINGS";
1075 DBUG_TRACE;
1076
1077 if (!mysql) return;
1078
1079 if (mysql_query_wrapper(mysql, query)) {
1080 log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql),
1081 mysql_error(mysql));
1082 return;
1083 }
1084
1085 if ((res = mysql_store_result_wrapper(mysql)) == nullptr) {
1086 /* No result set returned */
1087 return;
1088 }
1089
1090 if (mysql_num_rows(res) <= 1) {
1091 /* Don't display the last row, it's "last error" */
1092 } else {
1093 MYSQL_ROW row;
1094 unsigned int row_num = 0;
1095 unsigned int num_fields = mysql_num_fields(res);
1096
1097 fprintf(stderr, "\nWarnings from just before the error:\n");
1098 while ((row = mysql_fetch_row_wrapper(res))) {
1099 unsigned int i;
1100 unsigned long *lengths = mysql_fetch_lengths(res);
1101
1102 if (++row_num >= mysql_num_rows(res)) {
1103 /* Don't display the last row, it's "last error" */
1104 break;
1105 }
1106
1107 for (i = 0; i < num_fields; i++) {
1108 /* looks ugly , but put here to convince parfait */
1109 assert(lengths);
1110 fprintf(stderr, "%.*s ", (int)lengths[i], row[i] ? row[i] : "NULL");
1111 }
1112 fprintf(stderr, "\n");
1113 }
1114 }
1115 mysql_free_result_wrapper(res);
1116 }
1117
1118 enum arg_type { ARG_STRING, ARG_REST };
1119
1120 struct command_arg {
1121 const char *argname; /* Name of argument */
1122 enum arg_type type; /* Type of argument */
1123 bool required; /* Argument required */
1124 DYNAMIC_STRING *ds; /* Storage for argument */
1125 const char *description; /* Description of the argument */
1126 };
1127
check_command_args(struct st_command * command,char * arguments,const struct command_arg * args,int num_args,const char delimiter_arg)1128 static void check_command_args(struct st_command *command, char *arguments,
1129 const struct command_arg *args, int num_args,
1130 const char delimiter_arg) {
1131 int i;
1132 char *ptr = arguments;
1133 const char *start;
1134 DBUG_TRACE;
1135 DBUG_PRINT("enter", ("num_args: %d", num_args));
1136
1137 for (i = 0; i < num_args; i++) {
1138 const struct command_arg *arg = &args[i];
1139 char delimiter;
1140
1141 switch (arg->type) {
1142 /* A string */
1143 case ARG_STRING:
1144 /* Skip leading spaces */
1145 while (*ptr && *ptr == ' ') ptr++;
1146 start = ptr;
1147 delimiter = delimiter_arg;
1148 /* If start of arg is ' ` or " search to matching quote end instead */
1149 if (*ptr && strchr("'`\"", *ptr)) {
1150 delimiter = *ptr;
1151 start = ++ptr;
1152 }
1153 /* Find end of arg, terminated by "delimiter" */
1154 while (*ptr && *ptr != delimiter) ptr++;
1155 if (ptr > start) {
1156 init_dynamic_string(arg->ds, nullptr, ptr - start, 32);
1157 do_eval(arg->ds, start, ptr, false);
1158 } else {
1159 /* Empty string */
1160 init_dynamic_string(arg->ds, "", 0, 0);
1161 }
1162 /* Find real end of arg, terminated by "delimiter_arg" */
1163 /* This will do nothing if arg was not closed by quotes */
1164 while (*ptr && *ptr != delimiter_arg) ptr++;
1165
1166 command->last_argument = ptr;
1167
1168 /* Step past the delimiter */
1169 if (*ptr && *ptr == delimiter_arg) ptr++;
1170 DBUG_PRINT("info", ("val: %s", arg->ds->str));
1171 break;
1172
1173 /* Rest of line */
1174 case ARG_REST:
1175 start = ptr;
1176 init_dynamic_string(arg->ds, nullptr, command->query_len, 256);
1177 do_eval(arg->ds, start, command->end, false);
1178 command->last_argument = command->end;
1179 DBUG_PRINT("info", ("val: %s", arg->ds->str));
1180 break;
1181
1182 default:
1183 DBUG_ASSERT("Unknown argument type");
1184 break;
1185 }
1186
1187 /* Check required arg */
1188 if (arg->ds->length == 0 && arg->required)
1189 die("Missing required argument '%s' to command '%.*s'", arg->argname,
1190 static_cast<int>(command->first_word_len), command->query);
1191 }
1192 /* Check for too many arguments passed */
1193 ptr = command->last_argument;
1194 while (ptr <= command->end && *ptr != '#') {
1195 if (*ptr && *ptr != ' ')
1196 die("Extra argument '%s' passed to '%.*s'", ptr,
1197 static_cast<int>(command->first_word_len), command->query);
1198 ptr++;
1199 }
1200 }
1201
1202 /// Check whether given error is in list of expected errors.
1203 ///
1204 /// @param command Pointer to the st_command structure which holds the
1205 /// arguments and information for the command.
1206 /// @param err_errno Error number of the error that actually occurred.
1207 /// @param err_sqlstate SQLSTATE that was thrown, or NULL for impossible
1208 /// (file-ops, diff, etc.)
1209 ///
1210 /// @retval -1 if the given error is not in the list, index in the
1211 /// list of expected errors otherwise.
1212 ///
1213 /// @note
1214 /// If caller needs to know whether the list was empty, they should
1215 /// check the value of expected_errors.count.
match_expected_error(struct st_command * command,std::uint32_t err_errno,const char * err_sqlstate)1216 static int match_expected_error(struct st_command *command,
1217 std::uint32_t err_errno,
1218 const char *err_sqlstate) {
1219 std::uint8_t index = 0;
1220
1221 // Iterator for list/vector of expected errors
1222 std::vector<std::unique_ptr<Error>>::iterator error =
1223 expected_errors->begin();
1224
1225 // Iterate over list of expected errors
1226 for (; error != expected_errors->end(); error++) {
1227 if ((*error)->type() == ERR_ERRNO) {
1228 // Error type is ERR_ERRNO
1229 if ((*error)->error_code() == err_errno) return index;
1230 } else if ((*error)->type() == ERR_SQLSTATE) {
1231 // Error type is ERR_SQLSTATE. NULL is quite likely, but not in
1232 // conjunction with a SQL-state expect.
1233 if (unlikely(err_sqlstate == nullptr)) {
1234 die("Expecting a SQLSTATE (%s) from query '%s' which cannot produce "
1235 "one.",
1236 (*error)->sqlstate(), command->query);
1237 }
1238
1239 if (!std::strncmp((*error)->sqlstate(), err_sqlstate, SQLSTATE_LENGTH))
1240 return index;
1241 }
1242
1243 index++;
1244 }
1245
1246 return -1;
1247 }
1248
1249 /// Handle errors which occurred during execution of a query.
1250 ///
1251 /// @param command Pointer to the st_command structure which holds the
1252 /// arguments and information for the command.
1253 /// @param err_errno Error number
1254 /// @param err_error Error message
1255 /// @param err_sqlstate SQLSTATE that was thrown
1256 /// @param ds Dynamic string to store the result.
1257 ///
1258 /// @note
1259 /// If there is an unexpected error, this function will abort mysqltest
1260 /// immediately.
handle_error(struct st_command * command,std::uint32_t err_errno,const char * err_error,const char * err_sqlstate,DYNAMIC_STRING * ds)1261 void handle_error(struct st_command *command, std::uint32_t err_errno,
1262 const char *err_error, const char *err_sqlstate,
1263 DYNAMIC_STRING *ds) {
1264 DBUG_TRACE;
1265
1266 if (command->abort_on_error)
1267 die("Query '%s' failed.\nERROR %d (%s): %s", command->query, err_errno,
1268 err_sqlstate, err_error);
1269
1270 DBUG_PRINT("info", ("Expected errors count: %zu", expected_errors->count()));
1271
1272 int i = match_expected_error(command, err_errno, err_sqlstate);
1273
1274 if (i >= 0) {
1275 if (!disable_result_log) {
1276 if (expected_errors->count() == 1) {
1277 // Only log error if there is one possible error
1278 dynstr_append_mem(ds, "ERROR ", 6);
1279 replace_dynstr_append(ds, err_sqlstate);
1280 dynstr_append_mem(ds, ": ", 2);
1281 replace_dynstr_append(ds, err_error);
1282 dynstr_append_mem(ds, "\n", 1);
1283 }
1284 // Don't log error if we may not get an error
1285 else if (expected_errors->type() == ERR_SQLSTATE ||
1286 (expected_errors->type() == ERR_ERRNO &&
1287 expected_errors->error_code() != 0))
1288 dynstr_append(ds, "Got one of the listed errors\n");
1289 }
1290
1291 revert_properties();
1292 return;
1293 }
1294
1295 DBUG_PRINT("info",
1296 ("i: %d Expected errors count: %zu", i, expected_errors->count()));
1297
1298 if (!disable_result_log) {
1299 dynstr_append_mem(ds, "ERROR ", 6);
1300 replace_dynstr_append(ds, err_sqlstate);
1301 dynstr_append_mem(ds, ": ", 2);
1302 replace_dynstr_append(ds, err_error);
1303 dynstr_append_mem(ds, "\n", 1);
1304 }
1305
1306 if (expected_errors->count()) {
1307 if (expected_errors->count() == 1) {
1308 die("Query '%s' failed with wrong error %d: '%s', should have failed "
1309 "with error '%s'.",
1310 command->query, err_errno, err_error,
1311 expected_errors->error_list().c_str());
1312 } else {
1313 die("Query '%s' failed with wrong error %d: '%s', should have failed "
1314 "with any of '%s' errors.",
1315 command->query, err_errno, err_error,
1316 expected_errors->error_list().c_str());
1317 }
1318 }
1319
1320 revert_properties();
1321 }
1322
1323 /// Handle absence of errors after execution.
1324 ///
1325 /// Abort test run if the query succeeds and was expected to fail with
1326 /// an error.
1327 ///
1328 /// @param command Pointer to the st_command structure which holds the
1329 /// arguments and information for the command.
handle_no_error(struct st_command * command)1330 void handle_no_error(struct st_command *command) {
1331 DBUG_TRACE;
1332
1333 if (expected_errors->count()) {
1334 int index = match_expected_error(command, 0, "00000");
1335 if (index == -1) {
1336 if (expected_errors->count() == 1) {
1337 die("Query '%s' succeeded, should have failed with error '%s'",
1338 command->query, expected_errors->error_list().c_str());
1339 } else {
1340 die("Query '%s' succeeded, should have failed with any of '%s' errors.",
1341 command->query, expected_errors->error_list().c_str());
1342 }
1343 }
1344 }
1345 }
1346
1347 /// Save error code returned by a mysqltest command in '$__error'
1348 /// variable.
1349 ///
1350 /// @param error Error code
save_error_code(std::uint32_t error)1351 static void save_error_code(std::uint32_t error) {
1352 char error_value[10];
1353 size_t error_length = std::snprintf(error_value, 10, "%u", error);
1354 error_value[error_length > 9 ? 9 : error_length] = '0';
1355 const char *var_name = "__error";
1356 var_set(var_name, var_name + 7, error_value, error_value + error_length);
1357 }
1358
1359 /// Handle errors which occurred during execution of mysqltest commands
1360 /// like 'move_file', 'remove_file' etc which are used to perform file
1361 /// system operations.
1362 ///
1363 /// @param command Pointer to the st_command structure which holds the
1364 /// arguments and information for the command.
1365 /// @param error Error number
1366 ///
1367 /// @note
1368 /// If there is an unexpected error, this function will abort mysqltest
1369 /// client immediately.
handle_command_error(struct st_command * command,std::uint32_t error)1370 static void handle_command_error(struct st_command *command,
1371 std::uint32_t error) {
1372 DBUG_TRACE;
1373 DBUG_PRINT("enter", ("error: %d", error));
1374
1375 if (error != 0 && command->abort_on_error) {
1376 die("Command \"%s\" failed with error %d. my_errno=%d.",
1377 command_names[command->type - 1], error, my_errno());
1378 }
1379
1380 if (expected_errors->count()) {
1381 int index = match_expected_error(command, error, nullptr);
1382 if (index == -1) {
1383 if (error != 0) {
1384 if (expected_errors->count() == 1) {
1385 die("Command \"%s\" failed with wrong error: %d, my_errno=%d. should "
1386 "have failed with error '%s'.",
1387 command_names[command->type - 1], error, my_errno(),
1388 expected_errors->error_list().c_str());
1389 } else {
1390 die("Command \"%s\" failed with wrong error: %d, my_errno=%d. should "
1391 "have failed with any of '%s' errors.",
1392 command_names[command->type - 1], error, my_errno(),
1393 expected_errors->error_list().c_str());
1394 }
1395 } else {
1396 // Command succeeded, should have failed with an error
1397 if (expected_errors->count() == 1) {
1398 die("Command \"%s\" succeeded, should have failed with error '%s'.",
1399 command_names[command->type - 1],
1400 expected_errors->error_list().c_str());
1401 } else {
1402 die("Command \"%s\" succeeded, should have failed with any of '%s' "
1403 "errors.",
1404 command_names[command->type - 1],
1405 expected_errors->error_list().c_str());
1406 }
1407 }
1408 }
1409 }
1410
1411 // Save the error code
1412 save_error_code(error);
1413
1414 revert_properties();
1415 }
1416
close_connections()1417 static void close_connections() {
1418 DBUG_TRACE;
1419 for (--next_con; next_con >= connections; --next_con) {
1420 if (next_con->stmt) mysql_stmt_close(next_con->stmt);
1421 next_con->stmt = nullptr;
1422 mysql_close(&next_con->mysql);
1423 if (next_con->util_mysql) mysql_close(next_con->util_mysql);
1424 my_free(next_con->name);
1425 }
1426 my_free(connections);
1427 }
1428
close_statements()1429 static void close_statements() {
1430 struct st_connection *con;
1431 DBUG_TRACE;
1432 for (con = connections; con < next_con; con++) {
1433 if (con->stmt) mysql_stmt_close(con->stmt);
1434 con->stmt = nullptr;
1435 }
1436 }
1437
close_files()1438 static void close_files() {
1439 DBUG_TRACE;
1440 for (; cur_file >= file_stack; cur_file--) {
1441 if (cur_file->file && cur_file->file != stdin) {
1442 DBUG_PRINT("info", ("closing file: %s", cur_file->file_name));
1443 fclose(cur_file->file);
1444 }
1445 my_free(cur_file->file_name);
1446 cur_file->file_name = nullptr;
1447 }
1448 }
1449
free_used_memory()1450 static void free_used_memory() {
1451 // Delete the exptected errors pointer
1452 delete expected_errors;
1453
1454 // Delete disabled and enabled warning list
1455 delete disabled_warnings;
1456 delete enabled_warnings;
1457
1458 if (connections) close_connections();
1459 close_files();
1460 delete var_hash;
1461 var_hash = nullptr;
1462
1463 struct st_command **q;
1464 for (q = q_lines->begin(); q != q_lines->end(); ++q) {
1465 my_free((*q)->query_buf);
1466 if ((*q)->content.str) dynstr_free(&(*q)->content);
1467 my_free((*q));
1468 }
1469
1470 for (size_t i = 0; i < 10; i++) {
1471 if (var_reg[i].alloced_len) my_free(var_reg[i].str_val);
1472 }
1473
1474 delete q_lines;
1475 dynstr_free(&ds_res);
1476 dynstr_free(&ds_result);
1477 if (ds_warn) dynstr_free(ds_warn);
1478 free_all_replace();
1479 my_free(opt_pass);
1480 #ifdef _WIN32
1481 free_win_path_patterns();
1482 #endif
1483
1484 // Only call mysql_server_end if mysql_server_init has been called.
1485 if (server_initialized) mysql_server_end();
1486
1487 // Don't use DBUG after mysql_server_end()
1488 return;
1489 }
1490
cleanup_and_exit(int exit_code)1491 static void cleanup_and_exit(int exit_code) {
1492 if (opt_offload_count_file) {
1493 // Check if the current connection is active, if not create one.
1494 if (cur_con->mysql.net.vio == nullptr) {
1495 mysql_real_connect(&cur_con->mysql, opt_host, opt_user, opt_pass, opt_db,
1496 opt_port, unix_sock,
1497 CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS);
1498 }
1499
1500 // Save the final value of secondary engine execution status.
1501 if (secondary_engine->offload_count(&cur_con->mysql, "after"))
1502 exit_code = 1;
1503 secondary_engine->report_offload_count(opt_offload_count_file);
1504 }
1505
1506 free_used_memory();
1507 my_end(my_end_arg);
1508
1509 if (!silent) {
1510 switch (exit_code) {
1511 case 1:
1512 printf("not ok\n");
1513 break;
1514 case 0:
1515 printf("ok\n");
1516 break;
1517 case 62:
1518 printf("skipped\n");
1519 break;
1520 default:
1521 printf("unknown exit code: %d\n", exit_code);
1522 DBUG_ASSERT(0);
1523 }
1524 }
1525
1526 // exit() appears to be not 100% reliable on Windows under some conditions.
1527 #ifdef _WIN32
1528 if (opt_safe_process_pid) {
1529 // Close the stack trace request event handle
1530 if (stacktrace_request_event != NULL) CloseHandle(stacktrace_request_event);
1531
1532 // Detach or stop the thread waiting for stack trace event to occur.
1533 if (wait_for_stacktrace_request_event_thread.joinable())
1534 wait_for_stacktrace_request_event_thread.detach();
1535
1536 // Close the thread handle
1537 if (!CloseHandle(mysqltest_thread))
1538 die("CloseHandle failed, err = %d.\n", GetLastError());
1539 }
1540
1541 fflush(stdout);
1542 fflush(stderr);
1543 _exit(exit_code);
1544 #else
1545 exit(exit_code);
1546 #endif
1547 }
1548
print_file_stack()1549 static void print_file_stack() {
1550 fprintf(stderr, "file %s: %d\n", cur_file->file_name, cur_file->lineno);
1551
1552 struct st_test_file *err_file;
1553 for (err_file = cur_file - 1; err_file >= file_stack; err_file--) {
1554 fprintf(stderr, "included from %s: %d\n", err_file->file_name,
1555 err_file->lineno);
1556 }
1557 }
1558
die(const char * fmt,...)1559 void die(const char *fmt, ...) {
1560 static int dying = 0;
1561 va_list args;
1562 DBUG_PRINT("enter", ("start_lineno: %d", start_lineno));
1563
1564 // Protect against dying twice first time 'die' is called, try to
1565 // write log files second time, just exit.
1566 if (dying) cleanup_and_exit(1);
1567 dying = 1;
1568
1569 // Print the error message
1570 fprintf(stderr, "mysqltest: ");
1571
1572 if (start_lineno > 0) fprintf(stderr, "At line %u: ", start_lineno);
1573
1574 if (fmt) {
1575 va_start(args, fmt);
1576 vfprintf(stderr, fmt, args);
1577 fprintf(stderr, "\n");
1578 va_end(args);
1579 } else
1580 fprintf(stderr, "Unknown error");
1581
1582 // Print the file stack
1583 if (cur_file && cur_file != file_stack) {
1584 fprintf(stderr, "In included ");
1585 print_file_stack();
1586 }
1587
1588 fflush(stderr);
1589
1590 if (result_file_name) log_file.show_tail(opt_tail_lines);
1591
1592 // Help debugging by displaying any warnings that might have
1593 // been produced prior to the error.
1594 if (cur_con && !cur_con->pending) show_warnings_before_error(&cur_con->mysql);
1595
1596 cleanup_and_exit(1);
1597 }
1598
abort_not_supported_test(const char * fmt,...)1599 void abort_not_supported_test(const char *fmt, ...) {
1600 va_list args;
1601 DBUG_TRACE;
1602
1603 /* Print include filestack */
1604 fprintf(stderr, "The test '%s' is not supported by this installation\n",
1605 file_stack->file_name);
1606 fprintf(stderr, "Detected in ");
1607 print_file_stack();
1608
1609 /* Print error message */
1610 va_start(args, fmt);
1611 if (fmt) {
1612 fprintf(stderr, "reason: ");
1613 vfprintf(stderr, fmt, args);
1614 fprintf(stderr, "\n");
1615 fflush(stderr);
1616 }
1617 va_end(args);
1618
1619 cleanup_and_exit(62);
1620 }
1621
verbose_msg(const char * fmt,...)1622 void verbose_msg(const char *fmt, ...) {
1623 va_list args;
1624 DBUG_TRACE;
1625 if (!verbose) return;
1626
1627 va_start(args, fmt);
1628 fprintf(stderr, "mysqltest: ");
1629 if (cur_file && cur_file != file_stack)
1630 fprintf(stderr, "In included file \"%s\": ", cur_file->file_name);
1631 if (start_lineno != 0) fprintf(stderr, "At line %u: ", start_lineno);
1632 vfprintf(stderr, fmt, args);
1633 fprintf(stderr, "\n");
1634 va_end(args);
1635 }
1636
log_msg(const char * fmt,...)1637 void log_msg(const char *fmt, ...) {
1638 va_list args;
1639 char buff[1024];
1640 size_t len;
1641 DBUG_TRACE;
1642
1643 va_start(args, fmt);
1644 len = vsnprintf(buff, sizeof(buff) - 1, fmt, args);
1645 va_end(args);
1646
1647 dynstr_append_mem(&ds_res, buff, len);
1648 dynstr_append(&ds_res, "\n");
1649 }
1650
1651 /*
1652 Read a file and append it to ds
1653
1654 SYNOPSIS
1655 cat_file
1656 ds - pointer to dynamic string where to add the files content
1657 filename - name of the file to read
1658
1659 */
1660
cat_file(DYNAMIC_STRING * ds,const char * filename)1661 static int cat_file(DYNAMIC_STRING *ds, const char *filename) {
1662 int fd;
1663 size_t len;
1664 char buff[512];
1665 bool dangling_cr = false;
1666
1667 if ((fd = my_open(filename, O_RDONLY, MYF(0))) < 0) return 1;
1668
1669 std::string file_content;
1670
1671 while ((len = my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0))) > 0) {
1672 char *p = buff, *start = buff;
1673 if (dangling_cr) {
1674 if (*p != '\n') file_content.append("\r");
1675 dangling_cr = false;
1676 }
1677 while (p < buff + len) {
1678 /* Convert cr/lf to lf */
1679 if (*p == '\r' && *(p + 1) && *(p + 1) == '\n') {
1680 /* Add fake newline instead of cr and output the line */
1681 *p = '\n';
1682 p++; /* Step past the "fake" newline */
1683 file_content.append(start, p - start);
1684 p++; /* Step past the "fake" newline */
1685 start = p;
1686 } else
1687 p++;
1688 }
1689 if (*(p - 1) == '\r' && len == 512) dangling_cr = true;
1690 size_t buf_len;
1691 /* Add any characters that might be left */
1692 if (dangling_cr)
1693 buf_len = p - start - 1;
1694 else
1695 buf_len = p - start;
1696 file_content.append(start, buf_len);
1697 }
1698
1699 replace_dynstr_append(ds, file_content.c_str());
1700 my_close(fd, MYF(0));
1701
1702 return 0;
1703 }
1704
1705 /*
1706 Run the specified command with popen
1707
1708 SYNOPSIS
1709 run_command
1710 cmd - command to execute(should be properly quoted
1711 ds_res- pointer to dynamic string where to store the result
1712
1713 */
1714
run_command(char * cmd,DYNAMIC_STRING * ds_res)1715 static int run_command(char *cmd, DYNAMIC_STRING *ds_res) {
1716 char buf[512] = {0};
1717 FILE *res_file;
1718 int error;
1719
1720 if (!(res_file = popen(cmd, "r"))) die("popen(\"%s\", \"r\") failed", cmd);
1721
1722 while (fgets(buf, sizeof(buf), res_file)) {
1723 DBUG_PRINT("info", ("buf: %s", buf));
1724 if (ds_res) {
1725 /* Save the output of this command in the supplied string */
1726 dynstr_append(ds_res, buf);
1727 } else {
1728 /* Print it directly on screen */
1729 fprintf(stdout, "%s", buf);
1730 }
1731 }
1732
1733 error = pclose(res_file);
1734 return WEXITSTATUS(error);
1735 }
1736
1737 /*
1738 Run the specified tool with variable number of arguments
1739
1740 SYNOPSIS
1741 run_tool
1742 tool_path - the name of the tool to run
1743 ds_res - pointer to dynamic string where to store the result
1744 ... - variable number of arguments that will be properly
1745 quoted and appended after the tool's name
1746
1747 */
1748
run_tool(const char * tool_path,DYNAMIC_STRING * ds_res,...)1749 static int run_tool(const char *tool_path, DYNAMIC_STRING *ds_res, ...) {
1750 int ret;
1751 const char *arg;
1752 va_list args;
1753 DYNAMIC_STRING ds_cmdline;
1754
1755 DBUG_TRACE;
1756 DBUG_PRINT("enter", ("tool_path: %s", tool_path));
1757
1758 if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
1759 die("Out of memory");
1760
1761 dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS);
1762 dynstr_append(&ds_cmdline, " ");
1763
1764 va_start(args, ds_res);
1765
1766 while ((arg = va_arg(args, char *))) {
1767 /* Options should be os quoted */
1768 if (std::strncmp(arg, "--", 2) == 0)
1769 dynstr_append_os_quoted(&ds_cmdline, arg, NullS);
1770 else
1771 dynstr_append(&ds_cmdline, arg);
1772 dynstr_append(&ds_cmdline, " ");
1773 }
1774
1775 va_end(args);
1776
1777 #ifdef _WIN32
1778 dynstr_append(&ds_cmdline, "\"");
1779 #endif
1780
1781 DBUG_PRINT("info", ("Running: %s", ds_cmdline.str));
1782 ret = run_command(ds_cmdline.str, ds_res);
1783 DBUG_PRINT("exit", ("ret: %d", ret));
1784 dynstr_free(&ds_cmdline);
1785 return ret;
1786 }
1787
1788 /*
1789 Test if diff is present. This is needed on Windows systems
1790 as the OS returns 1 whether diff is successful or if it is
1791 not present.
1792
1793 We run diff -v and look for output in stdout.
1794 We don't redirect stderr to stdout to make for a simplified check
1795 Windows will output '"diff"' is not recognized... to stderr if it is
1796 not present.
1797 */
1798
1799 #ifdef _WIN32
1800
diff_check(const char * diff_name)1801 static int diff_check(const char *diff_name) {
1802 FILE *res_file;
1803 char buf[128];
1804 int have_diff = 0;
1805
1806 snprintf(buf, sizeof(buf), "%s -v", diff_name);
1807
1808 if (!(res_file = popen(buf, "r"))) die("popen(\"%s\", \"r\") failed", buf);
1809
1810 /* if diff is not present, nothing will be in stdout to increment have_diff */
1811 if (fgets(buf, sizeof(buf), res_file)) have_diff = 1;
1812
1813 pclose(res_file);
1814
1815 return have_diff;
1816 }
1817
1818 #endif
1819
1820 /// Show the diff of two files using the systems builtin diff
1821 /// command. If no such diff command exist, just dump the content
1822 /// of the two files and inform about how to get "diff"
1823 ///
1824 /// @param ds Pointer to dynamic string where to add the
1825 /// diff. If NULL, print the diff to stderr.
1826 /// @param filename1 Name of the first file
1827 /// @param filename2 Name of the second file
show_diff(DYNAMIC_STRING * ds,const char * filename1,const char * filename2)1828 static void show_diff(DYNAMIC_STRING *ds, const char *filename1,
1829 const char *filename2) {
1830 DYNAMIC_STRING ds_diff;
1831 if (init_dynamic_string(&ds_diff, "", 256, 256)) die("Out of memory");
1832
1833 const char *diff_name = nullptr;
1834
1835 // Determine if we have diff on Windows. If yes, then needs special
1836 // processing due to return values on that OS. This test is only done
1837 // on Windows since it's only needed there in order to correctly
1838 // detect non-availibility of 'diff', and the way it's implemented
1839 // does not work with default 'diff' on Solaris.
1840 #ifdef _WIN32
1841 if (diff_check("diff"))
1842 diff_name = "diff";
1843 else if (diff_check("mtrdiff"))
1844 diff_name = "mtrdiff";
1845 else
1846 diff_name = 0;
1847 #else
1848 // Otherwise always assume it's called diff
1849 diff_name = "diff";
1850 #endif
1851
1852 if (diff_name) {
1853 int exit_code = 0;
1854 // Use 'diff --color=always' to print the colored diff if it is enabled
1855 if (opt_colored_diff) {
1856 // Most "diff" tools return '> 1' if error
1857 exit_code = run_tool(diff_name, &ds_diff, "-u --color='always'",
1858 filename1, filename2, "2>&1", NULL);
1859
1860 if (exit_code > 1)
1861 die("Option '--colored-diff' is not supported on this machine. "
1862 "To get colored diff output, install GNU diffutils version "
1863 "3.4 or higher.");
1864 } else {
1865 // Colored diff is disabled, clear the diff string and try unified
1866 // diff with "diff -u".
1867 dynstr_set(&ds_diff, "");
1868 exit_code = run_tool(diff_name, &ds_diff, "-u", filename1, filename2,
1869 "2>&1", NULL);
1870
1871 if (exit_code > 1) {
1872 // Clear the diff string and fallback to context diff with "diff -c"
1873 dynstr_set(&ds_diff, "");
1874 exit_code = run_tool(diff_name, &ds_diff, "-c", filename1, filename2,
1875 "2>&1", NULL);
1876
1877 if (exit_code > 1) {
1878 // Clear the diff string and fallback to simple diff with "diff"
1879 dynstr_set(&ds_diff, "");
1880 exit_code =
1881 run_tool(diff_name, &ds_diff, filename1, filename2, "2>&1", NULL);
1882 if (exit_code > 1) diff_name = nullptr;
1883 }
1884 }
1885 }
1886 }
1887
1888 if (!diff_name) {
1889 // Fallback to dump both files to result file and inform
1890 // about installing "diff".
1891 dynstr_append(&ds_diff, "\n");
1892 dynstr_append(
1893 &ds_diff,
1894 "\n"
1895 "The two files differ but it was not possible to execute 'diff' in\n"
1896 "order to show only the difference. Instead the whole content of the\n"
1897 "two files was shown for you to diff manually.\n\n"
1898 "To get a better report you should install 'diff' on your system, "
1899 "which you\n"
1900 "for example can get from "
1901 "http://www.gnu.org/software/diffutils/diffutils.html\n"
1902 #ifdef _WIN32
1903 "or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n"
1904 #endif
1905 "\n");
1906
1907 dynstr_append(&ds_diff, " --- ");
1908 dynstr_append(&ds_diff, filename1);
1909 dynstr_append(&ds_diff, " >>>\n");
1910 cat_file(&ds_diff, filename1);
1911 dynstr_append(&ds_diff, "<<<\n --- ");
1912 dynstr_append(&ds_diff, filename1);
1913 dynstr_append(&ds_diff, " >>>\n");
1914 cat_file(&ds_diff, filename2);
1915 dynstr_append(&ds_diff, "<<<<\n");
1916 }
1917
1918 if (ds)
1919 // Add the diff to output
1920 dynstr_append_mem(ds, ds_diff.str, ds_diff.length);
1921 else
1922 // Print diff directly to stderr
1923 fprintf(stderr, "%s\n", ds_diff.str);
1924
1925 dynstr_free(&ds_diff);
1926 }
1927
1928 enum compare_files_result_enum {
1929 RESULT_OK = 0,
1930 RESULT_CONTENT_MISMATCH = 1,
1931 RESULT_LENGTH_MISMATCH = 2
1932 };
1933
1934 /*
1935 Compare two files, given a fd to the first file and
1936 name of the second file
1937
1938 SYNOPSIS
1939 compare_files2
1940 fd - Open file descriptor of the first file
1941 filename2 - Name of second file
1942
1943 RETURN VALUES
1944 According to the values in "compare_files_result_enum"
1945
1946 */
1947
compare_files2(File fd,const char * filename2)1948 static int compare_files2(File fd, const char *filename2) {
1949 int error = RESULT_OK;
1950 File fd2;
1951 size_t len, len2;
1952 char buff[512], buff2[512];
1953
1954 if ((fd2 = my_open(filename2, O_RDONLY, MYF(0))) < 0) {
1955 my_close(fd, MYF(0));
1956 die("Failed to open second file: '%s'", filename2);
1957 }
1958 while ((len = my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0))) > 0) {
1959 if ((len2 = my_read(fd2, (uchar *)&buff2, sizeof(buff2), MYF(0))) < len) {
1960 /* File 2 was smaller */
1961 error = RESULT_LENGTH_MISMATCH;
1962 break;
1963 }
1964 if (len2 > len) {
1965 /* File 1 was smaller */
1966 error = RESULT_LENGTH_MISMATCH;
1967 break;
1968 }
1969 if ((memcmp(buff, buff2, len))) {
1970 /* Content of this part differed */
1971 error = RESULT_CONTENT_MISMATCH;
1972 break;
1973 }
1974 }
1975 if (!error && my_read(fd2, (uchar *)&buff2, sizeof(buff2), MYF(0)) > 0) {
1976 /* File 1 was smaller */
1977 error = RESULT_LENGTH_MISMATCH;
1978 }
1979
1980 my_close(fd2, MYF(0));
1981
1982 return error;
1983 }
1984
1985 /*
1986 Compare two files, given their filenames
1987
1988 SYNOPSIS
1989 compare_files
1990 filename1 - Name of first file
1991 filename2 - Name of second file
1992
1993 RETURN VALUES
1994 See 'compare_files2'
1995
1996 */
1997
compare_files(const char * filename1,const char * filename2)1998 static int compare_files(const char *filename1, const char *filename2) {
1999 File fd;
2000 int error;
2001
2002 if ((fd = my_open(filename1, O_RDONLY, MYF(0))) < 0)
2003 die("Failed to open first file: '%s'", filename1);
2004
2005 error = compare_files2(fd, filename2);
2006
2007 my_close(fd, MYF(0));
2008
2009 return error;
2010 }
2011
2012 /*
2013 Check the content of log against result file
2014
2015 SYNOPSIS
2016 check_result
2017
2018 RETURN VALUES
2019 error - the function will not return
2020
2021 */
2022
check_result()2023 static void check_result() {
2024 const char *mess = "Result content mismatch\n";
2025
2026 DBUG_TRACE;
2027 DBUG_ASSERT(result_file_name);
2028 DBUG_PRINT("enter", ("result_file_name: %s", result_file_name));
2029
2030 /*
2031 Removing the unnecessary warning messages generated
2032 on GCOV platform.
2033 */
2034 #ifdef HAVE_GCOV
2035 char cmd[FN_REFLEN];
2036 strcpy(cmd, "sed -i '/gcda:Merge mismatch for function/d' ");
2037 std::strcat(cmd, log_file.file_name());
2038 system(cmd);
2039 #endif
2040
2041 switch (compare_files(log_file.file_name(), result_file_name)) {
2042 case RESULT_OK:
2043 break; /* ok */
2044 case RESULT_LENGTH_MISMATCH:
2045 mess = "Result length mismatch\n";
2046 /* Fallthrough */
2047 case RESULT_CONTENT_MISMATCH: {
2048 /*
2049 Result mismatched, dump results to .reject file
2050 and then show the diff
2051 */
2052 char reject_file[FN_REFLEN];
2053 size_t reject_length;
2054 dirname_part(reject_file, result_file_name, &reject_length);
2055
2056 /* Put reject file in opt_logdir */
2057 fn_format(reject_file, result_file_name, opt_logdir, ".reject",
2058 MY_REPLACE_DIR | MY_REPLACE_EXT);
2059
2060 if (my_copy(log_file.file_name(), reject_file, MYF(0)) != 0)
2061 die("Failed to copy '%s' to '%s', errno: %d", log_file.file_name(),
2062 reject_file, errno);
2063
2064 show_diff(nullptr, result_file_name, reject_file);
2065 die("%s", mess);
2066 break;
2067 }
2068 default: /* impossible */
2069 die("Unknown error code from dyn_string_cmp()");
2070 }
2071 }
2072
2073 /*
2074 Remove surrounding chars from string
2075
2076 Return 1 if first character is found but not last
2077 */
strip_surrounding(char * str,char c1,char c2)2078 static int strip_surrounding(char *str, char c1, char c2) {
2079 char *ptr = str;
2080
2081 /* Check if the first non space character is c1 */
2082 while (*ptr && my_isspace(charset_info, *ptr)) ptr++;
2083 if (*ptr == c1) {
2084 /* Replace it with a space */
2085 *ptr = ' ';
2086
2087 /* Last non space charecter should be c2 */
2088 ptr = strend(str) - 1;
2089 while (*ptr && my_isspace(charset_info, *ptr)) ptr--;
2090 if (*ptr == c2) {
2091 /* Replace it with \0 */
2092 *ptr = 0;
2093 } else {
2094 /* Mismatch detected */
2095 return 1;
2096 }
2097 }
2098 return 0;
2099 }
2100
strip_parentheses(struct st_command * command)2101 static void strip_parentheses(struct st_command *command) {
2102 if (strip_surrounding(command->first_argument, '(', ')'))
2103 die("%.*s - argument list started with '%c' must be ended with '%c'",
2104 static_cast<int>(command->first_word_len), command->query, '(', ')');
2105 }
2106
operator ()(VAR * var) const2107 void var_free::operator()(VAR *var) const {
2108 my_free(var->str_val);
2109 if (var->alloced) my_free(var);
2110 }
2111
var_check_int(VAR * v)2112 static void var_check_int(VAR *v) {
2113 char *endptr;
2114 char *str = v->str_val;
2115
2116 /* Initially assume not a number */
2117 v->int_val = 0;
2118 v->is_int = false;
2119 v->int_dirty = false;
2120 if (!str) return;
2121
2122 v->int_val = (int)strtol(str, &endptr, 10);
2123 /* It is an int if strtol consumed something up to end/space/tab */
2124 if (endptr > str && (!*endptr || *endptr == ' ' || *endptr == '\t'))
2125 v->is_int = true;
2126 }
2127
var_init(VAR * v,const char * name,size_t name_len,const char * val,size_t val_len)2128 VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val,
2129 size_t val_len) {
2130 size_t val_alloc_len;
2131 VAR *tmp_var;
2132 if (!name_len && name) name_len = std::strlen(name);
2133 if (!val_len && val) val_len = std::strlen(val);
2134 if (!val) val_len = 0;
2135 val_alloc_len = val_len + 16; /* room to grow */
2136 if (!(tmp_var = v) && !(tmp_var = (VAR *)my_malloc(
2137 PSI_NOT_INSTRUMENTED,
2138 sizeof(*tmp_var) + name_len + 2, MYF(MY_WME))))
2139 die("Out of memory");
2140
2141 if (name != nullptr) {
2142 tmp_var->name = reinterpret_cast<char *>(tmp_var) + sizeof(*tmp_var);
2143 memcpy(tmp_var->name, name, name_len);
2144 tmp_var->name[name_len] = 0;
2145 } else
2146 tmp_var->name = nullptr;
2147
2148 tmp_var->alloced = (v == nullptr);
2149
2150 if (!(tmp_var->str_val = (char *)my_malloc(PSI_NOT_INSTRUMENTED,
2151 val_alloc_len + 1, MYF(MY_WME))))
2152 die("Out of memory");
2153
2154 if (val) memcpy(tmp_var->str_val, val, val_len);
2155 tmp_var->str_val[val_len] = 0;
2156
2157 var_check_int(tmp_var);
2158 tmp_var->name_len = name_len;
2159 tmp_var->str_val_len = val_len;
2160 tmp_var->alloced_len = val_alloc_len;
2161 return tmp_var;
2162 }
2163
var_from_env(const char * name,const char * def_val)2164 VAR *var_from_env(const char *name, const char *def_val) {
2165 const char *tmp;
2166 VAR *v;
2167 if (!(tmp = getenv(name))) tmp = def_val;
2168
2169 v = var_init(nullptr, name, std::strlen(name), tmp, std::strlen(tmp));
2170 var_hash->emplace(name, std::unique_ptr<VAR, var_free>(v));
2171 return v;
2172 }
2173
var_get(const char * var_name,const char ** var_name_end,bool raw,bool ignore_not_existing)2174 VAR *var_get(const char *var_name, const char **var_name_end, bool raw,
2175 bool ignore_not_existing) {
2176 int digit;
2177 VAR *v;
2178 DBUG_TRACE;
2179 DBUG_PRINT("enter", ("var_name: %s", var_name));
2180
2181 if (*var_name != '$') goto err;
2182 digit = *++var_name - '0';
2183 if (digit < 0 || digit >= 10) {
2184 const char *save_var_name = var_name, *end;
2185 uint length;
2186 end = (var_name_end) ? *var_name_end : nullptr;
2187 while (my_isvar(charset_info, *var_name) && var_name != end) var_name++;
2188 if (var_name == save_var_name) {
2189 if (ignore_not_existing) return nullptr;
2190 die("Empty variable");
2191 }
2192 length = (uint)(var_name - save_var_name);
2193 if (length >= MAX_VAR_NAME_LENGTH)
2194 die("Too long variable name: %s", save_var_name);
2195
2196 if (!(v = find_or_nullptr(*var_hash, std::string(save_var_name, length)))) {
2197 char buff[MAX_VAR_NAME_LENGTH + 1];
2198 strmake(buff, save_var_name, length);
2199 v = var_from_env(buff, "");
2200 }
2201 var_name--; /* Point at last character */
2202 } else
2203 v = var_reg + digit;
2204
2205 if (!raw && v->int_dirty) {
2206 sprintf(v->str_val, "%d", v->int_val);
2207 v->int_dirty = false;
2208 v->str_val_len = std::strlen(v->str_val);
2209 }
2210 if (var_name_end) *var_name_end = var_name;
2211 return v;
2212 err:
2213 if (var_name_end) *var_name_end = nullptr;
2214 die("Unsupported variable name: %s", var_name);
2215 return nullptr;
2216 }
2217
var_obtain(const char * name,int len)2218 static VAR *var_obtain(const char *name, int len) {
2219 VAR *v = find_or_nullptr(*var_hash, std::string(name, len));
2220 if (v == nullptr) {
2221 v = var_init(nullptr, name, len, "", 0);
2222 var_hash->emplace(std::string(name, len),
2223 std::unique_ptr<VAR, var_free>(v));
2224 }
2225 return v;
2226 }
2227
2228 /*
2229 - if variable starts with a $ it is regarded as a local test varable
2230 - if not it is treated as a environment variable, and the corresponding
2231 environment variable will be updated
2232 */
2233
var_set(const char * var_name,const char * var_name_end,const char * var_val,const char * var_val_end)2234 void var_set(const char *var_name, const char *var_name_end,
2235 const char *var_val, const char *var_val_end) {
2236 int digit, env_var = 0;
2237 VAR *v;
2238 DBUG_TRACE;
2239 DBUG_PRINT("enter", ("var_name: '%.*s' = '%.*s' (length: %d)",
2240 (int)(var_name_end - var_name), var_name,
2241 (int)(var_val_end - var_val), var_val,
2242 (int)(var_val_end - var_val)));
2243
2244 if (*var_name != '$')
2245 env_var = 1;
2246 else
2247 var_name++;
2248
2249 digit = *var_name - '0';
2250 if (!(digit < 10 && digit >= 0)) {
2251 v = var_obtain(var_name, (uint)(var_name_end - var_name));
2252 } else
2253 v = var_reg + digit;
2254
2255 eval_expr(v, var_val, (const char **)&var_val_end);
2256
2257 if (env_var) {
2258 if (v->int_dirty) {
2259 sprintf(v->str_val, "%d", v->int_val);
2260 v->int_dirty = false;
2261 v->str_val_len = std::strlen(v->str_val);
2262 }
2263 /* setenv() expects \0-terminated strings */
2264 DBUG_ASSERT(v->name[v->name_len] == 0);
2265 setenv(v->name, v->str_val, 1);
2266 }
2267 }
2268
var_set_string(const char * name,const char * value)2269 static void var_set_string(const char *name, const char *value) {
2270 var_set(name, name + std::strlen(name), value, value + std::strlen(value));
2271 }
2272
var_set_int(const char * name,int value)2273 static void var_set_int(const char *name, int value) {
2274 char buf[21];
2275 snprintf(buf, sizeof(buf), "%d", value);
2276 var_set_string(name, buf);
2277 }
2278
2279 /*
2280 Store an integer (typically the returncode of the last SQL)
2281 statement in the mysqltest builtin variable $mysql_errno
2282 */
2283
var_set_errno(int sql_errno)2284 static void var_set_errno(int sql_errno) {
2285 var_set_int("$mysql_errno", sql_errno);
2286 var_set_string("$mysql_errname", get_errname_from_code(sql_errno));
2287 }
2288
2289 /// Variable '$DISABLED_WARNINGS_LIST' contains comma separated list
2290 /// of disabled warnings and variable '$ENABLED_WARNINGS_LIST' contains
2291 /// comma separated list of enabled warnings.
2292 ///
2293 /// Update the value of these two variables with the latest list of
2294 /// disabled and enabled warnings. The value of these variables will be
2295 /// empty if there are no disabled or enabled warnings.
2296 ///
2297 /// These variables will always contain the latest list of disabled
2298 /// and enabled warnings, and can be referenced inside a test or inside
2299 /// a test utility file to access the current list of disabled or
2300 /// enabled warnings.
update_disabled_enabled_warnings_list_var()2301 static void update_disabled_enabled_warnings_list_var() {
2302 // Update '$DISABLED_WARNINGS_LIST' variable
2303 std::string disabled_warning_list = disabled_warnings->warnings_list();
2304 var_set_string("DISABLED_WARNINGS_LIST", disabled_warning_list.c_str());
2305
2306 // Update '$ENABLED_WARNINGS_LIST' variable
2307 std::string enabled_warning_list = enabled_warnings->warnings_list();
2308 var_set_string("ENABLED_WARNINGS_LIST", enabled_warning_list.c_str());
2309 }
2310
2311 /// Set a property value to either 0 or 1 for a disable_X or a enable_X
2312 /// command, and the new value set will be applicable for next statement
2313 /// only. After that, property value will be reset back to the old value.
2314 ///
2315 /// @param property Enum value representing a Property
2316 /// @param value Value for the property, either 0 or 1
set_once_property(enum_prop property,bool value)2317 static void set_once_property(enum_prop property, bool value) {
2318 Property &prop = prop_list[property];
2319 prop.set = true;
2320 prop.old = *prop.var;
2321 *prop.var = value;
2322 var_set_int(prop.env_name, (value != prop.reverse));
2323 once_property = true;
2324 }
2325
2326 /// Set a property value to either 0 or 1 for a disable_X or a enable_X
2327 /// command.
2328 ///
2329 /// @param command Pointer to the st_command structure which holds the
2330 /// arguments and information for the command.
2331 /// @param property Enum value representing a Property
2332 /// @param value Value for the property, either 0 or 1
set_property(st_command * command,enum_prop property,bool value)2333 static void set_property(st_command *command, enum_prop property, bool value) {
2334 char *arg = command->first_argument;
2335
2336 // If "ONCE" argument is specified, the new value for the property is
2337 // set for next statement only. After that, property value will be
2338 // reset back to the old value.
2339 if (arg) {
2340 // "ONCE" is the second argument to 'disable_warnings/enable_warnings'
2341 // command.
2342 if (((command->type == Q_DISABLE_WARNINGS ||
2343 command->type == Q_ENABLE_WARNINGS) &&
2344 std::strstr(arg, "ONCE") != nullptr) ||
2345 !std::strcmp(arg, "ONCE")) {
2346 command->last_argument = arg + std::strlen(arg);
2347 set_once_property(property, value);
2348 return;
2349 }
2350 }
2351
2352 Property &prop = prop_list[property];
2353 prop.set = false;
2354 *prop.var = value;
2355 var_set_int(prop.env_name, (value != prop.reverse));
2356 }
2357
2358 /// Reset property value to the old value for all properties which are
2359 /// set for the next statement only, i.e properties specified using
2360 /// keyword "ONCE" argument.
revert_properties()2361 void revert_properties() {
2362 if (!once_property) return;
2363
2364 for (std::size_t i = 0; i < P_MAX; i++) {
2365 Property &prop = prop_list[i];
2366 if (prop.set) {
2367 *prop.var = prop.old;
2368 prop.set = false;
2369 var_set_int(prop.env_name, (prop.old != prop.reverse));
2370 }
2371 }
2372
2373 // Remove warnings which are disabled or enabled for the next
2374 // statement only.
2375 disabled_warnings->update_list();
2376 enabled_warnings->update_list();
2377
2378 // Update $DISABLED_WARNINGS_LIST and $ENABLED_WARNINGS_LIST
2379 // variable value.
2380 update_disabled_enabled_warnings_list_var();
2381
2382 once_property = false;
2383 }
2384
2385 /*
2386 Set variable from the result of a query
2387
2388 SYNOPSIS
2389 var_query_set()
2390 var variable to set from query
2391 query start of query string to execute
2392 query_end end of the query string to execute
2393
2394
2395 DESCRIPTION
2396 let @<var_name> = `<query>`
2397
2398 Execute the query and assign the first row of result to var as
2399 a tab separated strings
2400
2401 Also assign each column of the result set to
2402 variable "$<var_name>_<column_name>"
2403 Thus the tab separated output can be read from $<var_name> and
2404 and each individual column can be read as $<var_name>_<col_name>
2405
2406 */
2407
var_query_set(VAR * var,const char * query,const char ** query_end)2408 static void var_query_set(VAR *var, const char *query, const char **query_end) {
2409 const char *end =
2410 (query_end && *query_end) ? *query_end : query + std::strlen(query);
2411 MYSQL_RES *res = nullptr;
2412 MYSQL_ROW row;
2413 MYSQL *mysql = &cur_con->mysql;
2414 DYNAMIC_STRING ds_query;
2415 DBUG_TRACE;
2416
2417 /* Only white space or ) allowed past ending ` */
2418 while (end > query && *end != '`') {
2419 if (*end && (*end != ' ' && *end != '\t' && *end != '\n' && *end != ')'))
2420 die("Spurious text after `query` expression");
2421 --end;
2422 }
2423
2424 if (query == end) die("Syntax error in query, missing '`'");
2425 ++query;
2426
2427 /* Eval the query, thus replacing all environment variables */
2428 init_dynamic_string(&ds_query, nullptr, (end - query) + 32, 256);
2429 do_eval(&ds_query, query, end, false);
2430
2431 if (mysql_real_query_wrapper(mysql, ds_query.str,
2432 static_cast<ulong>(ds_query.length))) {
2433 handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
2434 mysql_sqlstate(mysql), &ds_res);
2435 /* If error was acceptable, return empty string */
2436 dynstr_free(&ds_query);
2437 eval_expr(var, "", nullptr);
2438 return;
2439 }
2440
2441 if (!(res = mysql_store_result_wrapper(mysql)))
2442 die("Query '%s' didn't return a result set", ds_query.str);
2443 dynstr_free(&ds_query);
2444
2445 if ((row = mysql_fetch_row_wrapper(res)) && row[0]) {
2446 /*
2447 Concatenate all fields in the first row with tab in between
2448 and assign that string to the $variable
2449 */
2450 DYNAMIC_STRING result;
2451 uint i;
2452 ulong *lengths;
2453
2454 init_dynamic_string(&result, "", 512, 512);
2455 lengths = mysql_fetch_lengths(res);
2456 for (i = 0; i < mysql_num_fields(res); i++) {
2457 if (row[i]) {
2458 /* Add column to tab separated string */
2459 char *val = row[i];
2460 size_t len = lengths[i];
2461
2462 if (glob_replace_regex) {
2463 size_t orig_len = len;
2464 // Regex replace
2465 if (!multi_reg_replace(glob_replace_regex, (char *)val, &len)) {
2466 val = glob_replace_regex->buf;
2467 } else {
2468 len = orig_len;
2469 }
2470 }
2471 DYNAMIC_STRING ds_temp;
2472 init_dynamic_string(&ds_temp, "", 512, 512);
2473
2474 /* Store result from replace_result in ds_temp */
2475 if (glob_replace)
2476 replace_strings_append(glob_replace, &ds_temp, val, len);
2477
2478 /*
2479 Call the replace_numeric_round function with the specified
2480 precision. It may be used along with replace_result, so use the
2481 output from replace_result as the input for replace_numeric_round.
2482 */
2483 if (glob_replace_numeric_round >= 0) {
2484 /* Copy the result from replace_result if it was used, into buffer */
2485 if (ds_temp.length > 0) {
2486 char buffer[512];
2487 strcpy(buffer, ds_temp.str);
2488 dynstr_free(&ds_temp);
2489 init_dynamic_string(&ds_temp, "", 512, 512);
2490 replace_numeric_round_append(glob_replace_numeric_round, &ds_temp,
2491 buffer, std::strlen(buffer));
2492 } else
2493 replace_numeric_round_append(glob_replace_numeric_round, &ds_temp,
2494 val, len);
2495 }
2496
2497 if (!glob_replace && glob_replace_numeric_round < 0)
2498 dynstr_append_mem(&result, val, len);
2499 else
2500 dynstr_append_mem(&result, ds_temp.str, std::strlen(ds_temp.str));
2501 dynstr_free(&ds_temp);
2502 }
2503 dynstr_append_mem(&result, "\t", 1);
2504 }
2505 end = result.str + result.length - 1;
2506 /* Evaluation should not recurse via backtick */
2507 eval_expr(var, result.str, &end, false, false);
2508 dynstr_free(&result);
2509 } else
2510 eval_expr(var, "", nullptr);
2511
2512 mysql_free_result_wrapper(res);
2513 }
2514
set_result_format_version(ulong new_version)2515 static void set_result_format_version(ulong new_version) {
2516 switch (new_version) {
2517 case 1:
2518 /* The first format */
2519 break;
2520 case 2:
2521 /* New format that also writes comments and empty lines
2522 from test file to result */
2523 break;
2524 default:
2525 die("Version format %lu has not yet been implemented", new_version);
2526 break;
2527 }
2528 opt_result_format_version = new_version;
2529 }
2530
2531 /*
2532 Set the result format version to use when generating
2533 the .result file
2534 */
2535
do_result_format_version(struct st_command * command)2536 static void do_result_format_version(struct st_command *command) {
2537 long version;
2538 static DYNAMIC_STRING ds_version;
2539 const struct command_arg result_format_args[] = {
2540 {"version", ARG_STRING, true, &ds_version, "Version to use"}};
2541
2542 DBUG_TRACE;
2543
2544 check_command_args(command, command->first_argument, result_format_args,
2545 sizeof(result_format_args) / sizeof(struct command_arg),
2546 ',');
2547
2548 /* Convert version number to int */
2549 if (!str2int(ds_version.str, 10, (long)0, (long)INT_MAX, &version))
2550 die("Invalid version number: '%s'", ds_version.str);
2551
2552 set_result_format_version(version);
2553
2554 dynstr_append(&ds_res, "result_format: ");
2555 dynstr_append_mem(&ds_res, ds_version.str, ds_version.length);
2556 dynstr_append(&ds_res, "\n");
2557 dynstr_free(&ds_version);
2558 }
2559
2560 /// Convert between error numbers and error names/strings.
2561 ///
2562 /// @code
2563 /// let $var = convert_error(ER_UNKNOWN_ERROR);
2564 /// let $var = convert_error(1234);
2565 /// @endcode
2566 ///
2567 /// The variable '$var' will be populated with error number if the
2568 /// argument is string. The variable var will be populated with error
2569 /// string if the argument is number.
2570 ///
2571 /// @param command Pointer to the st_command structure which holds the
2572 /// arguments and information for the command.
2573 /// @param var Pointer to VAR object containing a variable
2574 /// information.
var_set_convert_error(struct st_command * command,VAR * var)2575 static void var_set_convert_error(struct st_command *command, VAR *var) {
2576 // The command->query contains the statement convert_error(1234)
2577 char *first = std::strchr(command->query, '(') + 1;
2578 char *last = std::strchr(command->query, ')');
2579
2580 // Denoting an empty string
2581 if (last == first) {
2582 eval_expr(var, "0", nullptr);
2583 return;
2584 }
2585
2586 // If the string is an error string , it starts with 'E' as is the norm
2587 if (*first == 'E') {
2588 std::string error_name(first, int(last - first));
2589 int error = get_errcode_from_name(error_name);
2590 if (error == -1) die("Unknown SQL error name '%s'.", error_name.c_str());
2591 char str[100];
2592 std::sprintf(str, "%d", error);
2593 eval_expr(var, str, nullptr);
2594 } else if (my_isdigit(charset_info, *first)) {
2595 // Error number argument
2596 long int err = std::strtol(first, &last, 0);
2597 const char *err_name = get_errname_from_code(err);
2598 eval_expr(var, err_name, nullptr);
2599 } else {
2600 die("Invalid error in input");
2601 }
2602 }
2603
2604 /*
2605 Set variable from the result of a field in a query
2606
2607 This function is useful when checking for a certain value
2608 in the output from a query that can't be restricted to only
2609 return some values. A very good example of that is most SHOW
2610 commands.
2611
2612 SYNOPSIS
2613 var_set_query_get_value()
2614
2615 DESCRIPTION
2616 let $variable= query_get_value(<query to run>,<column name>,<row no>);
2617
2618 <query to run> - The query that should be sent to the server
2619 <column name> - Name of the column that holds the field be compared
2620 against the expected value
2621 <row no> - Number of the row that holds the field to be
2622 compared against the expected value
2623
2624 */
2625
var_set_query_get_value(struct st_command * command,VAR * var)2626 static void var_set_query_get_value(struct st_command *command, VAR *var) {
2627 long row_no;
2628 int col_no = -1;
2629 MYSQL_RES *res = nullptr;
2630 MYSQL *mysql = &cur_con->mysql;
2631
2632 static DYNAMIC_STRING ds_query;
2633 static DYNAMIC_STRING ds_col;
2634 static DYNAMIC_STRING ds_row;
2635 const struct command_arg query_get_value_args[] = {
2636 {"query", ARG_STRING, true, &ds_query, "Query to run"},
2637 {"column name", ARG_STRING, true, &ds_col, "Name of column"},
2638 {"row number", ARG_STRING, true, &ds_row, "Number for row"}};
2639
2640 DBUG_TRACE;
2641
2642 strip_parentheses(command);
2643 DBUG_PRINT("info", ("query: %s", command->query));
2644 check_command_args(command, command->first_argument, query_get_value_args,
2645 sizeof(query_get_value_args) / sizeof(struct command_arg),
2646 ',');
2647
2648 DBUG_PRINT("info", ("query: %s", ds_query.str));
2649 DBUG_PRINT("info", ("col: %s", ds_col.str));
2650
2651 /* Convert row number to int */
2652 if (!str2int(ds_row.str, 10, (long)0, (long)INT_MAX, &row_no))
2653 die("Invalid row number: '%s'", ds_row.str);
2654 DBUG_PRINT("info", ("row: %s, row_no: %ld", ds_row.str, row_no));
2655 dynstr_free(&ds_row);
2656
2657 /* Remove any surrounding "'s from the query - if there is any */
2658 if (strip_surrounding(ds_query.str, '"', '"'))
2659 die("Mismatched \"'s around query '%s'", ds_query.str);
2660
2661 /* Run the query */
2662 if (mysql_real_query_wrapper(mysql, ds_query.str,
2663 static_cast<ulong>(ds_query.length))) {
2664 handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
2665 mysql_sqlstate(mysql), &ds_res);
2666 /* If error was acceptable, return empty string */
2667 dynstr_free(&ds_query);
2668 dynstr_free(&ds_col);
2669 eval_expr(var, "", nullptr);
2670 return;
2671 }
2672
2673 if (!(res = mysql_store_result_wrapper(mysql)))
2674 die("Query '%s' didn't return a result set", ds_query.str);
2675
2676 {
2677 /* Find column number from the given column name */
2678 uint i;
2679 uint num_fields = mysql_num_fields(res);
2680 MYSQL_FIELD *fields = mysql_fetch_fields(res);
2681
2682 for (i = 0; i < num_fields; i++) {
2683 if (std::strcmp(fields[i].name, ds_col.str) == 0 &&
2684 std::strlen(fields[i].name) == ds_col.length) {
2685 col_no = i;
2686 break;
2687 }
2688 }
2689 if (col_no == -1) {
2690 mysql_free_result_wrapper(res);
2691 die("Could not find column '%s' in the result of '%s'", ds_col.str,
2692 ds_query.str);
2693 }
2694 DBUG_PRINT("info", ("Found column %d with name '%s'", i, fields[i].name));
2695 }
2696 dynstr_free(&ds_col);
2697
2698 {
2699 /* Get the value */
2700 MYSQL_ROW row;
2701 long rows = 0;
2702 const char *value = "No such row";
2703
2704 while ((row = mysql_fetch_row_wrapper(res))) {
2705 if (++rows == row_no) {
2706 DBUG_PRINT("info", ("At row %ld, column %d is '%s'", row_no, col_no,
2707 row[col_no]));
2708 /* Found the row to get */
2709 if (row[col_no])
2710 value = row[col_no];
2711 else
2712 value = "NULL";
2713
2714 break;
2715 }
2716 }
2717 eval_expr(var, value, nullptr, false, false);
2718 }
2719 dynstr_free(&ds_query);
2720 mysql_free_result_wrapper(res);
2721 }
2722
var_copy(VAR * dest,VAR * src)2723 static void var_copy(VAR *dest, VAR *src) {
2724 dest->int_val = src->int_val;
2725 dest->is_int = src->is_int;
2726 dest->int_dirty = src->int_dirty;
2727
2728 /* Alloc/realloc data for str_val in dest */
2729 if (dest->alloced_len < src->alloced_len &&
2730 !(dest->str_val =
2731 dest->str_val
2732 ? (char *)my_realloc(PSI_NOT_INSTRUMENTED, dest->str_val,
2733 src->alloced_len, MYF(MY_WME))
2734 : (char *)my_malloc(PSI_NOT_INSTRUMENTED, src->alloced_len,
2735 MYF(MY_WME))))
2736 die("Out of memory");
2737 else
2738 dest->alloced_len = src->alloced_len;
2739
2740 /* Copy str_val data to dest */
2741 dest->str_val_len = src->str_val_len;
2742 if (src->str_val_len) memcpy(dest->str_val, src->str_val, src->str_val_len);
2743 }
2744
eval_expr(VAR * v,const char * p,const char ** p_end,bool open_end,bool do_eval)2745 void eval_expr(VAR *v, const char *p, const char **p_end, bool open_end,
2746 bool do_eval) {
2747 DBUG_TRACE;
2748 if (p_end) {
2749 DBUG_PRINT("enter", ("p: '%.*s'", (int)(*p_end - p), p));
2750 } else {
2751 DBUG_PRINT("enter", ("p: '%s'", p));
2752 }
2753 /* Skip to treat as pure string if no evaluation */
2754 if (!do_eval) goto NO_EVAL;
2755
2756 if (*p == '$') {
2757 VAR *vp;
2758 const char *expected_end = *p_end; // Remember var end
2759 if ((vp = var_get(p, p_end, false, false))) var_copy(v, vp);
2760
2761 /* Apparently it is not safe to assume null-terminated string */
2762 v->str_val[v->str_val_len] = 0;
2763
2764 /* Make sure there was just a $variable and nothing else */
2765 const char *end = *p_end + 1;
2766 if (end < expected_end && !open_end)
2767 die("Found junk '%.*s' after $variable in expression",
2768 (int)(expected_end - end - 1), end);
2769
2770 return;
2771 }
2772
2773 if (*p == '`') {
2774 var_query_set(v, p, p_end);
2775 return;
2776 }
2777
2778 {
2779 /* Check if this is a "let $var= query_get_value()" */
2780 const char *get_value_str = "query_get_value";
2781 const size_t len = std::strlen(get_value_str);
2782 if (std::strncmp(p, get_value_str, len) == 0) {
2783 struct st_command command;
2784 memset(&command, 0, sizeof(command));
2785 command.query = const_cast<char *>(p);
2786 command.first_word_len = len;
2787 command.first_argument = command.query + len;
2788 command.end = const_cast<char *>(*p_end);
2789 var_set_query_get_value(&command, v);
2790 return;
2791 }
2792 /* Check if this is a "let $var= convert_error()" */
2793 const char *get_value_str1 = "convert_error";
2794 const size_t len1 = std::strlen(get_value_str1);
2795 if (std::strncmp(p, get_value_str1, len1) == 0) {
2796 struct st_command command;
2797 memset(&command, 0, sizeof(command));
2798 command.query = const_cast<char *>(p);
2799 command.first_word_len = len;
2800 command.first_argument = command.query + len;
2801 command.end = const_cast<char *>(*p_end);
2802 var_set_convert_error(&command, v);
2803 return;
2804 }
2805 }
2806
2807 NO_EVAL : {
2808 size_t new_val_len =
2809 (p_end && *p_end) ? static_cast<size_t>(*p_end - p) : std::strlen(p);
2810 if (new_val_len + 1 >= v->alloced_len) {
2811 static size_t MIN_VAR_ALLOC = 32;
2812 v->alloced_len =
2813 (new_val_len < MIN_VAR_ALLOC - 1) ? MIN_VAR_ALLOC : new_val_len + 1;
2814 if (!(v->str_val =
2815 v->str_val ? (char *)my_realloc(PSI_NOT_INSTRUMENTED, v->str_val,
2816 v->alloced_len + 1, MYF(MY_WME))
2817 : (char *)my_malloc(PSI_NOT_INSTRUMENTED,
2818 v->alloced_len + 1, MYF(MY_WME))))
2819 die("Out of memory");
2820 }
2821 v->str_val_len = new_val_len;
2822 memcpy(v->str_val, p, new_val_len);
2823 v->str_val[new_val_len] = 0;
2824 var_check_int(v);
2825 }
2826 }
2827
open_file(const char * name)2828 static int open_file(const char *name) {
2829 char buff[FN_REFLEN];
2830 size_t length;
2831 DBUG_TRACE;
2832 DBUG_PRINT("enter", ("name: %s", name));
2833
2834 bool file_exists = false;
2835 /* Extract path from current file and try it as base first */
2836 if (dirname_part(buff, cur_file->file_name, &length)) {
2837 strxmov(buff, buff, name, NullS);
2838 if (access(buff, F_OK) == 0) {
2839 DBUG_PRINT("info", ("The file exists"));
2840 name = buff;
2841 file_exists = true;
2842 }
2843 }
2844
2845 if (!test_if_hard_path(name) && !file_exists) {
2846 strxmov(buff, opt_basedir, name, NullS);
2847 name = buff;
2848 }
2849 fn_format(buff, name, "", "", MY_UNPACK_FILENAME);
2850
2851 if (cur_file == file_stack_end) die("Source directives are nesting too deep");
2852 cur_file++;
2853 if (!(cur_file->file = fopen(buff, "rb"))) {
2854 cur_file--;
2855 die("Could not open '%s' for reading, errno: %d", buff, errno);
2856 }
2857 cur_file->file_name = my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE));
2858 cur_file->lineno = 1;
2859 return 0;
2860 }
2861
2862 /*
2863 Source and execute the given file
2864
2865 SYNOPSIS
2866 do_source()
2867 query called command
2868
2869 DESCRIPTION
2870 source <file_name>
2871
2872 Open the file <file_name> and execute it
2873
2874 */
2875
do_source(struct st_command * command)2876 static void do_source(struct st_command *command) {
2877 static DYNAMIC_STRING ds_filename;
2878 const struct command_arg source_args[] = {
2879 {"filename", ARG_STRING, true, &ds_filename, "File to source"}};
2880 DBUG_TRACE;
2881
2882 check_command_args(command, command->first_argument, source_args,
2883 sizeof(source_args) / sizeof(struct command_arg), ' ');
2884
2885 /*
2886 If this file has already been sourced, don't source it again.
2887 It's already available in the q_lines cache.
2888 */
2889 if (parser.current_line < (parser.read_lines - 1))
2890 ; /* Do nothing */
2891 else {
2892 DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str));
2893 open_file(ds_filename.str);
2894 }
2895
2896 dynstr_free(&ds_filename);
2897 }
2898
my_popen(DYNAMIC_STRING * ds_cmd,const char * mode,struct st_command * command MY_ATTRIBUTE ((unused)))2899 static FILE *my_popen(DYNAMIC_STRING *ds_cmd, const char *mode,
2900 struct st_command *command MY_ATTRIBUTE((unused))) {
2901 #ifdef _WIN32
2902 /*
2903 --execw is for tests executing commands containing non-ASCII characters.
2904
2905 To correctly start such a program on Windows, we need to use the "wide"
2906 version of popen, with prior translation of the command line from
2907 the file character set to wide string. We use the current value
2908 of --character_set as a file character set, so before using --execw
2909 make sure to set --character_set properly.
2910
2911 If we use the non-wide version of popen, Windows internally
2912 converts command line from the current ANSI code page to wide string.
2913 In case when character set of the command line does not match the
2914 current ANSI code page, non-ASCII characters get garbled in most cases.
2915
2916 On Linux, the command line passed to popen() is considered
2917 as a binary string, no any internal to-wide and from-wide
2918 character set conversion happens, so we don't need to do anything.
2919 On Linux --execw is just a synonym to --exec.
2920
2921 For simplicity, assume that command line is limited to 4KB
2922 (like in cmd.exe) and that mode at most 10 characters.
2923 */
2924 if (command->type == Q_EXECW) {
2925 wchar_t wcmd[4096];
2926 wchar_t wmode[10];
2927 const char *cmd = ds_cmd->str;
2928 uint dummy_errors;
2929 size_t len;
2930 len = my_convert((char *)wcmd, sizeof(wcmd) - sizeof(wcmd[0]),
2931 &my_charset_utf16le_bin, ds_cmd->str,
2932 std::strlen(ds_cmd->str), charset_info, &dummy_errors);
2933 wcmd[len / sizeof(wchar_t)] = 0;
2934 len = my_convert((char *)wmode, sizeof(wmode) - sizeof(wmode[0]),
2935 &my_charset_utf16le_bin, mode, std::strlen(mode),
2936 charset_info, &dummy_errors);
2937 wmode[len / sizeof(wchar_t)] = 0;
2938 return _wpopen(wcmd, wmode);
2939 }
2940 #endif /* _WIN32 */
2941
2942 return popen(ds_cmd->str, mode);
2943 }
2944
init_builtin_echo(void)2945 static void init_builtin_echo(void) {
2946 #ifdef _WIN32
2947 size_t echo_length;
2948
2949 /* Look for "echo.exe" in same dir as mysqltest was started from */
2950 dirname_part(builtin_echo, my_progname, &echo_length);
2951 fn_format(builtin_echo, ".\\echo.exe", builtin_echo, "", MYF(MY_REPLACE_DIR));
2952
2953 /* Make sure echo.exe exists */
2954 if (access(builtin_echo, F_OK) != 0) builtin_echo[0] = 0;
2955 return;
2956
2957 #else
2958
2959 builtin_echo[0] = 0;
2960 return;
2961
2962 #endif
2963 }
2964
2965 /*
2966 Replace a substring
2967
2968 SYNOPSIS
2969 replace
2970 ds_str The string to search and perform the replace in
2971 search_str The string to search for
2972 search_len Length of the string to search for
2973 replace_str The string to replace with
2974 replace_len Length of the string to replace with
2975
2976 RETURN
2977 0 String replaced
2978 1 Could not find search_str in str
2979 */
2980
replace(DYNAMIC_STRING * ds_str,const char * search_str,size_t search_len,const char * replace_str,size_t replace_len)2981 static int replace(DYNAMIC_STRING *ds_str, const char *search_str,
2982 size_t search_len, const char *replace_str,
2983 size_t replace_len) {
2984 DYNAMIC_STRING ds_tmp;
2985 const char *start = strstr(ds_str->str, search_str);
2986 if (!start) return 1;
2987 init_dynamic_string(&ds_tmp, "", ds_str->length + replace_len, 256);
2988 dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str);
2989 dynstr_append_mem(&ds_tmp, replace_str, replace_len);
2990 dynstr_append(&ds_tmp, start + search_len);
2991 dynstr_set(ds_str, ds_tmp.str);
2992 dynstr_free(&ds_tmp);
2993 return 0;
2994 }
2995
2996 #ifdef _WIN32
2997 /**
2998 Replace CRLF sequence with LF in place.
2999
3000 This function is required as a workaround for a bug in the Microsoft
3001 C runtime library introduced in Visual Studio 2015.
3002 See bug#22608247 and bug#22811243
3003
3004 @param buf Null terminated buffer.
3005 */
replace_crlf_with_lf(char * buf)3006 static void replace_crlf_with_lf(char *buf) {
3007 char *replace = buf;
3008 while (*buf) {
3009 *replace = *buf++;
3010 if (!((*replace == '\x0D') && (*buf == '\x0A'))) {
3011 replace++;
3012 }
3013 }
3014 *replace = '\x0';
3015 }
3016 #endif
3017
3018 /// Execute the shell command using the popen() library call. References
3019 /// to variables within the command are replaced with the corresponding
3020 /// values. Use “\\$” to specify a literal “$” character.
3021 ///
3022 /// The error code returned from the subprocess is checked against the
3023 /// expected error array, previously set with the --error command. It can
3024 /// thus be used to execute a command that shall fail.
3025 ///
3026 /// @code
3027 /// exec command [args]
3028 /// @endcode
3029 ///
3030 /// @param command Pointer to the st_command structure which holds the
3031 /// arguments and information for the command.
3032 /// @param run_in_background Specifies if command should be run in background.
3033 /// In such case we don't wait nor attempt to read the
3034 /// output.
3035 ///
3036 /// @note
3037 /// It is recommended to use mysqltest command(s) like "remove_file"
3038 /// instead of executing the shell commands using 'exec' command.
do_exec(struct st_command * command,bool run_in_background)3039 static void do_exec(struct st_command *command, bool run_in_background) {
3040 DBUG_TRACE;
3041
3042 const char *cmd = command->first_argument;
3043 DBUG_PRINT("enter", ("cmd: '%s'", cmd));
3044
3045 // Skip leading space
3046 while (*cmd && my_isspace(charset_info, *cmd)) cmd++;
3047 if (!*cmd) die("Missing argument in exec");
3048 command->last_argument = command->end;
3049
3050 DYNAMIC_STRING ds_cmd;
3051 init_dynamic_string(&ds_cmd, nullptr, command->query_len + 256, 256);
3052
3053 // Eval the command, thus replacing all environment variables
3054 do_eval(&ds_cmd, cmd, command->end, !is_windows);
3055
3056 // Check if echo should be replaced with "builtin" echo
3057 if (builtin_echo[0] && std::strncmp(cmd, "echo", 4) == 0) {
3058 // Replace echo with our "builtin" echo
3059 replace(&ds_cmd, "echo", 4, builtin_echo, std::strlen(builtin_echo));
3060 }
3061
3062 #ifdef _WIN32
3063 // Replace "/dev/null" with NUL
3064 while (replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0)
3065 ;
3066
3067 // Replace "closed stdout" with non existing output fd
3068 while (replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0)
3069 ;
3070 #endif
3071
3072 if (run_in_background) {
3073 /* Add an invocation of "START /B" on Windows, append " &" on Linux*/
3074 DYNAMIC_STRING ds_tmp;
3075 #ifdef WIN32
3076 init_dynamic_string(&ds_tmp, "START /B ", ds_cmd.length + 9, 256);
3077 dynstr_append_mem(&ds_tmp, ds_cmd.str, ds_cmd.length);
3078 #else
3079 init_dynamic_string(&ds_tmp, ds_cmd.str, ds_cmd.length + 2, 256);
3080 dynstr_append_mem(&ds_tmp, " &", 2);
3081 #endif
3082 dynstr_set(&ds_cmd, ds_tmp.str);
3083 dynstr_free(&ds_tmp);
3084 }
3085
3086 // exec command is interpreted externally and will not take newlines
3087 while (replace(&ds_cmd, "\n", 1, " ", 1) == 0)
3088 ;
3089
3090 DBUG_PRINT("info",
3091 ("Executing '%s' as '%s'", command->first_argument, ds_cmd.str));
3092
3093 #ifdef WIN32
3094 // Open pipe in binary mode as part of handling Microsoft _read bug.
3095 // See bug#22608247 and bug#22811243
3096 const char *mode = "rb";
3097 #else
3098 const char *mode = "r";
3099 #endif
3100 FILE *res_file;
3101 if (!(res_file = my_popen(&ds_cmd, mode, command)) &&
3102 command->abort_on_error) {
3103 dynstr_free(&ds_cmd);
3104 die("popen(\"%s\", \"r\") failed", command->first_argument);
3105 }
3106
3107 if (!run_in_background) {
3108 char buf[512];
3109 std::string str;
3110 while (std::fgets(buf, sizeof(buf), res_file)) {
3111 if (std::strlen(buf) < 1) continue;
3112
3113 #ifdef WIN32
3114 // Replace CRLF char with LF.
3115 // See bug#22608247 and bug#22811243
3116 DBUG_ASSERT(!std::strcmp(mode, "rb"));
3117 replace_crlf_with_lf(buf);
3118 #endif
3119 if (trace_exec) {
3120 fprintf(stdout, "%s", buf);
3121 fflush(stdout);
3122 }
3123 if (disable_result_log) {
3124 buf[std::strlen(buf) - 1] = 0;
3125 DBUG_PRINT("exec_result", ("%s", buf));
3126 } else {
3127 // Read the file line by line. Check if the buffer read from the
3128 // file ends with EOL character.
3129 if ((buf[std::strlen(buf) - 1] != '\n' &&
3130 std::strlen(buf) < (sizeof(buf) - 1)) ||
3131 (buf[std::strlen(buf) - 1] == '\n')) {
3132 // Found EOL
3133 if (str.length()) {
3134 // Temporary string exists, append the current buffer read
3135 // to the temporary string.
3136 str.append(buf);
3137 replace_dynstr_append(&ds_res, str.c_str());
3138 str.clear();
3139 } else {
3140 // Entire line is read at once
3141 replace_dynstr_append(&ds_res, buf);
3142 }
3143 } else {
3144 // The buffer read from the file doesn't end with EOL character,
3145 // store it in a temporary string.
3146 str.append(buf);
3147 }
3148 }
3149 }
3150 }
3151
3152 std::uint32_t status = 0;
3153 int error = pclose(res_file);
3154
3155 if (error != 0) {
3156 #ifdef _WIN32
3157 status = WEXITSTATUS(error);
3158 #else
3159 if (error > 0) {
3160 // Do the same as many shells here: show SIGKILL as 137
3161 if (WIFEXITED(error))
3162 status = WEXITSTATUS(error);
3163 else if (WIFSIGNALED(error))
3164 status = 0x80 + WTERMSIG(error);
3165 }
3166 #endif
3167
3168 if (command->abort_on_error) {
3169 log_msg("exec of '%s' failed, error: %d, status: %d, errno: %d.",
3170 ds_cmd.str, error, status, errno);
3171 dynstr_free(&ds_cmd);
3172 die("Command \"%s\" failed.\n\nOutput from before failure:\n%s",
3173 command->first_argument, ds_res.str);
3174 }
3175
3176 if (status == 0) status = error;
3177 }
3178
3179 dynstr_free(&ds_cmd);
3180 handle_command_error(command, status);
3181
3182 // Save error code
3183 save_error_code(error);
3184 }
3185
3186 enum enum_operator { DO_DEC, DO_INC };
3187
3188 /// Template function that frees memory of the dynamic string
3189 /// passed to the function.
3190 ///
3191 /// @param val Dynamic string whose memory needs to be freed.
3192 template <typename T>
free_dynamic_strings(T * val)3193 static void free_dynamic_strings(T *val) {
3194 dynstr_free(val);
3195 }
3196
3197 /// Frees the memory of dynamic strings passed to the function.
3198 /// It accepts a variable number of dynamic strings, and through
3199 /// recursion, frees the memory. The other template function
3200 /// which calls dynstr_free() is called here.
3201 ///
3202 /// @param first The dynamic string passed to the function which
3203 /// gets freed using dynstr_free().
3204 /// @param rest Rest of the dynamic strings which are passed to
3205 /// the function, through recursion, end up being
3206 /// freed by dynstr_free().
3207 template <typename T1, typename... T2>
free_dynamic_strings(T1 * first,T2 * ...rest)3208 static void free_dynamic_strings(T1 *first, T2 *... rest) {
3209 free_dynamic_strings(first);
3210 free_dynamic_strings(rest...);
3211 }
3212
3213 /*
3214 Decrease or increase the value of a variable
3215
3216 SYNOPSIS
3217 do_modify_var()
3218 query called command
3219 op operation to perform on the var
3220
3221 DESCRIPTION
3222 dec $var_name
3223 inc $var_name
3224
3225 */
3226
do_modify_var(struct st_command * command,enum enum_operator op)3227 static int do_modify_var(struct st_command *command, enum enum_operator op) {
3228 const char *p = command->first_argument;
3229 VAR *v;
3230 if (!*p)
3231 die("Missing argument to %.*s", static_cast<int>(command->first_word_len),
3232 command->query);
3233 if (*p != '$')
3234 die("The argument to %.*s must be a variable (start with $)",
3235 static_cast<int>(command->first_word_len), command->query);
3236 v = var_get(p, &p, true, false);
3237 if (!v->is_int) die("Cannot perform inc/dec on a non-numeric value");
3238 switch (op) {
3239 case DO_DEC:
3240 v->int_val--;
3241 break;
3242 case DO_INC:
3243 v->int_val++;
3244 break;
3245 default:
3246 die("Invalid operator to do_modify_var");
3247 break;
3248 }
3249 v->int_dirty = true;
3250 command->last_argument = const_cast<char *>(++p);
3251 return 0;
3252 }
3253
3254 /// Removes the file passed as the argument and retries a specified
3255 /// number of times, if it is unsuccessful.
3256 ///
3257 /// @param command Pointer to the st_command structure which holds the
3258 /// arguments and information for the command.
do_remove_file(struct st_command * command)3259 static void do_remove_file(struct st_command *command) {
3260 int error;
3261 static DYNAMIC_STRING ds_filename;
3262 static DYNAMIC_STRING ds_retry;
3263
3264 const struct command_arg rm_args[] = {
3265 {"filename", ARG_STRING, true, &ds_filename, "File to delete"},
3266 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3267 DBUG_TRACE;
3268
3269 check_command_args(command, command->first_argument, rm_args,
3270 sizeof(rm_args) / sizeof(struct command_arg), ' ');
3271
3272 // Check if the retry value is passed, and if it is an integer
3273 int retry = 0;
3274 if (ds_retry.length) {
3275 retry = get_int_val(ds_retry.str);
3276 if (retry < 0) {
3277 // In case of invalid retry, copy the value passed to print later
3278 char buf[32];
3279 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3280 free_dynamic_strings(&ds_filename, &ds_retry);
3281 die("Invalid value '%s' for retry argument given to remove_file "
3282 "command.",
3283 buf);
3284 }
3285 }
3286
3287 DBUG_PRINT("info", ("removing file: %s", ds_filename.str));
3288 error = my_delete(ds_filename.str, MYF(0)) != 0;
3289
3290 /*
3291 If the remove command fails due to an environmental issue, the command can
3292 be retried a specified number of times before throwing an error.
3293 */
3294 for (int i = 0; error && (i < retry); i++) {
3295 my_sleep(1000 * 1000);
3296 error = my_delete(ds_filename.str, MYF(0)) != 0;
3297 }
3298
3299 handle_command_error(command, error);
3300 free_dynamic_strings(&ds_filename, &ds_retry);
3301 }
3302
3303 /// Removes the files in the specified directory, by matching the
3304 /// file name pattern. Retry of the command can happen optionally with
3305 /// an interval of one second between each retry if the command fails.
3306 ///
3307 /// @param command Pointer to the st_command structure which holds the
3308 /// arguments and information for the command.
do_remove_files_wildcard(struct st_command * command)3309 static void do_remove_files_wildcard(struct st_command *command) {
3310 int error = 0;
3311 uint i;
3312 MY_DIR *dir_info;
3313 FILEINFO *file;
3314 char dir_separator[2];
3315 static DYNAMIC_STRING ds_directory;
3316 static DYNAMIC_STRING ds_wild;
3317 static DYNAMIC_STRING ds_retry;
3318 char dirname[FN_REFLEN];
3319
3320 const struct command_arg rm_args[] = {
3321 {"directory", ARG_STRING, true, &ds_directory,
3322 "Directory containing files to delete"},
3323 {"pattern", ARG_STRING, true, &ds_wild, "File pattern to delete"},
3324 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3325 DBUG_TRACE;
3326
3327 check_command_args(command, command->first_argument, rm_args,
3328 sizeof(rm_args) / sizeof(struct command_arg), ' ');
3329 fn_format(dirname, ds_directory.str, "", "", MY_UNPACK_FILENAME);
3330
3331 // Check if the retry value is passed, and if it is an interger
3332 int retry = 0;
3333 if (ds_retry.length) {
3334 retry = get_int_val(ds_retry.str);
3335 if (retry < 0) {
3336 // In case of invalid retry, copy the value passed to print later
3337 char buf[32];
3338 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3339 free_dynamic_strings(&ds_directory, &ds_wild, &ds_retry);
3340 die("Invalid value '%s' for retry argument given to "
3341 "remove_files_wildcard command.",
3342 buf);
3343 }
3344 }
3345
3346 static DYNAMIC_STRING ds_file_to_remove;
3347 DBUG_PRINT("info", ("listing directory: %s", dirname));
3348 /* Note that my_dir sorts the list if not given any flags */
3349 if (!(dir_info = my_dir(dirname, MYF(MY_DONT_SORT | MY_WANT_STAT)))) {
3350 error = 1;
3351 goto end;
3352 }
3353 init_dynamic_string(&ds_file_to_remove, dirname, 1024, 1024);
3354 dir_separator[0] = FN_LIBCHAR;
3355 dir_separator[1] = 0;
3356 dynstr_append(&ds_file_to_remove, dir_separator);
3357
3358 size_t length;
3359 /* Storing the length of the path to the file, so it can be reused */
3360 length = ds_file_to_remove.length;
3361 for (i = 0; i < (uint)dir_info->number_off_files; i++) {
3362 ds_file_to_remove.length = length;
3363 file = dir_info->dir_entry + i;
3364 /* Remove only regular files, i.e. no directories etc. */
3365 /* if (!MY_S_ISREG(file->mystat->st_mode)) */
3366 /* MY_S_ISREG does not work here on Windows, just skip directories */
3367 if (MY_S_ISDIR(file->mystat->st_mode)) continue;
3368 if (wild_compare_full(file->name, std::strlen(file->name), ds_wild.str,
3369 std::strlen(ds_wild.str), false, 0, '?', '*'))
3370 continue;
3371 /* Not required as the var ds_file_to_remove.length already has the
3372 length in canonnicalized form */
3373 /* ds_file_to_remove.length= ds_directory.length + 1;
3374 ds_file_to_remove.str[ds_directory.length + 1]= 0; */
3375 dynstr_append(&ds_file_to_remove, file->name);
3376 DBUG_PRINT("info", ("removing file: %s", ds_file_to_remove.str));
3377 error = my_delete(ds_file_to_remove.str, MYF(0)) != 0;
3378
3379 /*
3380 If the remove command fails due to an environmental issue, the command
3381 can be retried a specified number of times before throwing an error.
3382 */
3383 for (int j = 0; error && (j < retry); j++) {
3384 my_sleep(1000 * 1000);
3385 error = my_delete(ds_file_to_remove.str, MYF(0)) != 0;
3386 }
3387 if (error) break;
3388 }
3389 my_dirend(dir_info);
3390
3391 end:
3392 handle_command_error(command, error);
3393 free_dynamic_strings(&ds_directory, &ds_wild, &ds_file_to_remove, &ds_retry);
3394 }
3395
3396 /// Copy the source file to destination file. Copy will fail if the
3397 /// destination file exists. Retry of the command can happen optionally with
3398 /// an interval of one second between each retry if the command fails.
3399 ///
3400 /// @param command Pointer to the st_command structure which holds the
3401 /// arguments and information for the command.
do_copy_file(struct st_command * command)3402 static void do_copy_file(struct st_command *command) {
3403 int error;
3404 static DYNAMIC_STRING ds_from_file;
3405 static DYNAMIC_STRING ds_to_file;
3406 static DYNAMIC_STRING ds_retry;
3407
3408 const struct command_arg copy_file_args[] = {
3409 {"from_file", ARG_STRING, true, &ds_from_file, "Filename to copy from"},
3410 {"to_file", ARG_STRING, true, &ds_to_file, "Filename to copy to"},
3411 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3412 DBUG_TRACE;
3413
3414 check_command_args(command, command->first_argument, copy_file_args,
3415 sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3416
3417 // Check if the retry value is passed, and if it is an interger
3418 int retry = 0;
3419 if (ds_retry.length) {
3420 retry = get_int_val(ds_retry.str);
3421 if (retry < 0) {
3422 // In case of invalid retry, copy the value passed to print later
3423 char buf[32];
3424 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3425 free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3426 die("Invalid value '%s' for retry argument given to copy_file "
3427 "command.",
3428 buf);
3429 }
3430 }
3431
3432 DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str));
3433 /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3434 error = (my_copy(ds_from_file.str, ds_to_file.str,
3435 MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3436
3437 /*
3438 If the copy command fails due to an environmental issue, the command can
3439 be retried a specified number of times before throwing an error.
3440 */
3441 for (int i = 0; error && (i < retry); i++) {
3442 my_sleep(1000 * 1000);
3443 error =
3444 (my_copy(ds_from_file.str, ds_to_file.str,
3445 MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3446 }
3447
3448 handle_command_error(command, error);
3449 free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3450 }
3451
3452 /*
3453 SYNOPSIS
3454 recursive_copy
3455 ds_source - pointer to dynamic string containing source
3456 directory informtion
3457 ds_destination - pointer to dynamic string containing destination
3458 directory informtion
3459
3460 DESCRIPTION
3461 Recursive copy of <ds_source> to <ds_destination>
3462 */
3463
recursive_copy(DYNAMIC_STRING * ds_source,DYNAMIC_STRING * ds_destination)3464 static int recursive_copy(DYNAMIC_STRING *ds_source,
3465 DYNAMIC_STRING *ds_destination) {
3466 /* Note that my_dir sorts the list if not given any flags */
3467 MY_DIR *src_dir_info =
3468 my_dir(ds_source->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3469
3470 int error = 0;
3471
3472 /* Source directory exists */
3473 if (src_dir_info) {
3474 /* Note that my_dir sorts the list if not given any flags */
3475 MY_DIR *dest_dir_info =
3476 my_dir(ds_destination->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3477
3478 /* Create destination directory if it doesn't exist */
3479 if (!dest_dir_info) {
3480 error = my_mkdir(ds_destination->str, 0777, MYF(0)) != 0;
3481 if (error) {
3482 my_dirend(dest_dir_info);
3483 goto end;
3484 }
3485 } else {
3486 /* Extracting the source directory name */
3487 if (ds_source->str[std::strlen(ds_source->str) - 1] == '/') {
3488 strmake(ds_source->str, ds_source->str,
3489 std::strlen(ds_source->str) - 1);
3490 ds_source->length = ds_source->length - 1;
3491 }
3492 char *src_dir_name = strrchr(ds_source->str, '/');
3493
3494 /* Extracting the destination directory name */
3495 if (ds_destination->str[std::strlen(ds_destination->str) - 1] == '/') {
3496 strmake(ds_destination->str, ds_destination->str,
3497 std::strlen(ds_destination->str) - 1);
3498 ds_destination->length = ds_destination->length - 1;
3499 }
3500 char *dest_dir_name = strrchr(ds_destination->str, '/');
3501
3502 /*
3503 Destination directory might not exist if source directory
3504 name and destination directory name are not same.
3505
3506 For example, if source is "abc" and destintion is "def",
3507 check for the existance of directory "def/abc". If it exists
3508 then, copy the files from source directory(i.e "abc") to
3509 destination directory(i.e "def/abc"), otherwise create a new
3510 directory "abc" under "def" and copy the files from source to
3511 destination directory.
3512 */
3513 if (std::strcmp(src_dir_name, dest_dir_name)) {
3514 dynstr_append(ds_destination, src_dir_name);
3515 my_dirend(dest_dir_info);
3516 dest_dir_info =
3517 my_dir(ds_destination->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3518
3519 /* Create destination directory if it doesn't exist */
3520 if (!dest_dir_info) {
3521 error = my_mkdir(ds_destination->str, 0777, MYF(0)) != 0;
3522 if (error) {
3523 my_dirend(dest_dir_info);
3524 goto end;
3525 }
3526 }
3527 }
3528 }
3529
3530 char dir_separator[2] = {FN_LIBCHAR, 0};
3531 dynstr_append(ds_source, dir_separator);
3532 dynstr_append(ds_destination, dir_separator);
3533
3534 /*
3535 Storing the length of source and destination
3536 directory paths so it can be reused.
3537 */
3538 size_t source_dir_length = ds_source->length;
3539 size_t destination_dir_length = ds_destination->length;
3540 ;
3541
3542 for (uint i = 0; i < src_dir_info->number_off_files; i++) {
3543 ds_source->length = source_dir_length;
3544 ds_destination->length = destination_dir_length;
3545 FILEINFO *file = src_dir_info->dir_entry + i;
3546
3547 /* Skip the names "." and ".." */
3548 if (!std::strcmp(file->name, ".") || !std::strcmp(file->name, ".."))
3549 continue;
3550
3551 dynstr_append(ds_source, file->name);
3552 dynstr_append(ds_destination, file->name);
3553
3554 if (MY_S_ISDIR(file->mystat->st_mode))
3555 error = (recursive_copy(ds_source, ds_destination) != 0) ? 1 : error;
3556 else {
3557 DBUG_PRINT("info", ("Copying file: %s to %s", ds_source->str,
3558 ds_destination->str));
3559
3560 /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3561 error = (my_copy(ds_source->str, ds_destination->str,
3562 MYF(MY_HOLD_ORIGINAL_MODES)) != 0)
3563 ? 1
3564 : error;
3565 }
3566 }
3567 my_dirend(dest_dir_info);
3568 }
3569 /* Source directory does not exist or access denied */
3570 else
3571 error = 1;
3572
3573 end:
3574 my_dirend(src_dir_info);
3575 return error;
3576 }
3577
3578 /*
3579 SYNOPSIS
3580 do_force_cpdir
3581 command - command handle
3582
3583 DESCRIPTION
3584 force-cpdir <from_directory> <to_directory>
3585 Recursive copy of <from_directory> to <to_directory>.
3586 Destination directory is created if it doesn't exist.
3587
3588 NOTE
3589 Will fail if <from_directory> doesn't exist.
3590 */
3591
do_force_cpdir(struct st_command * command)3592 static void do_force_cpdir(struct st_command *command) {
3593 DBUG_TRACE;
3594
3595 static DYNAMIC_STRING ds_source;
3596 static DYNAMIC_STRING ds_destination;
3597
3598 const struct command_arg copy_file_args[] = {
3599 {"from_directory", ARG_STRING, true, &ds_source,
3600 "Directory to copy from"},
3601 {"to_directory", ARG_STRING, true, &ds_destination,
3602 "Directory to copy to"}};
3603
3604 check_command_args(command, command->first_argument, copy_file_args,
3605 sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3606
3607 DBUG_PRINT("info", ("Recursive copy files of %s to %s", ds_source.str,
3608 ds_destination.str));
3609
3610 DBUG_PRINT("info", ("listing directory: %s", ds_source.str));
3611
3612 int error = 0;
3613
3614 /*
3615 Throw an error if source directory path and
3616 destination directory path are same.
3617 */
3618 if (!std::strcmp(ds_source.str, ds_destination.str)) {
3619 error = 1;
3620 set_my_errno(EEXIST);
3621 } else
3622 error = recursive_copy(&ds_source, &ds_destination);
3623
3624 handle_command_error(command, error);
3625 dynstr_free(&ds_source);
3626 dynstr_free(&ds_destination);
3627 }
3628
3629 /// Copy files from source directory to destination directory, by matching
3630 /// a specified file name pattern.
3631 ///
3632 /// Copy will fail if no files match the pattern. It will fail if source
3633 /// directory is empty and/or there are no files in it. Copy will also
3634 /// fail if source directory or destination directory or both do not
3635 /// exist. Retry of the command can happen optionally with an interval of
3636 /// one second between each retry if the command fails.
3637 ///
3638 /// @param command Pointer to the st_command structure which holds the
3639 /// arguments and information for the command.
do_copy_files_wildcard(struct st_command * command)3640 static void do_copy_files_wildcard(struct st_command *command) {
3641 static DYNAMIC_STRING ds_source;
3642 static DYNAMIC_STRING ds_destination;
3643 static DYNAMIC_STRING ds_wild;
3644 static DYNAMIC_STRING ds_retry;
3645
3646 const struct command_arg copy_file_args[] = {
3647 {"from_directory", ARG_STRING, true, &ds_source,
3648 "Directory to copy from"},
3649 {"to_directory", ARG_STRING, true, &ds_destination,
3650 "Directory to copy to"},
3651 {"pattern", ARG_STRING, true, &ds_wild, "File name pattern"},
3652 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3653 DBUG_TRACE;
3654
3655 check_command_args(command, command->first_argument, copy_file_args,
3656 sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3657
3658 DBUG_PRINT("info",
3659 ("Copy files of %s to %s", ds_source.str, ds_destination.str));
3660
3661 DBUG_PRINT("info", ("listing directory: %s", ds_source.str));
3662
3663 int error = 0;
3664
3665 // Check if the retry value is passed, and if it is an integer
3666 int retry = 0;
3667 if (ds_retry.length) {
3668 retry = get_int_val(ds_retry.str);
3669 if (retry < 0) {
3670 // In case of invalid retry, copy the value passed to print later
3671 char buf[32];
3672 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3673 free_dynamic_strings(&ds_source, &ds_destination, &ds_wild, &ds_retry);
3674 die("Invalid value '%s' for retry argument given to "
3675 "copy_files_wildcard command.",
3676 buf);
3677 }
3678 }
3679
3680 /* Note that my_dir sorts the list if not given any flags */
3681 MY_DIR *dir_info = my_dir(ds_source.str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3682
3683 /* Directory does not exist or access denied */
3684 if (!dir_info) {
3685 error = 1;
3686 goto end;
3687 }
3688
3689 /* The directory exists but is empty */
3690 if (dir_info->number_off_files == 2) {
3691 error = 1;
3692 set_my_errno(ENOENT);
3693 goto end;
3694 }
3695
3696 char dir_separator[2];
3697 dir_separator[0] = FN_LIBCHAR;
3698 dir_separator[1] = 0;
3699 dynstr_append(&ds_source, dir_separator);
3700 dynstr_append(&ds_destination, dir_separator);
3701
3702 /* Storing the length of the path to the file, so it can be reused */
3703 size_t source_file_length;
3704 size_t dest_file_length;
3705 dest_file_length = ds_destination.length;
3706 source_file_length = ds_source.length;
3707 uint match_count;
3708 match_count = 0;
3709
3710 for (uint i = 0; i < dir_info->number_off_files; i++) {
3711 ds_source.length = source_file_length;
3712 ds_destination.length = dest_file_length;
3713 FILEINFO *file = dir_info->dir_entry + i;
3714
3715 /*
3716 Copy only regular files, i.e. no directories etc.
3717 if (!MY_S_ISREG(file->mystat->st_mode))
3718 MY_S_ISREG does not work here on Windows, just skip directories
3719 */
3720 if (MY_S_ISDIR(file->mystat->st_mode)) continue;
3721
3722 /* Copy only those files which the pattern matches */
3723 if (wild_compare_full(file->name, std::strlen(file->name), ds_wild.str,
3724 std::strlen(ds_wild.str), false, 0, '?', '*'))
3725 continue;
3726
3727 match_count++;
3728 dynstr_append(&ds_source, file->name);
3729 dynstr_append(&ds_destination, file->name);
3730 DBUG_PRINT("info",
3731 ("Copying file: %s to %s", ds_source.str, ds_destination.str));
3732
3733 /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3734 error = (my_copy(ds_source.str, ds_destination.str,
3735 MYF(MY_HOLD_ORIGINAL_MODES)) != 0);
3736
3737 /*
3738 If the copy command fails due to an environmental issue, the command can
3739 be retried a specified number of times before throwing an error.
3740 */
3741 for (int j = 0; error && (j < retry); j++) {
3742 my_sleep(1000 * 1000);
3743 error =
3744 (my_copy(ds_source.str, ds_destination.str,
3745 MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3746 }
3747
3748 if (error) goto end;
3749 }
3750
3751 /* Pattern did not match any files */
3752 if (!match_count) {
3753 error = 1;
3754 set_my_errno(ENOENT);
3755 }
3756
3757 end:
3758 my_dirend(dir_info);
3759 handle_command_error(command, error);
3760 free_dynamic_strings(&ds_source, &ds_destination, &ds_wild, &ds_retry);
3761 }
3762
3763 /*
3764 SYNOPSIS
3765 move_file_by_copy_delete
3766 from path of source
3767 to path of destination
3768
3769 DESCRIPTION
3770 Move <from_file> to <to_file>
3771 Auxiliary function for copying <from_file> to <to_file> followed by
3772 deleting <to_file>.
3773 */
3774
move_file_by_copy_delete(const char * from,const char * to)3775 static int move_file_by_copy_delete(const char *from, const char *to) {
3776 int error_copy, error_delete;
3777 error_copy = (my_copy(from, to, MYF(MY_HOLD_ORIGINAL_MODES)) != 0);
3778 if (error_copy) {
3779 return error_copy;
3780 }
3781
3782 error_delete = my_delete(from, MYF(0)) != 0;
3783
3784 /*
3785 If deleting the source file fails, rollback by deleting the
3786 redundant copy at the destinatiion.
3787 */
3788 if (error_delete) {
3789 my_delete(to, MYF(0));
3790 }
3791 return error_delete;
3792 }
3793
3794 /// Moves a file to destination file. Retry of the command can happen
3795 /// optionally with an interval of one second between each retry if
3796 /// the command fails.
3797 ///
3798 /// @param command Pointer to the st_command structure which holds the
3799 /// arguments and information for the command.
do_move_file(struct st_command * command)3800 static void do_move_file(struct st_command *command) {
3801 int error;
3802 static DYNAMIC_STRING ds_from_file;
3803 static DYNAMIC_STRING ds_to_file;
3804 static DYNAMIC_STRING ds_retry;
3805
3806 const struct command_arg move_file_args[] = {
3807 {"from_file", ARG_STRING, true, &ds_from_file, "Filename to move from"},
3808 {"to_file", ARG_STRING, true, &ds_to_file, "Filename to move to"},
3809 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3810 DBUG_TRACE;
3811
3812 check_command_args(command, command->first_argument, move_file_args,
3813 sizeof(move_file_args) / sizeof(struct command_arg), ' ');
3814
3815 // Check if the retry value is passed, and if it is an interger
3816 int retry = 0;
3817 if (ds_retry.length) {
3818 retry = get_int_val(ds_retry.str);
3819 if (retry < 0) {
3820 // In case of invalid retry, copy the value passed to print later
3821 char buf[32];
3822 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3823 free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3824 die("Invalid value '%s' for retry argument given to move_file "
3825 "command.",
3826 buf);
3827 }
3828 }
3829
3830 DBUG_PRINT("info", ("Move %s to %s", ds_from_file.str, ds_to_file.str));
3831 error = (my_rename(ds_from_file.str, ds_to_file.str, MYF(0)) != 0);
3832
3833 /*
3834 Use my_copy() followed by my_delete() for moving a file instead of
3835 my_rename() when my_errno is EXDEV. This is because my_rename() fails
3836 with the error "Invalid cross-device link" while moving a file between
3837 locations having different filesystems in some operating systems.
3838 */
3839 if (error && (my_errno() == EXDEV)) {
3840 error = move_file_by_copy_delete(ds_from_file.str, ds_to_file.str);
3841 }
3842
3843 /*
3844 If the command fails due to an environmental issue, the command can be
3845 retried a specified number of times before throwing an error.
3846 */
3847 for (int i = 0; error && (i < retry); i++) {
3848 my_sleep(1000 * 1000);
3849 error = (my_rename(ds_from_file.str, ds_to_file.str, MYF(0)) != 0);
3850
3851 if (error && (my_errno() == EXDEV))
3852 error = move_file_by_copy_delete(ds_from_file.str, ds_to_file.str);
3853 }
3854
3855 handle_command_error(command, error);
3856 free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3857 }
3858
3859 /*
3860 SYNOPSIS
3861 do_chmod_file
3862 command command handle
3863
3864 DESCRIPTION
3865 chmod <octal> <file_name>
3866 Change file permission of <file_name>
3867
3868 */
3869
do_chmod_file(struct st_command * command)3870 static void do_chmod_file(struct st_command *command) {
3871 long mode = 0;
3872 int err_code;
3873 static DYNAMIC_STRING ds_mode;
3874 static DYNAMIC_STRING ds_file;
3875 const struct command_arg chmod_file_args[] = {
3876 {"mode", ARG_STRING, true, &ds_mode, "Mode of file(octal) ex. 0660"},
3877 {"filename", ARG_STRING, true, &ds_file, "Filename of file to modify"}};
3878 DBUG_TRACE;
3879
3880 check_command_args(command, command->first_argument, chmod_file_args,
3881 sizeof(chmod_file_args) / sizeof(struct command_arg), ' ');
3882
3883 /* Parse what mode to set */
3884 if (ds_mode.length != 4 ||
3885 str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS)
3886 die("You must write a 4 digit octal number for mode");
3887
3888 DBUG_PRINT("info", ("chmod %o %s", (uint)mode, ds_file.str));
3889 err_code = chmod(ds_file.str, mode);
3890 if (err_code < 0) err_code = 1;
3891 handle_command_error(command, err_code);
3892 dynstr_free(&ds_mode);
3893 dynstr_free(&ds_file);
3894 }
3895
3896 /// Check if specified file exists. Retry of the command can happen
3897 /// optionally with an interval of one second between each retry if
3898 /// the command fails.
3899 ///
3900 /// @param command Pointer to the st_command structure which holds the
3901 /// arguments and information for the command.
do_file_exist(struct st_command * command)3902 static void do_file_exist(struct st_command *command) {
3903 int error;
3904 static DYNAMIC_STRING ds_filename;
3905 static DYNAMIC_STRING ds_retry;
3906
3907 const struct command_arg file_exist_args[] = {
3908 {"filename", ARG_STRING, true, &ds_filename, "File to check if it exist"},
3909 {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3910 DBUG_TRACE;
3911
3912 check_command_args(command, command->first_argument, file_exist_args,
3913 sizeof(file_exist_args) / sizeof(struct command_arg), ' ');
3914
3915 // Check if the retry value is passed, and if it is an interger
3916 int retry = 0;
3917 if (ds_retry.length) {
3918 retry = get_int_val(ds_retry.str);
3919 if (retry < 0) {
3920 // In case of invalid retry, copy the value passed to print later
3921 char buf[32];
3922 strmake(buf, ds_retry.str, sizeof(buf) - 1);
3923 free_dynamic_strings(&ds_filename, &ds_retry);
3924 die("Invalid value '%s' for retry argument given to file_exists "
3925 "command.",
3926 buf);
3927 }
3928 }
3929
3930 DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str));
3931 error = (access(ds_filename.str, F_OK) != 0);
3932
3933 /*
3934 If the file_exists command fails due to an environmental issue, the command
3935 can be retried a specified number of times before throwing an error.
3936 */
3937 for (int i = 0; error && (i < retry); i++) {
3938 my_sleep(1000 * 1000);
3939 error = (access(ds_filename.str, F_OK) != 0);
3940 }
3941
3942 handle_command_error(command, error);
3943 free_dynamic_strings(&ds_filename, &ds_retry);
3944 }
3945
3946 /*
3947 SYNOPSIS
3948 do_mkdir
3949 command called command
3950
3951 DESCRIPTION
3952 mkdir <dir_name>
3953 Create the directory <dir_name>
3954 */
3955
do_mkdir(struct st_command * command)3956 static void do_mkdir(struct st_command *command) {
3957 int error;
3958 static DYNAMIC_STRING ds_dirname;
3959 const struct command_arg mkdir_args[] = {
3960 {"dirname", ARG_STRING, true, &ds_dirname, "Directory to create"}};
3961 DBUG_TRACE;
3962
3963 check_command_args(command, command->first_argument, mkdir_args,
3964 sizeof(mkdir_args) / sizeof(struct command_arg), ' ');
3965
3966 DBUG_PRINT("info", ("creating directory: %s", ds_dirname.str));
3967 error = my_mkdir(ds_dirname.str, 0777, MYF(0)) != 0;
3968 handle_command_error(command, error);
3969 dynstr_free(&ds_dirname);
3970 }
3971
3972 /*
3973 SYNOPSIS
3974 do_force_rmdir
3975 command - command handle
3976 ds_dirname - pointer to dynamic string containing directory informtion
3977
3978 DESCRIPTION
3979 force-rmdir <dir_name>
3980 Remove the directory <dir_name>
3981 */
3982
do_force_rmdir(struct st_command * command,DYNAMIC_STRING * ds_dirname)3983 void do_force_rmdir(struct st_command *command, DYNAMIC_STRING *ds_dirname) {
3984 DBUG_TRACE;
3985
3986 char dir_name[FN_REFLEN + 1];
3987 strncpy(dir_name, ds_dirname->str, sizeof(dir_name) - 1);
3988 dir_name[FN_REFLEN] = '\0';
3989
3990 /* Note that my_dir sorts the list if not given any flags */
3991 MY_DIR *dir_info = my_dir(ds_dirname->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3992
3993 if (dir_info && dir_info->number_off_files > 2) {
3994 /* Storing the length of the path to the file, so it can be reused */
3995 size_t length = ds_dirname->length;
3996
3997 /* Delete the directory recursively */
3998 for (uint i = 0; i < dir_info->number_off_files; i++) {
3999 FILEINFO *file = dir_info->dir_entry + i;
4000
4001 /* Skip the names "." and ".." */
4002 if (!std::strcmp(file->name, ".") || !std::strcmp(file->name, ".."))
4003 continue;
4004
4005 ds_dirname->length = length;
4006 char dir_separator[2] = {FN_LIBCHAR, 0};
4007 dynstr_append(ds_dirname, dir_separator);
4008 dynstr_append(ds_dirname, file->name);
4009
4010 if (MY_S_ISDIR(file->mystat->st_mode)) /* It's a directory */
4011 do_force_rmdir(command, ds_dirname);
4012 else
4013 /* It's a file */
4014 my_delete(ds_dirname->str, MYF(0));
4015 }
4016 }
4017
4018 my_dirend(dir_info);
4019 int error = rmdir(dir_name) != 0;
4020 set_my_errno(errno);
4021 handle_command_error(command, error);
4022 }
4023
4024 /*
4025 SYNOPSIS
4026 do_rmdir
4027 command called command
4028 force Recursively delete a directory if the value is set to true,
4029 otherwise delete an empty direcory
4030
4031 DESCRIPTION
4032 rmdir <dir_name>
4033 Remove the empty directory <dir_name>
4034 */
4035
do_rmdir(struct st_command * command,bool force)4036 static void do_rmdir(struct st_command *command, bool force) {
4037 int error;
4038 static DYNAMIC_STRING ds_dirname;
4039 const struct command_arg rmdir_args[] = {
4040 {"dirname", ARG_STRING, true, &ds_dirname, "Directory to remove"}};
4041 DBUG_TRACE;
4042
4043 check_command_args(command, command->first_argument, rmdir_args,
4044 sizeof(rmdir_args) / sizeof(struct command_arg), ' ');
4045
4046 DBUG_PRINT("info", ("removing directory: %s", ds_dirname.str));
4047 if (force)
4048 do_force_rmdir(command, &ds_dirname);
4049 else {
4050 error = rmdir(ds_dirname.str) != 0;
4051 set_my_errno(errno);
4052 handle_command_error(command, error);
4053 }
4054 dynstr_free(&ds_dirname);
4055 }
4056
4057 /*
4058 SYNOPSIS
4059 get_list_files
4060 ds output
4061 ds_dirname dir to list
4062 ds_wild wild-card file pattern (can be empty)
4063
4064 DESCRIPTION
4065 list all entries in directory (matching ds_wild if given)
4066 */
4067
get_list_files(DYNAMIC_STRING * ds,const DYNAMIC_STRING * ds_dirname,const DYNAMIC_STRING * ds_wild)4068 static int get_list_files(DYNAMIC_STRING *ds, const DYNAMIC_STRING *ds_dirname,
4069 const DYNAMIC_STRING *ds_wild) {
4070 uint i;
4071 MY_DIR *dir_info;
4072 FILEINFO *file;
4073 DBUG_TRACE;
4074
4075 DBUG_PRINT("info", ("listing directory: %s", ds_dirname->str));
4076 /* Note that my_dir sorts the list if not given any flags */
4077 if (!(dir_info = my_dir(ds_dirname->str, MYF(0)))) return 1;
4078 for (i = 0; i < (uint)dir_info->number_off_files; i++) {
4079 file = dir_info->dir_entry + i;
4080 if (file->name[0] == '.' &&
4081 (file->name[1] == '\0' ||
4082 (file->name[1] == '.' && file->name[2] == '\0')))
4083 continue; /* . or .. */
4084 if (ds_wild && ds_wild->length &&
4085 wild_compare_full(file->name, std::strlen(file->name), ds_wild->str,
4086 std::strlen(ds_wild->str), false, 0, '?', '*'))
4087 continue;
4088 replace_dynstr_append(ds, file->name);
4089 dynstr_append(ds, "\n");
4090 }
4091 my_dirend(dir_info);
4092 return 0;
4093 }
4094
4095 /*
4096 SYNOPSIS
4097 do_list_files
4098 command called command
4099
4100 DESCRIPTION
4101 list_files <dir_name> [<file_name>]
4102 List files and directories in directory <dir_name> (like `ls`)
4103 [Matching <file_name>, where wild-cards are allowed]
4104 */
4105
do_list_files(struct st_command * command)4106 static void do_list_files(struct st_command *command) {
4107 int error;
4108 static DYNAMIC_STRING ds_dirname;
4109 static DYNAMIC_STRING ds_wild;
4110 const struct command_arg list_files_args[] = {
4111 {"dirname", ARG_STRING, true, &ds_dirname, "Directory to list"},
4112 {"file", ARG_STRING, false, &ds_wild, "Filename (incl. wildcard)"}};
4113 DBUG_TRACE;
4114 command->used_replace = true;
4115
4116 check_command_args(command, command->first_argument, list_files_args,
4117 sizeof(list_files_args) / sizeof(struct command_arg), ' ');
4118
4119 error = get_list_files(&ds_res, &ds_dirname, &ds_wild);
4120 handle_command_error(command, error);
4121 dynstr_free(&ds_dirname);
4122 dynstr_free(&ds_wild);
4123 }
4124
4125 /*
4126 SYNOPSIS
4127 do_list_files_write_file_command
4128 command called command
4129 append append file, or create new
4130
4131 DESCRIPTION
4132 list_files_{write|append}_file <filename> <dir_name> [<match_file>]
4133 List files and directories in directory <dir_name> (like `ls`)
4134 [Matching <match_file>, where wild-cards are allowed]
4135
4136 Note: File will be truncated if exists and append is not true.
4137 */
4138
do_list_files_write_file_command(struct st_command * command,bool append)4139 static void do_list_files_write_file_command(struct st_command *command,
4140 bool append) {
4141 int error;
4142 static DYNAMIC_STRING ds_content;
4143 static DYNAMIC_STRING ds_filename;
4144 static DYNAMIC_STRING ds_dirname;
4145 static DYNAMIC_STRING ds_wild;
4146 const struct command_arg list_files_args[] = {
4147 {"filename", ARG_STRING, true, &ds_filename, "Filename for write"},
4148 {"dirname", ARG_STRING, true, &ds_dirname, "Directory to list"},
4149 {"file", ARG_STRING, false, &ds_wild, "Filename (incl. wildcard)"}};
4150 DBUG_TRACE;
4151 command->used_replace = true;
4152
4153 check_command_args(command, command->first_argument, list_files_args,
4154 sizeof(list_files_args) / sizeof(struct command_arg), ' ');
4155
4156 init_dynamic_string(&ds_content, "", 1024, 1024);
4157 error = get_list_files(&ds_content, &ds_dirname, &ds_wild);
4158 handle_command_error(command, error);
4159 str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append);
4160 dynstr_free(&ds_content);
4161 dynstr_free(&ds_filename);
4162 dynstr_free(&ds_dirname);
4163 dynstr_free(&ds_wild);
4164 }
4165
4166 /*
4167 Read characters from line buffer or file. This is needed to allow
4168 my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file
4169
4170 NOTE:
4171 This works as long as one doesn't change files (with 'source file_name')
4172 when there is things pushed into the buffer. This should however not
4173 happen for any tests in the test suite.
4174 */
4175
my_getc(FILE * file)4176 static int my_getc(FILE *file) {
4177 if (line_buffer_pos == line_buffer) return fgetc(file);
4178 return *--line_buffer_pos;
4179 }
4180
my_ungetc(int c)4181 static void my_ungetc(int c) { *line_buffer_pos++ = (char)c; }
4182
read_until_delimiter(DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_delimiter)4183 static void read_until_delimiter(DYNAMIC_STRING *ds,
4184 DYNAMIC_STRING *ds_delimiter) {
4185 char c;
4186 DBUG_TRACE;
4187 DBUG_PRINT("enter", ("delimiter: %s, length: %u", ds_delimiter->str,
4188 (uint)ds_delimiter->length));
4189
4190 if (ds_delimiter->length > MAX_DELIMITER_LENGTH)
4191 die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH);
4192
4193 /* Read from file until delimiter is found */
4194 while (true) {
4195 c = my_getc(cur_file->file);
4196
4197 if (c == '\n') {
4198 cur_file->lineno++;
4199
4200 /* Skip newline from the same line as the command */
4201 if (start_lineno == (cur_file->lineno - 1)) continue;
4202 } else if (start_lineno == cur_file->lineno) {
4203 /*
4204 No characters except \n are allowed on
4205 the same line as the command
4206 */
4207 die("Trailing characters found after command");
4208 }
4209
4210 if (feof(cur_file->file))
4211 die("End of file encountered before '%s' delimiter was found",
4212 ds_delimiter->str);
4213
4214 if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) {
4215 DBUG_PRINT("exit", ("Found delimiter '%s'", ds_delimiter->str));
4216 break;
4217 }
4218 dynstr_append_mem(ds, (const char *)&c, 1);
4219 }
4220 DBUG_PRINT("exit", ("ds: %s", ds->str));
4221 }
4222
do_write_file_command(struct st_command * command,bool append)4223 static void do_write_file_command(struct st_command *command, bool append) {
4224 static DYNAMIC_STRING ds_content;
4225 static DYNAMIC_STRING ds_filename;
4226 static DYNAMIC_STRING ds_delimiter;
4227 const struct command_arg write_file_args[] = {
4228 {"filename", ARG_STRING, true, &ds_filename, "File to write to"},
4229 {"delimiter", ARG_STRING, false, &ds_delimiter,
4230 "Delimiter to read until"}};
4231 DBUG_TRACE;
4232
4233 check_command_args(command, command->first_argument, write_file_args,
4234 sizeof(write_file_args) / sizeof(struct command_arg), ' ');
4235
4236 if (!append && access(ds_filename.str, F_OK) == 0) {
4237 /* The file should not be overwritten */
4238 die("File already exist: '%s'", ds_filename.str);
4239 }
4240
4241 ds_content = command->content;
4242 /* If it hasn't been done already by a loop iteration, fill it in */
4243 if (!ds_content.str) {
4244 /* If no delimiter was provided, use EOF */
4245 if (ds_delimiter.length == 0) dynstr_set(&ds_delimiter, "EOF");
4246
4247 init_dynamic_string(&ds_content, "", 1024, 1024);
4248 read_until_delimiter(&ds_content, &ds_delimiter);
4249 command->content = ds_content;
4250 }
4251 /* This function could be called even if "false", so check before printing */
4252 if (cur_block->ok) {
4253 DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str));
4254 str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append);
4255 }
4256 dynstr_free(&ds_filename);
4257 dynstr_free(&ds_delimiter);
4258 }
4259
4260 /*
4261 SYNOPSIS
4262 do_write_file
4263 command called command
4264
4265 DESCRIPTION
4266 write_file <file_name> [<delimiter>];
4267 <what to write line 1>
4268 <...>
4269 < what to write line n>
4270 EOF
4271
4272 --write_file <file_name>;
4273 <what to write line 1>
4274 <...>
4275 < what to write line n>
4276 EOF
4277
4278 Write everything between the "write_file" command and 'delimiter'
4279 to "file_name"
4280
4281 NOTE! Will fail if <file_name> exists
4282
4283 Default <delimiter> is EOF
4284
4285 */
4286
do_write_file(struct st_command * command)4287 static void do_write_file(struct st_command *command) {
4288 do_write_file_command(command, false);
4289 }
4290
4291 /*
4292 SYNOPSIS
4293 do_append_file
4294 command called command
4295
4296 DESCRIPTION
4297 append_file <file_name> [<delimiter>];
4298 <what to write line 1>
4299 <...>
4300 < what to write line n>
4301 EOF
4302
4303 --append_file <file_name>;
4304 <what to write line 1>
4305 <...>
4306 < what to write line n>
4307 EOF
4308
4309 Append everything between the "append_file" command
4310 and 'delimiter' to "file_name"
4311
4312 Default <delimiter> is EOF
4313
4314 */
4315
do_append_file(struct st_command * command)4316 static void do_append_file(struct st_command *command) {
4317 do_write_file_command(command, true);
4318 }
4319
4320 /*
4321 SYNOPSIS
4322 do_cat_file
4323 command called command
4324
4325 DESCRIPTION
4326 cat_file <file_name>;
4327
4328 Print the given file to result log
4329
4330 */
4331
do_cat_file(struct st_command * command)4332 static void do_cat_file(struct st_command *command) {
4333 int error;
4334 static DYNAMIC_STRING ds_filename;
4335 const struct command_arg cat_file_args[] = {
4336 {"filename", ARG_STRING, true, &ds_filename, "File to read from"}};
4337 DBUG_TRACE;
4338 command->used_replace = true;
4339
4340 check_command_args(command, command->first_argument, cat_file_args,
4341 sizeof(cat_file_args) / sizeof(struct command_arg), ' ');
4342
4343 DBUG_PRINT("info", ("Reading from, file: %s", ds_filename.str));
4344
4345 error = cat_file(&ds_res, ds_filename.str);
4346 handle_command_error(command, error);
4347 dynstr_free(&ds_filename);
4348 }
4349
4350 /// Compare the two files.
4351 ///
4352 /// Success if 2 files are same, failure if the files are different or
4353 /// either file does not exist.
4354 ///
4355 /// @code
4356 /// diff_files <file1> <file2>;
4357 /// @endcode
4358 ///
4359 /// @param command Pointer to the st_command structure which holds the
4360 /// arguments and information for the command.
do_diff_files(struct st_command * command)4361 static void do_diff_files(struct st_command *command) {
4362 static DYNAMIC_STRING ds_filename1;
4363 static DYNAMIC_STRING ds_filename2;
4364
4365 const struct command_arg diff_file_args[] = {
4366 {"file1", ARG_STRING, true, &ds_filename1, "First file to diff"},
4367 {"file2", ARG_STRING, true, &ds_filename2, "Second file to diff"}};
4368
4369 check_command_args(command, command->first_argument, diff_file_args,
4370 sizeof(diff_file_args) / sizeof(struct command_arg), ' ');
4371
4372 if (access(ds_filename1.str, F_OK) != 0)
4373 die("Command \"diff_files\" failed, file '%s' does not exist.",
4374 ds_filename1.str);
4375
4376 if (access(ds_filename2.str, F_OK) != 0)
4377 die("Command \"diff_files\" failed, file '%s' does not exist.",
4378 ds_filename2.str);
4379
4380 int error = 0;
4381 if ((error = compare_files(ds_filename1.str, ds_filename2.str)) &&
4382 match_expected_error(command, error, nullptr) < 0) {
4383 // Compare of the two files failed, append them to output so the
4384 // failure can be analyzed, but only if it was not expected to fail.
4385 show_diff(&ds_res, ds_filename1.str, ds_filename2.str);
4386 if (log_file.write(ds_res.str, ds_res.length) || log_file.flush())
4387 cleanup_and_exit(1);
4388 dynstr_set(&ds_res, nullptr);
4389 }
4390
4391 free_dynamic_strings(&ds_filename1, &ds_filename2);
4392 handle_command_error(command, error);
4393 }
4394
find_connection_by_name(const char * name)4395 static struct st_connection *find_connection_by_name(const char *name) {
4396 struct st_connection *con;
4397 for (con = connections; con < next_con; con++) {
4398 if (!std::strcmp(con->name, name)) {
4399 return con;
4400 }
4401 }
4402 return nullptr; /* Connection not found */
4403 }
4404
4405 /*
4406 SYNOPSIS
4407 do_send_quit
4408 command called command
4409
4410 DESCRIPTION
4411 Sends a simple quit command to the server for the named connection.
4412
4413 */
4414
do_send_quit(struct st_command * command)4415 static void do_send_quit(struct st_command *command) {
4416 char *p = command->first_argument;
4417 const char *name;
4418 struct st_connection *con;
4419
4420 DBUG_TRACE;
4421 DBUG_PRINT("enter", ("name: '%s'", p));
4422
4423 if (!*p) die("Missing connection name in send_quit");
4424 name = p;
4425 while (*p && !my_isspace(charset_info, *p)) p++;
4426
4427 if (*p) *p++ = 0;
4428 command->last_argument = p;
4429
4430 if (!(con = find_connection_by_name(name)))
4431 die("connection '%s' not found in connection pool", name);
4432
4433 simple_command(&con->mysql, COM_QUIT, nullptr, 0, 1);
4434 }
4435
4436 /*
4437 SYNOPSIS
4438 do_change_user
4439 command called command
4440
4441 DESCRIPTION
4442 change_user [<user>], [<passwd>], [<db>]
4443 <user> - user to change to
4444 <passwd> - user password
4445 <db> - default database
4446
4447 Changes the user and causes the database specified by db to become
4448 the default (current) database for the the current connection.
4449
4450 */
4451
do_change_user(struct st_command * command)4452 static void do_change_user(struct st_command *command) {
4453 MYSQL *mysql = &cur_con->mysql;
4454 static DYNAMIC_STRING ds_user, ds_passwd, ds_db;
4455 const struct command_arg change_user_args[] = {
4456 {"user", ARG_STRING, false, &ds_user, "User to connect as"},
4457 {"password", ARG_STRING, false, &ds_passwd,
4458 "Password used when connecting"},
4459 {"database", ARG_STRING, false, &ds_db,
4460 "Database to select after connect"},
4461 };
4462
4463 DBUG_TRACE;
4464
4465 check_command_args(command, command->first_argument, change_user_args,
4466 sizeof(change_user_args) / sizeof(struct command_arg),
4467 ',');
4468
4469 if (cur_con->stmt) {
4470 mysql_stmt_close(cur_con->stmt);
4471 cur_con->stmt = nullptr;
4472 }
4473
4474 if (!ds_user.length) {
4475 dynstr_set(&ds_user, mysql->user);
4476
4477 if (!ds_passwd.length) dynstr_set(&ds_passwd, mysql->passwd);
4478
4479 if (!ds_db.length) dynstr_set(&ds_db, mysql->db);
4480 }
4481
4482 DBUG_PRINT("info",
4483 ("connection: '%s' user: '%s' password: '%s' database: '%s'",
4484 cur_con->name, ds_user.str, ds_passwd.str, ds_db.str));
4485
4486 if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) {
4487 handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
4488 mysql_sqlstate(mysql), &ds_res);
4489 mysql->reconnect = true;
4490 mysql_reconnect(&cur_con->mysql);
4491 }
4492
4493 dynstr_free(&ds_user);
4494 dynstr_free(&ds_passwd);
4495 dynstr_free(&ds_db);
4496 }
4497
4498 /*
4499 SYNOPSIS
4500 do_perl
4501 command command handle
4502
4503 DESCRIPTION
4504 perl [<delimiter>];
4505 <perlscript line 1>
4506 <...>
4507 <perlscript line n>
4508 EOF
4509
4510 Execute everything after "perl" until <delimiter> as perl.
4511 Useful for doing more advanced things
4512 but still being able to execute it on all platforms.
4513
4514 Default <delimiter> is EOF
4515 */
4516
do_perl(struct st_command * command)4517 static void do_perl(struct st_command *command) {
4518 int error;
4519 File fd;
4520 FILE *res_file;
4521 char buf[FN_REFLEN + 10];
4522 char temp_file_path[FN_REFLEN];
4523 static DYNAMIC_STRING ds_script;
4524 static DYNAMIC_STRING ds_delimiter;
4525 const struct command_arg perl_args[] = {{"delimiter", ARG_STRING, false,
4526 &ds_delimiter,
4527 "Delimiter to read until"}};
4528 DBUG_TRACE;
4529
4530 check_command_args(command, command->first_argument, perl_args,
4531 sizeof(perl_args) / sizeof(struct command_arg), ' ');
4532
4533 ds_script = command->content;
4534 /* If it hasn't been done already by a loop iteration, fill it in */
4535 if (!ds_script.str) {
4536 /* If no delimiter was provided, use EOF */
4537 if (ds_delimiter.length == 0) dynstr_set(&ds_delimiter, "EOF");
4538
4539 init_dynamic_string(&ds_script, "", 1024, 1024);
4540 read_until_delimiter(&ds_script, &ds_delimiter);
4541 command->content = ds_script;
4542 }
4543
4544 /* This function could be called even if "false", so check before doing */
4545 if (cur_block->ok) {
4546 DBUG_PRINT("info", ("Executing perl: %s", ds_script.str));
4547
4548 /* Create temporary file name */
4549 if ((fd =
4550 create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), "tmp",
4551 O_CREAT | O_RDWR, KEEP_FILE, MYF(MY_WME))) < 0)
4552 die("Failed to create temporary file for perl command");
4553 my_close(fd, MYF(0));
4554
4555 /* Compatibility for Perl 5.24 and newer. */
4556 std::string script = "push @INC, \".\";\n";
4557 script.append(ds_script.str, ds_script.length);
4558
4559 str_to_file(temp_file_path, &script[0], script.size());
4560
4561 /* Format the "perl <filename>" command */
4562 snprintf(buf, sizeof(buf), "perl %s", temp_file_path);
4563
4564 if (!(res_file = popen(buf, "r")) && command->abort_on_error)
4565 die("popen(\"%s\", \"r\") failed", buf);
4566
4567 while (fgets(buf, sizeof(buf), res_file)) {
4568 if (disable_result_log) {
4569 buf[std::strlen(buf) - 1] = 0;
4570 DBUG_PRINT("exec_result", ("%s", buf));
4571 } else {
4572 replace_dynstr_append(&ds_res, buf);
4573 }
4574 }
4575 error = pclose(res_file);
4576
4577 /* Remove the temporary file, but keep it if perl failed */
4578 if (!error) my_delete(temp_file_path, MYF(0));
4579
4580 /* Check for error code that indicates perl could not be started */
4581 int exstat = WEXITSTATUS(error);
4582 #ifdef _WIN32
4583 if (exstat == 1) /* Text must begin 'perl not found' as mtr looks for it */
4584 abort_not_supported_test("perl not found in path or did not start");
4585 #else
4586 if (exstat == 127) abort_not_supported_test("perl not found in path");
4587 #endif
4588 else
4589 handle_command_error(command, exstat);
4590 }
4591 dynstr_free(&ds_delimiter);
4592 }
4593
4594 /*
4595 Print the content between echo and <delimiter> to result file.
4596 Evaluate all variables in the string before printing, allow
4597 for variable names to be escaped using \
4598
4599 SYNOPSIS
4600 do_echo()
4601 command called command
4602
4603 DESCRIPTION
4604 echo text
4605 Print the text after echo until end of command to result file
4606
4607 echo $<var_name>
4608 Print the content of the variable <var_name> to result file
4609
4610 echo Some text $<var_name>
4611 Print "Some text" plus the content of the variable <var_name> to
4612 result file
4613
4614 echo Some text \$<var_name>
4615 Print "Some text" plus $<var_name> to result file
4616 */
4617
do_echo(struct st_command * command)4618 static int do_echo(struct st_command *command) {
4619 DYNAMIC_STRING ds_echo;
4620 DBUG_TRACE;
4621
4622 init_dynamic_string(&ds_echo, "", command->query_len, 256);
4623 do_eval(&ds_echo, command->first_argument, command->end, false);
4624 dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length);
4625 dynstr_append_mem(&ds_res, "\n", 1);
4626 dynstr_free(&ds_echo);
4627 command->last_argument = command->end;
4628 return 0;
4629 }
4630
do_wait_for_slave_to_stop(struct st_command * c MY_ATTRIBUTE ((unused)))4631 static void do_wait_for_slave_to_stop(
4632 struct st_command *c MY_ATTRIBUTE((unused))) {
4633 static int SLAVE_POLL_INTERVAL = 300000;
4634 MYSQL *mysql = &cur_con->mysql;
4635 for (;;) {
4636 MYSQL_RES *res = nullptr;
4637 MYSQL_ROW row;
4638 int done;
4639
4640 if (mysql_query_wrapper(
4641 mysql,
4642 "SELECT 'Slave_running' as Variable_name,"
4643 " IF(count(*)>0,'ON','OFF') as Value FROM"
4644 " performance_schema.replication_applier_status ras,"
4645 "performance_schema.replication_connection_status rcs WHERE "
4646 "ras.SERVICE_STATE='ON' AND rcs.SERVICE_STATE='ON'") ||
4647 !(res = mysql_store_result_wrapper(mysql)))
4648
4649 die("Query failed while probing slave for stop: %s", mysql_error(mysql));
4650
4651 if (!(row = mysql_fetch_row_wrapper(res)) || !row[1]) {
4652 mysql_free_result_wrapper(res);
4653 die("Strange result from query while probing slave for stop");
4654 }
4655 done = !std::strcmp(row[1], "OFF");
4656 mysql_free_result_wrapper(res);
4657 if (done) break;
4658 my_sleep(SLAVE_POLL_INTERVAL);
4659 }
4660 return;
4661 }
4662
do_sync_with_master2(struct st_command * command,long offset)4663 static void do_sync_with_master2(struct st_command *command, long offset) {
4664 MYSQL_RES *res;
4665 MYSQL_ROW row;
4666 MYSQL *mysql = &cur_con->mysql;
4667 char query_buf[FN_REFLEN + 128];
4668 int timeout = 300; /* seconds */
4669
4670 if (!master_pos.file[0])
4671 die("Calling 'sync_with_master' without calling 'save_master_pos'");
4672
4673 sprintf(query_buf, "select master_pos_wait('%s', %ld, %d)", master_pos.file,
4674 master_pos.pos + offset, timeout);
4675
4676 if (mysql_query_wrapper(mysql, query_buf))
4677 die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql),
4678 mysql_error(mysql));
4679
4680 if (!(res = mysql_store_result_wrapper(mysql)))
4681 die("mysql_store_result() returned NULL for '%s'", query_buf);
4682 if (!(row = mysql_fetch_row_wrapper(res))) {
4683 mysql_free_result_wrapper(res);
4684 die("empty result in %s", query_buf);
4685 }
4686
4687 int result = -99;
4688 const char *result_str = row[0];
4689 if (result_str) result = atoi(result_str);
4690
4691 mysql_free_result_wrapper(res);
4692
4693 if (!result_str || result < 0) {
4694 /* master_pos_wait returned NULL or < 0 */
4695 show_query(mysql, "SHOW MASTER STATUS");
4696 show_query(mysql, "SHOW SLAVE STATUS");
4697 show_query(mysql, "SHOW PROCESSLIST");
4698 fprintf(stderr, "analyze: sync_with_master\n");
4699
4700 if (!result_str) {
4701 /*
4702 master_pos_wait returned NULL. This indicates that
4703 slave SQL thread is not started, the slave's master
4704 information is not initialized, the arguments are
4705 incorrect, or an error has occurred
4706 */
4707 die("%.*s failed: '%s' returned NULL "
4708 "indicating slave SQL thread failure",
4709 static_cast<int>(command->first_word_len), command->query, query_buf);
4710 }
4711
4712 if (result == -1)
4713 die("%.*s failed: '%s' returned -1 "
4714 "indicating timeout after %d seconds",
4715 static_cast<int>(command->first_word_len), command->query, query_buf,
4716 timeout);
4717 else
4718 die("%.*s failed: '%s' returned unknown result :%d",
4719 static_cast<int>(command->first_word_len), command->query, query_buf,
4720 result);
4721 }
4722
4723 return;
4724 }
4725
do_sync_with_master(struct st_command * command)4726 static void do_sync_with_master(struct st_command *command) {
4727 long offset = 0;
4728 char *p = command->first_argument;
4729 const char *offset_start = p;
4730 if (*offset_start) {
4731 for (; my_isdigit(charset_info, *p); p++) offset = offset * 10 + *p - '0';
4732
4733 if (*p && !my_isspace(charset_info, *p))
4734 die("Invalid integer argument \"%s\"", offset_start);
4735 command->last_argument = p;
4736 }
4737 do_sync_with_master2(command, offset);
4738 return;
4739 }
4740
4741 /*
4742 Wait for ndb binlog injector to be up-to-date with all changes
4743 done on the local mysql server
4744 */
4745
ndb_wait_for_binlog_injector(void)4746 static void ndb_wait_for_binlog_injector(void) {
4747 MYSQL_RES *res;
4748 MYSQL_ROW row;
4749 MYSQL *mysql = &cur_con->mysql;
4750 const char *query;
4751 ulong have_ndbcluster;
4752 if (mysql_query_wrapper(
4753 mysql, query = "select count(*) from information_schema.engines"
4754 " where engine = 'ndbcluster' and"
4755 " support in ('YES', 'DEFAULT')"))
4756 die("'%s' failed: %d %s", query, mysql_errno(mysql), mysql_error(mysql));
4757 if (!(res = mysql_store_result_wrapper(mysql)))
4758 die("mysql_store_result() returned NULL for '%s'", query);
4759 if (!(row = mysql_fetch_row_wrapper(res)))
4760 die("Query '%s' returned empty result", query);
4761
4762 have_ndbcluster = std::strcmp(row[0], "1") == 0;
4763 mysql_free_result_wrapper(res);
4764
4765 if (!have_ndbcluster) {
4766 return;
4767 }
4768
4769 ulonglong start_epoch = 0, handled_epoch = 0, latest_trans_epoch = 0,
4770 latest_handled_binlog_epoch = 0, start_handled_binlog_epoch = 0;
4771 const int WaitSeconds = 150;
4772
4773 int count = 0;
4774 int do_continue = 1;
4775 while (do_continue) {
4776 const char binlog[] = "binlog";
4777 const char latest_trans_epoch_str[] = "latest_trans_epoch=";
4778 const char latest_handled_binlog_epoch_str[] =
4779 "latest_handled_binlog_epoch=";
4780 if (count) my_sleep(100 * 1000); /* 100ms */
4781
4782 if (mysql_query_wrapper(mysql, query = "show engine ndb status"))
4783 die("failed in '%s': %d %s", query, mysql_errno(mysql),
4784 mysql_error(mysql));
4785
4786 if (!(res = mysql_store_result_wrapper(mysql)))
4787 die("mysql_store_result() returned NULL for '%s'", query);
4788
4789 while ((row = mysql_fetch_row_wrapper(res))) {
4790 if (std::strcmp(row[1], binlog) == 0) {
4791 const char *status = row[2];
4792
4793 /* latest_trans_epoch */
4794 while (*status && std::strncmp(status, latest_trans_epoch_str,
4795 sizeof(latest_trans_epoch_str) - 1))
4796 status++;
4797 if (*status) {
4798 status += sizeof(latest_trans_epoch_str) - 1;
4799 latest_trans_epoch = my_strtoull(status, (char **)nullptr, 10);
4800 } else
4801 die("result does not contain '%s' in '%s'", latest_trans_epoch_str,
4802 query);
4803
4804 /* latest_handled_binlog */
4805 while (*status &&
4806 std::strncmp(status, latest_handled_binlog_epoch_str,
4807 sizeof(latest_handled_binlog_epoch_str) - 1))
4808 status++;
4809 if (*status) {
4810 status += sizeof(latest_handled_binlog_epoch_str) - 1;
4811 latest_handled_binlog_epoch =
4812 my_strtoull(status, (char **)nullptr, 10);
4813 } else
4814 die("result does not contain '%s' in '%s'",
4815 latest_handled_binlog_epoch_str, query);
4816
4817 if (count == 0) {
4818 start_epoch = latest_trans_epoch;
4819 start_handled_binlog_epoch = latest_handled_binlog_epoch;
4820 }
4821 break;
4822 }
4823 }
4824 if (!row) die("result does not contain '%s' in '%s'", binlog, query);
4825 if (latest_handled_binlog_epoch > handled_epoch) count = 0;
4826 handled_epoch = latest_handled_binlog_epoch;
4827 count++;
4828 if (latest_handled_binlog_epoch >= start_epoch)
4829 do_continue = 0;
4830 else if (count > (WaitSeconds * 10)) {
4831 die("do_save_master_pos() timed out after %u s waiting for "
4832 "last committed epoch to be applied by the "
4833 "Ndb binlog injector. "
4834 "Ndb epoch %llu/%llu to be handled. "
4835 "Last handled epoch : %llu/%llu. "
4836 "First handled epoch : %llu/%llu.",
4837 WaitSeconds, start_epoch >> 32, start_epoch & 0xffffffff,
4838 latest_handled_binlog_epoch >> 32,
4839 latest_handled_binlog_epoch & 0xffffffff,
4840 start_handled_binlog_epoch >> 32,
4841 start_handled_binlog_epoch & 0xffffffff);
4842 }
4843
4844 mysql_free_result_wrapper(res);
4845 }
4846 }
4847
do_save_master_pos()4848 static int do_save_master_pos() {
4849 MYSQL_RES *res;
4850 MYSQL_ROW row;
4851 MYSQL *mysql = &cur_con->mysql;
4852 const char *query;
4853 DBUG_TRACE;
4854 /*
4855 when ndb binlog is on, this call will wait until last updated epoch
4856 (locally in the mysqld) has been received into the binlog
4857 */
4858 ndb_wait_for_binlog_injector();
4859
4860 if (mysql_query_wrapper(mysql, query = "show master status"))
4861 die("failed in 'show master status': %d %s", mysql_errno(mysql),
4862 mysql_error(mysql));
4863
4864 if (!(res = mysql_store_result_wrapper(mysql)))
4865 die("mysql_store_result() retuned NULL for '%s'", query);
4866 if (!(row = mysql_fetch_row_wrapper(res)))
4867 die("empty result in show master status");
4868 my_stpnmov(master_pos.file, row[0], sizeof(master_pos.file) - 1);
4869 master_pos.pos = strtoul(row[1], (char **)nullptr, 10);
4870 mysql_free_result_wrapper(res);
4871 return 0;
4872 }
4873
4874 /*
4875 Check if a variable name is valid or not.
4876
4877 SYNOPSIS
4878 check_variable_name()
4879 var_name - pointer to the beginning of variable name
4880 var_name_end - pointer to the end of variable name
4881 dollar_flag - flag to check whether variable name should start with '$'
4882 */
check_variable_name(const char * var_name,const char * var_name_end,const bool dollar_flag)4883 static void check_variable_name(const char *var_name, const char *var_name_end,
4884 const bool dollar_flag) {
4885 char save_var_name[MAX_VAR_NAME_LENGTH];
4886 strmake(save_var_name, var_name, (var_name_end - var_name));
4887
4888 // Check if variable name should start with '$'
4889 if (!dollar_flag && (*var_name != '$'))
4890 die("Variable name '%s' should start with '$'", save_var_name);
4891
4892 if (*var_name == '$') var_name++;
4893
4894 // Check if variable name exists or not
4895 if (var_name == var_name_end) die("Missing variable name.");
4896
4897 // Check for non alphanumeric character(s) in variable name
4898 while ((var_name != var_name_end) && my_isvar(charset_info, *var_name))
4899 var_name++;
4900
4901 if (var_name != var_name_end)
4902 die("Invalid variable name '%s'", save_var_name);
4903 }
4904
4905 /*
4906 Check if the pointer points to an operator.
4907
4908 SYNOPSIS
4909 is_operator()
4910 op - character pointer to mathematical expression
4911 */
is_operator(const char * op)4912 static bool is_operator(const char *op) {
4913 if (*op == '+')
4914 return true;
4915 else if (*op == '-')
4916 return true;
4917 else if (*op == '*')
4918 return true;
4919 else if (*op == '/')
4920 return true;
4921 else if (*op == '%')
4922 return true;
4923 else if (*op == '&' && *(op + 1) == '&')
4924 return true;
4925 else if (*op == '|' && *(op + 1) == '|')
4926 return true;
4927 else if (*op == '&')
4928 return true;
4929 else if (*op == '|')
4930 return true;
4931 else if (*op == '^')
4932 return true;
4933 else if (*op == '>' && *(op + 1) == '>')
4934 return true;
4935 else if (*op == '<' && *(op + 1) == '<')
4936 return true;
4937
4938 return false;
4939 }
4940
4941 /*
4942 Perform basic mathematical operation.
4943
4944 SYNOPSIS
4945 do_expr()
4946 command - command handle
4947
4948 DESCRIPTION
4949 expr $<var_name>= <operand1> <operator> <operand2>
4950 Perform basic mathematical operation and store the result
4951 in a variable($<var_name>). Both <operand1> and <operand2>
4952 should be valid MTR variables.
4953
4954 'expr' command supports only binary operators that operates
4955 on two operands and manipulates them to return a result.
4956
4957 Following mathematical operators are supported.
4958 1 Arithmetic Operators
4959 1.1 Addition
4960 1.2 Subtraction
4961 1.3 Multiplication
4962 1.4 Division
4963 1.5 Modulo
4964
4965 2 Logical Operators
4966 2.1 Logical AND
4967 2.2 Logical OR
4968
4969 3 Bitwise Operators
4970 3.1 Binary AND
4971 3.2 Binary OR
4972 3.3 Binary XOR
4973 3.4 Binary Left Shift
4974 3.5 Binary Right Shift
4975
4976 NOTE
4977 1. Non-integer operand is truncated to integer value for operations
4978 that dont support non-integer operand.
4979 2. If the result is an infinite value, then expr command will return
4980 'inf' keyword to indicate the result is infinity.
4981 3. Division by 0 will result in an infinite value and expr command
4982 will return 'inf' keyword to indicate the result is infinity.
4983 */
do_expr(struct st_command * command)4984 static void do_expr(struct st_command *command) {
4985 DBUG_TRACE;
4986
4987 const char *p = command->first_argument;
4988 if (!*p) die("Missing arguments to expr command.");
4989
4990 // Find <var_name>
4991 const char *var_name = p;
4992 while (*p && (*p != '=') && !my_isspace(charset_info, *p)) p++;
4993 const char *var_name_end = p;
4994 check_variable_name(var_name, var_name_end, true);
4995
4996 // Skip spaces between <var_name> and '='
4997 while (my_isspace(charset_info, *p)) p++;
4998
4999 if (*p++ != '=') die("Missing assignment operator in expr command.");
5000
5001 // Skip spaces after '='
5002 while (*p && my_isspace(charset_info, *p)) p++;
5003
5004 // Save the mathematical expression in a variable
5005 const char *expr = p;
5006
5007 // First operand in the expression
5008 const char *operand_name = p;
5009 while (*p && !is_operator(p) && !my_isspace(charset_info, *p)) p++;
5010 const char *operand_name_end = p;
5011 check_variable_name(operand_name, operand_name_end, false);
5012 VAR *v1 = var_get(operand_name, &operand_name_end, false, false);
5013
5014 double operand1;
5015 if ((my_isdigit(charset_info, *v1->str_val)) ||
5016 ((*v1->str_val == '-') && my_isdigit(charset_info, *(v1->str_val + 1))))
5017 operand1 = strtod(v1->str_val, nullptr);
5018 else
5019 die("Undefined/invalid first operand '$%s' in expr command.", v1->name);
5020
5021 // Skip spaces after the first operand
5022 while (*p && my_isspace(charset_info, *p)) p++;
5023
5024 // Extract the operator
5025 const char *operator_start = p;
5026 while (*p && (*p != '$') &&
5027 !(my_isspace(charset_info, *p) || my_isvar(charset_info, *p)))
5028 p++;
5029
5030 char math_operator[3];
5031 strmake(math_operator, operator_start, (p - operator_start));
5032 if (!std::strlen(math_operator))
5033 die("Missing mathematical operator in expr command.");
5034
5035 // Skip spaces after the operator
5036 while (*p && my_isspace(charset_info, *p)) p++;
5037
5038 // Second operand in the expression
5039 operand_name = p;
5040 while (*p && !my_isspace(charset_info, *p)) p++;
5041 operand_name_end = p;
5042 check_variable_name(operand_name, operand_name_end, false);
5043 VAR *v2 = var_get(operand_name, &operand_name_end, false, false);
5044
5045 double operand2;
5046 if ((my_isdigit(charset_info, *v2->str_val)) ||
5047 ((*v2->str_val == '-') && my_isdigit(charset_info, *(v2->str_val + 1))))
5048 operand2 = strtod(v2->str_val, nullptr);
5049 else
5050 die("Undefined/invalid second operand '$%s' in expr command.", v2->name);
5051
5052 // Skip spaces at the end
5053 while (*p && my_isspace(charset_info, *p)) p++;
5054
5055 // Check for any spurious text after the second operand
5056 if (*p) die("Invalid mathematical expression '%s' in expr command.", expr);
5057
5058 double result;
5059 // Arithmetic Operators
5060 if (!std::strcmp(math_operator, "+"))
5061 result = operand1 + operand2;
5062 else if (!std::strcmp(math_operator, "-"))
5063 result = operand1 - operand2;
5064 else if (!std::strcmp(math_operator, "*"))
5065 result = operand1 * operand2;
5066 else if (!std::strcmp(math_operator, "/")) {
5067 if (operand2 == 0.0) {
5068 if (operand1 == 0.0)
5069 result = std::numeric_limits<double>::quiet_NaN();
5070 else
5071 result = std::numeric_limits<double>::infinity();
5072 } else
5073 result = operand1 / operand2;
5074 } else if (!std::strcmp(math_operator, "%"))
5075 result = (int)operand1 % (int)operand2;
5076 // Logical Operators
5077 else if (!std::strcmp(math_operator, "&&"))
5078 result = operand1 && operand2;
5079 else if (!std::strcmp(math_operator, "||"))
5080 result = operand1 || operand2;
5081 // Bitwise Operators
5082 else if (!std::strcmp(math_operator, "&"))
5083 result = (int)operand1 & (int)operand2;
5084 else if (!std::strcmp(math_operator, "|"))
5085 result = (int)operand1 | (int)operand2;
5086 else if (!std::strcmp(math_operator, "^"))
5087 result = (int)operand1 ^ (int)operand2;
5088 else if (!std::strcmp(math_operator, ">>"))
5089 result = (int)operand1 >> (int)operand2;
5090 else if (!std::strcmp(math_operator, "<<"))
5091 result = (int)operand1 << (int)operand2;
5092 else
5093 die("Invalid operator '%s' in expr command", math_operator);
5094
5095 char buf[128];
5096 size_t result_len;
5097 // Check if result is an infinite value
5098 if (std::isnan(result)) {
5099 // Print 'nan' if result is Not a Number
5100 result_len = snprintf(buf, sizeof(buf), "%s", "nan");
5101 } else if (!std::isinf(result)) {
5102 const char *format = (result < 1e10 && result > -1e10) ? "%f" : "%g";
5103 result_len = snprintf(buf, sizeof(buf), format, result);
5104 } else
5105 // Print 'inf' if result is an infinite value
5106 result_len = snprintf(buf, sizeof(buf), "%s", "inf");
5107
5108 if (result < 1e10 && result > -1e10) {
5109 /*
5110 Remove the trailing 0's i.e 2.0000000 need to be represented
5111 as 2 for consistency, 2.0010000 also becomes 2.001.
5112 */
5113 while (buf[result_len - 1] == '0') result_len--;
5114
5115 // Remove trailing '.' if exists
5116 if (buf[result_len - 1] == '.') result_len--;
5117 }
5118
5119 var_set(var_name, var_name_end, buf, buf + result_len);
5120 command->last_argument = command->end;
5121 }
5122
5123 /*
5124 Assign the variable <var_name> with <var_val>
5125
5126 SYNOPSIS
5127 do_let()
5128 query called command
5129
5130 DESCRIPTION
5131 let $<var_name>=<var_val><delimiter>
5132
5133 <var_name> - is the string string found between the $ and =
5134 <var_val> - is the content between the = and <delimiter>, it may span
5135 multiple line and contain any characters except <delimiter>
5136 <delimiter> - is a string containing of one or more chars, default is ;
5137
5138 RETURN VALUES
5139 Program will die if error detected
5140 */
5141
do_let(struct st_command * command)5142 static void do_let(struct st_command *command) {
5143 const char *p = command->first_argument;
5144 const char *var_name, *var_name_end;
5145 DYNAMIC_STRING let_rhs_expr;
5146 DBUG_TRACE;
5147
5148 init_dynamic_string(&let_rhs_expr, "", 512, 2048);
5149
5150 /* Find <var_name> */
5151 if (!*p) die("Missing arguments to let");
5152 var_name = p;
5153 while (*p && (*p != '=') && !my_isspace(charset_info, *p)) p++;
5154 var_name_end = p;
5155 if (var_name == var_name_end ||
5156 (var_name + 1 == var_name_end && *var_name == '$'))
5157 die("Missing variable name in let");
5158 while (my_isspace(charset_info, *p)) p++;
5159 if (*p++ != '=') die("Missing assignment operator in let");
5160
5161 /* Find start of <var_val> */
5162 while (*p && my_isspace(charset_info, *p)) p++;
5163
5164 do_eval(&let_rhs_expr, p, command->end, false);
5165
5166 command->last_argument = command->end;
5167 /* Assign var_val to var_name */
5168 var_set(var_name, var_name_end, let_rhs_expr.str,
5169 (let_rhs_expr.str + let_rhs_expr.length));
5170 dynstr_free(&let_rhs_expr);
5171 revert_properties();
5172 }
5173
5174 /*
5175 Sleep the number of specified seconds
5176
5177 SYNOPSIS
5178 do_sleep()
5179 q called command
5180
5181 DESCRIPTION
5182 Sleep <seconds>
5183
5184 The argument provided to --sleep command is not required to be
5185 a whole number and can have fractional parts as well. For
5186 example, '--sleep 0.1' is valid.
5187
5188 */
5189
do_sleep(struct st_command * command)5190 static int do_sleep(struct st_command *command) {
5191 int error = 0;
5192 double sleep_val;
5193 char *p;
5194 static DYNAMIC_STRING ds_sleep;
5195 const struct command_arg sleep_args[] = {{"sleep_delay", ARG_STRING, true,
5196 &ds_sleep,
5197 "Number of seconds to sleep."}};
5198 check_command_args(command, command->first_argument, sleep_args,
5199 sizeof(sleep_args) / sizeof(struct command_arg), ' ');
5200
5201 p = ds_sleep.str;
5202 const char *sleep_end = ds_sleep.str + ds_sleep.length;
5203 while (my_isspace(charset_info, *p)) p++;
5204 if (!*p)
5205 die("Missing argument to %.*s", static_cast<int>(command->first_word_len),
5206 command->query);
5207 const char *sleep_start = p;
5208 /* Check that arg starts with a digit, not handled by my_strtod */
5209 if (!my_isdigit(charset_info, *sleep_start))
5210 die("Invalid argument to %.*s \"%s\"",
5211 static_cast<int>(command->first_word_len), command->query, sleep_start);
5212 sleep_val = my_strtod(sleep_start, &sleep_end, &error);
5213 check_eol_junk_line(sleep_end);
5214 if (error)
5215 die("Invalid argument to %.*s \"%s\"",
5216 static_cast<int>(command->first_word_len), command->query,
5217 command->first_argument);
5218 dynstr_free(&ds_sleep);
5219
5220 DBUG_PRINT("info", ("sleep_val: %f", sleep_val));
5221 if (sleep_val) my_sleep((ulong)(sleep_val * 1000000L));
5222 return 0;
5223 }
5224
do_set_charset(struct st_command * command)5225 static void do_set_charset(struct st_command *command) {
5226 char *charset_name = command->first_argument;
5227 char *p;
5228
5229 if (!charset_name || !*charset_name)
5230 die("Missing charset name in 'character_set'");
5231 /* Remove end space */
5232 p = charset_name;
5233 while (*p && !my_isspace(charset_info, *p)) p++;
5234 if (*p) *p++ = 0;
5235 command->last_argument = p;
5236 charset_info =
5237 get_charset_by_csname(charset_name, MY_CS_PRIMARY, MYF(MY_WME));
5238 if (!charset_info)
5239 abort_not_supported_test("Test requires charset '%s'", charset_name);
5240 }
5241
5242 /// Check if the bug number argument to disable_testcase is in a proper
5243 /// format.
5244 ///
5245 /// Bug number argument should follow 'BUG#XXXX' format
5246 ///
5247 /// - Keyword 'BUG" is case-insensitive
5248 /// - XXXX should contain only digits
5249 ///
5250 /// @param bug_number String representing a bug number
5251 ///
5252 /// @retval True if the bug number argument is in correct format, false
5253 /// otherwise.
validate_bug_number_argument(std::string bug_number)5254 static bool validate_bug_number_argument(std::string bug_number) {
5255 // Convert the string to lowercase characters
5256 std::transform(bug_number.begin(), bug_number.end(), bug_number.begin(),
5257 ::tolower);
5258
5259 // Check if string representing a bug number starts 'BUG' keyword.
5260 // Note: This keyword is case-inseinsitive.
5261 if (bug_number.substr(0, 3).compare("bug") != 0) return false;
5262
5263 // Check if the string contains '#' after 'BUG' keyword
5264 if (bug_number.at(3) != '#') return false;
5265
5266 // Check if the bug number string contains only digits after '#'
5267 if (get_int_val(bug_number.substr(4).c_str()) == -1) return false;
5268
5269 return true;
5270 }
5271
5272 /// Disable or don't execute the statements or commands appear after
5273 /// this command until the execution is enabled by 'enable_testcase'
5274 /// command. If test cases are already disabled, then throw an error
5275 /// and abort the test execution.
5276 ///
5277 /// This command also takes a mandatory parameter specifying the bug
5278 /// number.
5279 ///
5280 /// @code
5281 /// --disable_testcase BUG#XXXX
5282 /// statements or commands
5283 /// --enable_testcase
5284 /// @endcode
5285 ///
5286 /// @param command Pointer to the st_command structure which holds the
5287 /// arguments and information for the command.
do_disable_testcase(struct st_command * command)5288 static void do_disable_testcase(struct st_command *command) {
5289 DYNAMIC_STRING ds_bug_number;
5290 const struct command_arg disable_testcase_args[] = {
5291 {"Bug number", ARG_STRING, true, &ds_bug_number, "Bug number"}};
5292
5293 check_command_args(command, command->first_argument, disable_testcase_args,
5294 sizeof(disable_testcase_args) / sizeof(struct command_arg),
5295 ' ');
5296
5297 /// Check if the bug number argument to disable_testcase is in a
5298 /// proper format.
5299 if (validate_bug_number_argument(ds_bug_number.str) == 0) {
5300 free_dynamic_strings(&ds_bug_number);
5301 die("Bug number mentioned in '%s' command is not in a correct format. "
5302 "It should be 'BUG#XXXX', where keyword 'BUG' is case-insensitive "
5303 "and 'XXXX' should contain only digits.",
5304 command->query);
5305 }
5306
5307 testcase_disabled = true;
5308 free_dynamic_strings(&ds_bug_number);
5309 }
5310
5311 /**
5312 Check if process is active.
5313
5314 @param pid Process id.
5315
5316 @return true if process is active, false otherwise.
5317 */
is_process_active(int pid)5318 static bool is_process_active(int pid) {
5319 #ifdef _WIN32
5320 DWORD exit_code;
5321 HANDLE proc;
5322 proc = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
5323 if (proc == NULL) return false; /* Process could not be found. */
5324
5325 if (!GetExitCodeProcess(proc, &exit_code)) exit_code = 0;
5326
5327 CloseHandle(proc);
5328 if (exit_code != STILL_ACTIVE)
5329 return false; /* Error or process has terminated. */
5330
5331 return true;
5332 #else
5333 return (kill(pid, 0) == 0);
5334 #endif
5335 }
5336
5337 /**
5338 kill process.
5339
5340 @param pid Process id.
5341
5342 @return true if process is terminated, false otherwise.
5343 */
kill_process(int pid)5344 static bool kill_process(int pid) {
5345 bool killed = true;
5346 #ifdef _WIN32
5347 HANDLE proc;
5348 proc = OpenProcess(PROCESS_TERMINATE, false, pid);
5349 if (proc == NULL) return true; /* Process could not be found. */
5350
5351 if (!TerminateProcess(proc, 201)) killed = false;
5352
5353 CloseHandle(proc);
5354 #else
5355 killed = (kill(pid, SIGKILL) == 0);
5356 #endif
5357 return killed;
5358 }
5359
5360 /**
5361 Abort process.
5362
5363 @param pid Process id.
5364 @param path Path to create minidump file in.
5365 */
abort_process(int pid,const char * path MY_ATTRIBUTE ((unused)))5366 static void abort_process(int pid, const char *path MY_ATTRIBUTE((unused))) {
5367 #ifdef _WIN32
5368 HANDLE proc;
5369 proc = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
5370 verbose_msg("Aborting pid %d (handle: %p)\n", pid, proc);
5371 if (proc != NULL) {
5372 char name[FN_REFLEN], *end;
5373 BOOL is_debugged;
5374
5375 if (path) {
5376 /* Append mysqld.<pid>.dmp to the path. */
5377 strncpy(name, path, sizeof(name) - 1);
5378 name[sizeof(name) - 1] = '\0';
5379 end = name + std::strlen(name);
5380 /* Make sure "/mysqld.nnnnnnnnnn.dmp" fits */
5381 if ((end - name) < (sizeof(name) - 23)) {
5382 if (!is_directory_separator(end[-1])) {
5383 end[0] = FN_LIBCHAR2; // datadir path normally uses '/'.
5384 end++;
5385 }
5386 snprintf(end, sizeof(name) + name - end - 1, "mysqld.%d.dmp", pid);
5387
5388 verbose_msg("Creating minidump.\n");
5389 my_create_minidump(name, proc, pid);
5390 } else
5391 die("Path too long for creating minidump!\n");
5392 }
5393 /* If running in a debugger, send a break, otherwise terminate. */
5394 if (CheckRemoteDebuggerPresent(proc, &is_debugged) && is_debugged) {
5395 if (!DebugBreakProcess(proc)) {
5396 DWORD err = GetLastError();
5397 verbose_msg("DebugBreakProcess failed: %d\n", err);
5398 } else
5399 verbose_msg("DebugBreakProcess succeeded!\n");
5400 CloseHandle(proc);
5401 } else {
5402 CloseHandle(proc);
5403 (void)kill_process(pid);
5404 }
5405 } else {
5406 DWORD err = GetLastError();
5407 verbose_msg("OpenProcess failed: %d\n", err);
5408 }
5409 #else
5410 kill(pid, SIGABRT);
5411 #endif
5412 }
5413
5414 /// Shutdown or kill the server. If timeout is set to 0 the server is
5415 /// killed or terminated immediately. Otherwise the shutdown command
5416 /// is first sent and then it waits for the server to terminate within
5417 /// 'timeout' seconds. If it has not terminated before 'timeout'
5418 /// seconds the command will fail.
5419 ///
5420 /// @note
5421 /// Currently only works with local server
5422 ///
5423 /// @param command Pointer to the st_command structure which holds the
5424 /// arguments and information for the command. Optionally
5425 /// including a timeout else the default of 60 seconds
do_shutdown_server(struct st_command * command)5426 static void do_shutdown_server(struct st_command *command) {
5427 static DYNAMIC_STRING ds_timeout;
5428 const struct command_arg shutdown_args[] = {
5429 {"timeout", ARG_STRING, false, &ds_timeout,
5430 "Timeout before killing server"}};
5431
5432 check_command_args(command, command->first_argument, shutdown_args,
5433 sizeof(shutdown_args) / sizeof(struct command_arg), ' ');
5434 if (opt_offload_count_file) {
5435 // Save the value of secondary engine execution status
5436 // before shutting down the server.
5437 if (secondary_engine->offload_count(&cur_con->mysql, "after"))
5438 cleanup_and_exit(1);
5439 }
5440
5441 long timeout = 60;
5442
5443 if (ds_timeout.length) {
5444 char *endptr;
5445 timeout = std::strtol(ds_timeout.str, &endptr, 10);
5446 if (*endptr != '\0')
5447 die("Illegal argument for timeout: '%s'", ds_timeout.str);
5448 }
5449
5450 dynstr_free(&ds_timeout);
5451
5452 MYSQL *mysql = &cur_con->mysql;
5453 std::string ds_file_name;
5454
5455 // Get the servers pid_file name and use it to read pid
5456 if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1,
5457 &ds_file_name))
5458 die("Failed to get pid_file from server");
5459
5460 // Read the pid from the file
5461 int fd;
5462 char buff[32];
5463
5464 if ((fd = my_open(ds_file_name.c_str(), O_RDONLY, MYF(0))) < 0)
5465 die("Failed to open file '%s'", ds_file_name.c_str());
5466
5467 if (my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0)) <= 0) {
5468 my_close(fd, MYF(0));
5469 die("pid file was empty");
5470 }
5471
5472 my_close(fd, MYF(0));
5473
5474 int pid = std::atoi(buff);
5475 if (pid == 0) die("Pidfile didn't contain a valid number");
5476
5477 int error = 0;
5478 if (timeout) {
5479 // Check if we should generate a minidump on timeout.
5480 if (query_get_string(mysql, "SHOW VARIABLES LIKE 'core_file'", 1,
5481 &ds_file_name) ||
5482 std::strcmp("ON", ds_file_name.c_str())) {
5483 } else {
5484 // Get the data dir and use it as path for a minidump if needed.
5485 if (query_get_string(mysql, "SHOW VARIABLES LIKE 'datadir'", 1,
5486 &ds_file_name))
5487 die("Failed to get datadir from server");
5488 }
5489
5490 const char *var_name = "$MTR_MANUAL_DEBUG";
5491 VAR *var = var_get(var_name, &var_name, false, false);
5492 if (var->int_val) {
5493 if (!kill_process(pid) && is_process_active(pid)) error = 3;
5494 } else {
5495 // Tell server to shutdown if timeout > 0.
5496 if (timeout > 0 && mysql_query_wrapper(mysql, "shutdown")) {
5497 // Failed to issue shutdown command.
5498 error = 1;
5499 goto end;
5500 }
5501
5502 // Check that server dies
5503 do {
5504 if (!is_process_active(pid)) {
5505 DBUG_PRINT("info", ("Process %d does not exist anymore", pid));
5506 goto end;
5507 }
5508 if (timeout > 0) {
5509 DBUG_PRINT("info", ("Sleeping, timeout: %ld", timeout));
5510 my_sleep(1000000L);
5511 }
5512 } while (timeout-- > 0);
5513
5514 error = 2;
5515
5516 // Abort to make it easier to find the hang/problem.
5517 abort_process(pid, ds_file_name.c_str());
5518 }
5519 } else {
5520 // timeout value is 0, kill the server
5521 DBUG_PRINT("info", ("Killing server, pid: %d", pid));
5522
5523 // kill_process can fail (bad privileges, non existing process on
5524 // *nix etc), so also check if the process is active before setting
5525 // error.
5526 if (!kill_process(pid) && is_process_active(pid)) error = 3;
5527 }
5528
5529 end:
5530 if (error) handle_command_error(command, error);
5531 }
5532
5533 /// Evaluate the warning list argument specified with either
5534 /// disable_warnings or enable_warnings command and replace the
5535 /// variables with actual values if there exist any.
5536 ///
5537 /// @param command Pointer to the st_command structure which holds
5538 /// the arguments and information for the command.
5539 /// @param ds_warnings DYNAMIC_STRING object containing the argument.
5540 ///
5541 /// @retval Evaluated string after replacing the variables with values.
eval_warning_argument(struct st_command * command,DYNAMIC_STRING * ds_warnings)5542 const char *eval_warning_argument(struct st_command *command,
5543 DYNAMIC_STRING *ds_warnings) {
5544 dynstr_trunc(ds_warnings, ds_warnings->length);
5545 do_eval(ds_warnings, command->first_argument, command->end, false);
5546 return ds_warnings->str;
5547 }
5548
5549 /// Check if second argument "ONCE" to disable_warnings or enable_warnings
5550 /// command is specified. If yes, filter out the keyword "ONCE" from the
5551 /// argument string.
5552 ///
5553 /// @param ds_property DYNAMIC_STRING object containing the second argument
5554 /// @param warn_argument String to store the argument string containing only
5555 /// the list of warnings.
5556 ///
5557 /// @retval True if the second argument is specified, false otherwise.
check_and_filter_once_property(DYNAMIC_STRING ds_property,std::string * warn_argument)5558 static bool check_and_filter_once_property(DYNAMIC_STRING ds_property,
5559 std::string *warn_argument) {
5560 if (ds_property.length) {
5561 // Second argument exists, and it should be "ONCE" keyword.
5562 if (std::strcmp(ds_property.str, "ONCE"))
5563 die("Second argument to '%s' command should always be \"ONCE\" keyword.",
5564 command_names[curr_command->type - 1]);
5565
5566 // Filter out the keyword and save only the warnings.
5567 std::size_t position = warn_argument->find(" ONCE");
5568 DBUG_ASSERT(position != std::string::npos);
5569 warn_argument->erase(position, 5);
5570 return true;
5571 }
5572
5573 return false;
5574 }
5575
5576 /// Handle disabling of warnings.
5577 ///
5578 /// * If all warnings are disabled, don't add the warning to disabled
5579 /// warning list.
5580 /// * If there exist enabled warnings, remove the disabled warning from
5581 /// the list of enabled warnings.
5582 /// * If all the warnings are enabled or if there exist disabled warnings,
5583 /// add or append the new warning to the list of disabled warnings.
5584 ///
5585 /// @param warning_code Warning code
5586 /// @param warning Warning string
5587 /// @param once_prop Flag specifying whether a property should be set
5588 /// for next statement only.
handle_disable_warnings(std::uint32_t warning_code,std::string warning,bool once_prop)5589 static void handle_disable_warnings(std::uint32_t warning_code,
5590 std::string warning, bool once_prop) {
5591 if (enabled_warnings->count()) {
5592 // Remove the warning from list of enabled warnings.
5593 enabled_warnings->remove_warning(warning_code, once_prop);
5594 } else if (!disable_warnings || disabled_warnings->count()) {
5595 // Add the warning to list of expected warnings only if all the
5596 // warnings are not disabled.
5597 disabled_warnings->add_warning(warning_code, warning.c_str(), once_prop);
5598 }
5599 }
5600
5601 /// Handle enabling of warnings.
5602 ///
5603 /// * If all the warnings are enabled, don't add the warning to enabled
5604 /// warning list.
5605 /// * If there exist disabled warnings, remove the enabled warning from
5606 /// the list of disabled warnings.
5607 /// * If all the warnings are disabled or if there exist enabled warnings,
5608 /// add or append the new warning to the list of enabled warnings.
5609 ///
5610 /// @param warning_code Warning code
5611 /// @param warning Warning string
5612 /// @param once_prop Flag specifying whether a property should be set
5613 /// for next statement only.
handle_enable_warnings(std::uint32_t warning_code,std::string warning,bool once_prop)5614 static void handle_enable_warnings(std::uint32_t warning_code,
5615 std::string warning, bool once_prop) {
5616 if (disabled_warnings->count()) {
5617 // Remove the warning from list of disabled warnings.
5618 disabled_warnings->remove_warning(warning_code, once_prop);
5619 } else if (disabled_warnings) {
5620 // All the warnings are disabled, enable only the warnings specified
5621 // in the argument.
5622 enabled_warnings->add_warning(warning_code, warning.c_str(), once_prop);
5623 }
5624 }
5625
5626 /// Get an error code corresponding to a warning name. The warning name
5627 /// specified is in symbolic error name format.
5628 ///
5629 /// @param command Pointer to the st_command structure which holds the
5630 /// arguments and information for the command.
5631 /// @param warn_argument String containing warning argument
5632 /// @param once_prop Flag specifying whether a property should be set for
5633 /// next statement only.
get_warning_codes(struct st_command * command,std::string warn_argument,bool once_prop)5634 static void get_warning_codes(struct st_command *command,
5635 std::string warn_argument, bool once_prop) {
5636 std::string warning;
5637 std::stringstream warnings(warn_argument);
5638
5639 if (!my_isalnum(charset_info, warn_argument.back())) {
5640 die("Invalid argument '%s' to '%s' command.", command->first_argument,
5641 command_names[command->type - 1]);
5642 }
5643
5644 while (std::getline(warnings, warning, ',')) {
5645 // Remove any space in a string representing a warning.
5646 warning.erase(remove_if(warning.begin(), warning.end(), isspace),
5647 warning.end());
5648
5649 // Check if a warning name is a valid symbolic error name.
5650 if (warning.front() == 'E' || warning.front() == 'W') {
5651 int warning_code = get_errcode_from_name(warning);
5652 if (warning_code == -1)
5653 die("Unknown SQL error name '%s'.", warning.c_str());
5654 if (command->type == Q_DISABLE_WARNINGS)
5655 handle_disable_warnings(warning_code, warning, once_prop);
5656 else if (command->type == Q_ENABLE_WARNINGS) {
5657 handle_enable_warnings(warning_code, warning, once_prop);
5658 // If disable_warnings flag is set, and there are no disabled or
5659 // enabled warnings, set the disable_warnings flag to 0.
5660 if (disable_warnings) {
5661 if (!disabled_warnings->count() && !enabled_warnings->count())
5662 set_property(command, P_WARN, false);
5663 }
5664 }
5665 } else {
5666 // Invalid argument, should only consist of warnings specified in
5667 // symbolic error name format.
5668 die("Invalid argument '%s' to '%s' command, list of disabled or enabled "
5669 "warnings may only consist of symbolic error names.",
5670 command->first_argument, command_names[command->type - 1]);
5671 }
5672 }
5673 }
5674
5675 /// Parse the warning list argument specified with disable_warnings or
5676 /// enable_warnings command. Check if the second argument "ONCE" is
5677 /// specified, if yes, set once_property flag.
5678 ///
5679 /// @param command Pointer to the st_command structure which holds the
5680 /// arguments and information for the command.
5681 ///
5682 /// @retval True if second argument "ONCE" is specified, false otherwise.
parse_warning_list_argument(struct st_command * command)5683 static bool parse_warning_list_argument(struct st_command *command) {
5684 DYNAMIC_STRING ds_warnings, ds_property;
5685 const struct command_arg warning_args[] = {
5686 {"Warnings", ARG_STRING, false, &ds_warnings,
5687 "Comma separated list of warnings to be disabled or enabled."},
5688 {"Property", ARG_STRING, false, &ds_property,
5689 "Keyword \"ONCE\" repesenting the property should be set for next "
5690 "statement only."}};
5691
5692 check_command_args(command, command->first_argument, warning_args,
5693 sizeof(warning_args) / sizeof(struct command_arg), ' ');
5694
5695 // Waning list argument can't be an empty string.
5696 if (!ds_warnings.length)
5697 die("Warning list argument to command '%s' can't be an empty string.",
5698 command_names[command->type - 1]);
5699
5700 // Evaluate warning list argument and replace the variables with
5701 // actual values
5702 std::string warn_argument = eval_warning_argument(command, &ds_warnings);
5703
5704 // Set once_prop flag to true if keyword "ONCE" is specified as an
5705 // argument to a disable_warnings or a enable_warnings command.
5706 // Filter this keyword from the argument string and save only the
5707 // list of warnings.
5708 bool once_prop = check_and_filter_once_property(ds_property, &warn_argument);
5709
5710 // Free all the DYNAMIC_STRING objects created
5711 free_dynamic_strings(&ds_warnings, &ds_property);
5712
5713 // Get warning codes
5714 get_warning_codes(command, warn_argument, once_prop);
5715
5716 return once_prop;
5717 }
5718
5719 /// Create a list of disabled warnings that should be suppressed for
5720 /// the next statements until enabled by enable_warnings command.
5721 ///
5722 /// disable_warnings command can take an optional argument specifying
5723 /// a warning or a comma separated list of warnings to be disabled.
5724 /// The warnings specified should be in symbolic error name format.
5725 ///
5726 /// disable_warnings command can also take a second optional argument,
5727 /// which when specified will suppress the warnings for next statement
5728 /// only. The second argument must be a "ONCE" keyword.
5729 ///
5730 /// @param command Pointer to the st_command structure which holds the
5731 /// arguments and information for the command.
do_disable_warnings(struct st_command * command)5732 static void do_disable_warnings(struct st_command *command) {
5733 // Revert the previously set properties
5734 if (once_property) revert_properties();
5735
5736 // Check if disable_warnings command has warning list argument.
5737 if (!*command->first_argument) {
5738 // disable_warnings without an argument, disable all the warnings.
5739 if (disabled_warnings->count()) disabled_warnings->clear_list();
5740 if (enabled_warnings->count()) enabled_warnings->clear_list();
5741
5742 // Update the environment variables containing the list of disabled
5743 // and enabled warnings.
5744 update_disabled_enabled_warnings_list_var();
5745
5746 // Set 'disable_warnings' property value to 1
5747 set_property(command, P_WARN, true);
5748 return;
5749 } else {
5750 // Parse the warning list argument specified with disable_warnings
5751 // command.
5752 parse_warning_list_argument(command);
5753
5754 // Update the environment variables containing the list of disabled
5755 // and enabled warnings.
5756 update_disabled_enabled_warnings_list_var();
5757
5758 // Set 'disable_warnings' property value to 1
5759 set_property(command, P_WARN, true);
5760 }
5761
5762 command->last_argument = command->end;
5763 }
5764
5765 /// Create a list of enabled warnings that should be enabled for the
5766 /// next statements until disabled by disable_warnings command.
5767 ///
5768 /// enable_warnings command can take an optional argument specifying
5769 /// a warning or a comma separated list of warnings to be enabled. The
5770 /// warnings specified should be in symbolic error name format.
5771 ///
5772 /// enable_warnings command can also take a second optional argument,
5773 /// which when specified will enable the warnings for next statement
5774 /// only. The second argument must be a "ONCE" keyword.
5775 ///
5776 /// @param command Pointer to the st_command structure which holds the
5777 /// arguments and information for the command.
do_enable_warnings(struct st_command * command)5778 static void do_enable_warnings(struct st_command *command) {
5779 // Revert the previously set properties
5780 if (once_property) revert_properties();
5781
5782 bool once_prop = false;
5783 if (!*command->first_argument) {
5784 // enable_warnings without an argument, enable all the warnings.
5785 if (disabled_warnings->count()) disabled_warnings->clear_list();
5786 if (enabled_warnings->count()) enabled_warnings->clear_list();
5787
5788 // Update the environment variables containing the list of disabled
5789 // and enabled warnings.
5790 update_disabled_enabled_warnings_list_var();
5791
5792 // Set 'disable_warnings' property value to 0
5793 set_property(command, P_WARN, false);
5794 } else {
5795 // Parse the warning list argument specified with enable_warnings command.
5796 once_prop = parse_warning_list_argument(command);
5797
5798 // Update the environment variables containing the list of disabled and
5799 // enabled warnings.
5800 update_disabled_enabled_warnings_list_var();
5801 }
5802
5803 // Call set_once_property() to set once_propetry flag.
5804 if (disable_warnings && once_prop) set_once_property(P_WARN, true);
5805
5806 command->last_argument = command->end;
5807 }
5808
5809 /// Create a list of error values that the next statement is expected
5810 /// to return. Each error must be an error number or an SQLSTATE value
5811 /// or a symbolic error name representing an error.
5812 ///
5813 /// SQLSTATE value must start with 'S'. It is also possible to specify
5814 /// client errors with 'error' command.
5815 ///
5816 /// @code
5817 /// --error 1064
5818 /// --error S42S01
5819 /// --error ER_TABLE_EXISTS_ERROR,ER_PARSE_ERROR
5820 /// --error CR_SERVER_GONE_ERROR
5821 /// @endcode
5822 ///
5823 /// @param command Pointer to the st_command structure which holds the
5824 /// arguments and information for the command.
do_error(struct st_command * command)5825 static void do_error(struct st_command *command) {
5826 if (!*command->first_argument) die("Missing argument(s) to 'error' command.");
5827
5828 // Check if error command ends with a comment
5829 char *end = command->first_argument + std::strlen(command->first_argument);
5830 while (std::strlen(command->first_argument) > std::strlen(end)) {
5831 end--;
5832 if (*end == '#') break;
5833 }
5834
5835 if (std::strlen(command->first_argument) == std::strlen(end))
5836 end = command->first_argument + std::strlen(command->first_argument);
5837
5838 std::string error;
5839 std::stringstream errors(std::string(command->first_argument, end));
5840
5841 // Get error codes
5842 while (std::getline(errors, error, ',')) {
5843 // Remove any space from the string representing an error.
5844 error.erase(remove_if(error.begin(), error.end(), isspace), error.end());
5845
5846 // Code to handle a variable containing an error.
5847 if (error.front() == '$') {
5848 const char *varname_end = nullptr;
5849 VAR *var = var_get(error.c_str(), &varname_end, false, false);
5850 error.assign(var->str_val);
5851 }
5852
5853 if (error.front() == 'S') {
5854 // SQLSTATE string
5855 // * Must be SQLSTATE_LENGTH long
5856 // * May contain only digits[0-9] and _uppercase_ letters
5857
5858 // Step pass 'S' character
5859 error = error.substr(1, error.length());
5860
5861 if (error.length() != SQLSTATE_LENGTH)
5862 die("The sqlstate must be exactly %d chars long.", SQLSTATE_LENGTH);
5863
5864 // Check the validity of an SQLSTATE string.
5865 for (std::size_t i = 0; i < error.length(); i++) {
5866 if (!my_isdigit(charset_info, error[i]) &&
5867 !my_isupper(charset_info, error[i]))
5868 die("The sqlstate may only consist of digits[0-9] and _uppercase_ "
5869 "letters.");
5870 }
5871 expected_errors->add_error(error.c_str(), ERR_SQLSTATE);
5872 } else if (error.front() == 's') {
5873 die("The sqlstate definition must start with an uppercase S.");
5874 }
5875 // Checking for both server error names as well as client error names.
5876 else if (error.front() == 'C' || error.front() == 'E') {
5877 // Code to handle --error <error_string>.
5878 int error_code = get_errcode_from_name(error);
5879 if (error_code == -1) die("Unknown SQL error name '%s'.", error.c_str());
5880 expected_errors->add_error(error_code, ERR_ERRNO);
5881 } else if (error.front() == 'c' || error.front() == 'e') {
5882 die("The error name definition must start with an uppercase C or E.");
5883 } else {
5884 // Check that the string passed to error command contains only digits.
5885 int error_code = get_int_val(error.c_str());
5886 if (error_code == -1)
5887 die("Invalid argument '%s' to 'error' command, the argument may "
5888 "consist of either symbolic error names or error codes.",
5889 command->first_argument);
5890 expected_errors->add_error((std::uint32_t)error_code, ERR_ERRNO);
5891 }
5892
5893 if (expected_errors->count() >= MAX_ERROR_COUNT)
5894 die("Too many errorcodes specified.");
5895 }
5896
5897 command->last_argument = command->end;
5898 }
5899
5900 /*
5901 Get a string; Return ptr to end of string
5902 Strings may be surrounded by " or '
5903
5904 If string is a '$variable', return the value of the variable.
5905 */
5906
get_string(char ** to_ptr,const char ** from_ptr,struct st_command * command)5907 static char *get_string(char **to_ptr, const char **from_ptr,
5908 struct st_command *command) {
5909 char c, sep;
5910 char *to = *to_ptr, *start = to;
5911 const char *from = *from_ptr;
5912 DBUG_TRACE;
5913
5914 /* Find separator */
5915 if (*from == '"' || *from == '\'')
5916 sep = *from++;
5917 else
5918 sep = ' '; /* Separated with space */
5919
5920 for (; (c = *from); from++) {
5921 if (c == '\\' && from[1]) { /* Escaped character */
5922 /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */
5923 switch (*++from) {
5924 case 'n':
5925 *to++ = '\n';
5926 break;
5927 case 't':
5928 *to++ = '\t';
5929 break;
5930 case 'r':
5931 *to++ = '\r';
5932 break;
5933 case 'b':
5934 *to++ = '\b';
5935 break;
5936 case 'Z': /* ^Z must be escaped on Win32 */
5937 *to++ = '\032';
5938 break;
5939 default:
5940 *to++ = *from;
5941 break;
5942 }
5943 } else if (c == sep) {
5944 if (c == ' ' || c != *++from) break; /* Found end of string */
5945 *to++ = c; /* Copy duplicated separator */
5946 } else
5947 *to++ = c;
5948 }
5949 if (*from != ' ' && *from) die("Wrong string argument in %s", command->query);
5950
5951 while (my_isspace(charset_info, *from)) /* Point to next string */
5952 from++;
5953
5954 *to = 0; /* End of string marker */
5955 *to_ptr = to + 1; /* Store pointer to end */
5956 *from_ptr = from;
5957
5958 /* Check if this was a variable */
5959 if (*start == '$') {
5960 const char *end = to;
5961 VAR *var = var_get(start, &end, false, true);
5962 if (var && to == end + 1) {
5963 DBUG_PRINT("info", ("var: '%s' -> '%s'", start, var->str_val));
5964 return var->str_val; /* return found variable value */
5965 }
5966 }
5967 return start;
5968 }
5969
set_reconnect(MYSQL * mysql,int val)5970 static void set_reconnect(MYSQL *mysql, int val) {
5971 bool reconnect = val;
5972 DBUG_TRACE;
5973 DBUG_PRINT("info", ("val: %d", val));
5974 mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect);
5975 }
5976
5977 /**
5978 Change the current connection to the given st_connection, and update
5979 $mysql_get_server_version and $CURRENT_CONNECTION accordingly.
5980 */
set_current_connection(struct st_connection * con)5981 static void set_current_connection(struct st_connection *con) {
5982 cur_con = con;
5983 /* Update $mysql_get_server_version to that of current connection */
5984 var_set_int("$mysql_get_server_version",
5985 mysql_get_server_version(&con->mysql));
5986 /* Update $CURRENT_CONNECTION to the name of the current connection */
5987 var_set_string("$CURRENT_CONNECTION", con->name);
5988 }
5989
select_connection_name(const char * name)5990 static void select_connection_name(const char *name) {
5991 DBUG_TRACE;
5992 DBUG_PRINT("enter", ("name: '%s'", name));
5993 st_connection *con = find_connection_by_name(name);
5994
5995 if (!con) die("connection '%s' not found in connection pool", name);
5996
5997 set_current_connection(con);
5998
5999 /* Connection logging if enabled */
6000 if (!disable_connect_log && !disable_query_log) {
6001 DYNAMIC_STRING *ds = &ds_res;
6002
6003 dynstr_append_mem(ds, "connection ", 11);
6004 replace_dynstr_append(ds, name);
6005 dynstr_append_mem(ds, ";\n", 2);
6006 }
6007 }
6008
select_connection(struct st_command * command)6009 static void select_connection(struct st_command *command) {
6010 DBUG_TRACE;
6011 static DYNAMIC_STRING ds_connection;
6012 const struct command_arg connection_args[] = {
6013 {"connection_name", ARG_STRING, true, &ds_connection,
6014 "Name of the connection that we switch to."}};
6015 check_command_args(command, command->first_argument, connection_args,
6016 sizeof(connection_args) / sizeof(struct command_arg), ' ');
6017
6018 DBUG_PRINT("info", ("changing connection: %s", ds_connection.str));
6019 select_connection_name(ds_connection.str);
6020 dynstr_free(&ds_connection);
6021 }
6022
do_close_connection(struct st_command * command)6023 static void do_close_connection(struct st_command *command) {
6024 DBUG_TRACE;
6025
6026 struct st_connection *con;
6027 static DYNAMIC_STRING ds_connection;
6028 const struct command_arg close_connection_args[] = {
6029 {"connection_name", ARG_STRING, true, &ds_connection,
6030 "Name of the connection to close."}};
6031 check_command_args(command, command->first_argument, close_connection_args,
6032 sizeof(close_connection_args) / sizeof(struct command_arg),
6033 ' ');
6034
6035 DBUG_PRINT("enter", ("connection name: '%s'", ds_connection.str));
6036
6037 if (!(con = find_connection_by_name(ds_connection.str)))
6038 die("connection '%s' not found in connection pool", ds_connection.str);
6039
6040 DBUG_PRINT("info", ("Closing connection %s", con->name));
6041 if (command->type == Q_DIRTY_CLOSE) {
6042 if (con->mysql.net.vio) {
6043 vio_delete(con->mysql.net.vio);
6044 con->mysql.net.vio = nullptr;
6045 end_server(&con->mysql);
6046 }
6047 }
6048 if (con->stmt) mysql_stmt_close(con->stmt);
6049 con->stmt = nullptr;
6050
6051 mysql_close(&con->mysql);
6052
6053 if (con->util_mysql) mysql_close(con->util_mysql);
6054 con->util_mysql = nullptr;
6055 con->pending = false;
6056
6057 my_free(con->name);
6058
6059 /*
6060 When the connection is closed set name to "-closed_connection-"
6061 to make it possible to reuse the connection name.
6062 */
6063 if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "-closed_connection-",
6064 MYF(MY_WME))))
6065 die("Out of memory");
6066
6067 if (con == cur_con) {
6068 /* Current connection was closed */
6069 var_set_int("$mysql_get_server_version", 0xFFFFFFFF);
6070 var_set_string("$CURRENT_CONNECTION", con->name);
6071 }
6072
6073 /* Connection logging if enabled */
6074 if (!disable_connect_log && !disable_query_log) {
6075 DYNAMIC_STRING *ds = &ds_res;
6076
6077 dynstr_append_mem(ds, "disconnect ", 11);
6078 replace_dynstr_append(ds, ds_connection.str);
6079 dynstr_append_mem(ds, ";\n", 2);
6080 }
6081
6082 dynstr_free(&ds_connection);
6083 }
6084
6085 /*
6086 Connect to a server doing several retries if needed.
6087
6088 SYNOPSIS
6089 safe_connect()
6090 con - connection structure to be used
6091 host, user, pass, - connection parameters
6092 db, port, sock
6093
6094 NOTE
6095
6096 Sometimes in a test the client starts before
6097 the server - to solve the problem, we try again
6098 after some sleep if connection fails the first
6099 time
6100
6101 This function will try to connect to the given server
6102 "opt_max_connect_retries" times and sleep "connection_retry_sleep"
6103 seconds between attempts before finally giving up.
6104 This helps in situation when the client starts
6105 before the server (which happens sometimes).
6106 It will only ignore connection errors during these retries.
6107
6108 */
6109
safe_connect(MYSQL * mysql,const char * name,const char * host,const char * user,const char * pass,const char * db,int port,const char * sock)6110 static void safe_connect(MYSQL *mysql, const char *name, const char *host,
6111 const char *user, const char *pass, const char *db,
6112 int port, const char *sock) {
6113 int failed_attempts = 0;
6114
6115 DBUG_TRACE;
6116
6117 verbose_msg(
6118 "Connecting to server %s:%d (socket %s) as '%s'"
6119 ", connection '%s', attempt %d ...",
6120 host, port, sock, user, name, failed_attempts);
6121
6122 mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
6123 mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name",
6124 "mysqltest");
6125 mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
6126 &can_handle_expired_passwords);
6127 while (!mysql_real_connect_wrapper(
6128 mysql, host, user, pass, db, port, sock,
6129 CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) {
6130 /*
6131 Connect failed
6132
6133 Only allow retry if this was an error indicating the server
6134 could not be contacted. Error code differs depending
6135 on protocol/connection type
6136 */
6137
6138 if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR ||
6139 mysql_errno(mysql) == CR_CONNECTION_ERROR ||
6140 mysql_errno(mysql) == CR_NAMEDPIPEOPEN_ERROR) &&
6141 failed_attempts < opt_max_connect_retries) {
6142 verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts,
6143 opt_max_connect_retries, mysql_errno(mysql),
6144 mysql_error(mysql));
6145 my_sleep(connection_retry_sleep);
6146 } else {
6147 if (failed_attempts > 0)
6148 die("Could not open connection '%s' after %d attempts: %d %s", name,
6149 failed_attempts, mysql_errno(mysql), mysql_error(mysql));
6150 else
6151 die("Could not open connection '%s': %d %s", name, mysql_errno(mysql),
6152 mysql_error(mysql));
6153 }
6154 failed_attempts++;
6155 }
6156 verbose_msg("... Connected.");
6157 }
6158
6159 /// Connect to a server and handle connection errors in case they
6160 /// occur.
6161 ///
6162 /// This function will try to establish a connection to server and
6163 /// handle possible errors in the same manner as if "connect" was usual
6164 /// SQL-statement. If an error is expected it will ignore it once it
6165 /// occurs and log the "statement" to the query log. Unlike
6166 /// safe_connect() it won't do several attempts.
6167 ///
6168 /// @param command Pointer to the st_command structure which holds the
6169 /// arguments and information for the command.
6170 /// @param con Connection structure to be used
6171 /// @param host Host name
6172 /// @param user User name
6173 /// @param pass Password
6174 /// @param db Database name
6175 /// @param port Port number
6176 /// @param sock Socket value
6177 ///
6178 /// @retval 1 if connection succeeds, 0 otherwise
connect_n_handle_errors(struct st_command * command,MYSQL * con,const char * host,const char * user,const char * pass,const char * db,int port,const char * sock)6179 static int connect_n_handle_errors(struct st_command *command, MYSQL *con,
6180 const char *host, const char *user,
6181 const char *pass, const char *db, int port,
6182 const char *sock) {
6183 DYNAMIC_STRING *ds;
6184 int failed_attempts = 0;
6185
6186 ds = &ds_res;
6187
6188 /* Only log if an error is expected */
6189 if (expected_errors->count() > 0 && !disable_query_log) {
6190 /*
6191 Log the connect to result log
6192 */
6193 dynstr_append_mem(ds, "connect(", 8);
6194 replace_dynstr_append(ds, host);
6195 dynstr_append_mem(ds, ",", 1);
6196 replace_dynstr_append(ds, user);
6197 dynstr_append_mem(ds, ",", 1);
6198 replace_dynstr_append(ds, pass);
6199 dynstr_append_mem(ds, ",", 1);
6200 if (db) replace_dynstr_append(ds, db);
6201 dynstr_append_mem(ds, ",", 1);
6202 replace_dynstr_append_uint(ds, port);
6203 dynstr_append_mem(ds, ",", 1);
6204 if (sock) replace_dynstr_append(ds, sock);
6205 dynstr_append_mem(ds, ")", 1);
6206 dynstr_append_mem(ds, delimiter, delimiter_length);
6207 dynstr_append_mem(ds, "\n", 1);
6208 }
6209 /* Simlified logging if enabled */
6210 if (!disable_connect_log && !disable_query_log) {
6211 replace_dynstr_append(ds, command->query);
6212 dynstr_append_mem(ds, ";\n", 2);
6213 }
6214
6215 mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
6216 mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest");
6217 mysql_options(con, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
6218 &can_handle_expired_passwords);
6219 while (!mysql_real_connect_wrapper(con, host, user, pass, db, port,
6220 sock ? sock : nullptr,
6221 CLIENT_MULTI_STATEMENTS)) {
6222 /*
6223 If we have used up all our connections check whether this
6224 is expected (by --error). If so, handle the error right away.
6225 Otherwise, give it some extra time to rule out race-conditions.
6226 If extra-time doesn't help, we have an unexpected error and
6227 must abort -- just proceeding to handle_error() when second
6228 and third chances are used up will handle that for us.
6229
6230 There are various user-limits of which only max_user_connections
6231 and max_connections_per_hour apply at connect time. For the
6232 the second to create a race in our logic, we'd need a limits
6233 test that runs without a FLUSH for longer than an hour, so we'll
6234 stay clear of trying to work out which exact user-limit was
6235 exceeded.
6236 */
6237
6238 if (((mysql_errno(con) == ER_TOO_MANY_USER_CONNECTIONS) ||
6239 (mysql_errno(con) == ER_USER_LIMIT_REACHED)) &&
6240 (failed_attempts++ < opt_max_connect_retries)) {
6241 int i;
6242
6243 i = match_expected_error(command, mysql_errno(con), mysql_sqlstate(con));
6244
6245 if (i >= 0) goto do_handle_error; /* expected error, handle */
6246
6247 my_sleep(connection_retry_sleep); /* unexpected error, wait */
6248 continue; /* and give it 1 more chance */
6249 }
6250
6251 do_handle_error:
6252 var_set_errno(mysql_errno(con));
6253 handle_error(command, mysql_errno(con), mysql_error(con),
6254 mysql_sqlstate(con), ds);
6255 return 0; /* Not connected */
6256 }
6257
6258 var_set_errno(0);
6259 handle_no_error(command);
6260 revert_properties();
6261 return 1; /* Connected */
6262 }
6263
6264 /*
6265 Open a new connection to MySQL Server with the parameters
6266 specified. Make the new connection the current connection.
6267
6268 SYNOPSIS
6269 do_connect()
6270 q called command
6271
6272 DESCRIPTION
6273 connect(<name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]);
6274 connect <name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]];
6275
6276 <name> - name of the new connection
6277 <host> - hostname of server
6278 <user> - user to connect as
6279 <pass> - password used when connecting
6280 <db> - initial db when connected
6281 <port> - server port
6282 <sock> - server socket
6283 <opts> - options to use for the connection
6284 * SSL - use SSL if available
6285 * COMPRESS - use compression if available
6286 * SHM - use shared memory if available
6287 * PIPE - use named pipe if available
6288 * SOCKET - use socket protocol
6289 * TCP - use tcp protocol
6290 */
6291
do_connect(struct st_command * command)6292 static void do_connect(struct st_command *command) {
6293 int con_port = opt_port;
6294 char *con_options;
6295 bool con_ssl = false, con_compress = false;
6296 bool con_pipe = false, con_shm = false, con_cleartext_enable = false;
6297 struct st_connection *con_slot;
6298 uint save_opt_ssl_mode = opt_ssl_mode;
6299
6300 static DYNAMIC_STRING ds_connection_name;
6301 static DYNAMIC_STRING ds_host;
6302 static DYNAMIC_STRING ds_user;
6303 static DYNAMIC_STRING ds_password;
6304 static DYNAMIC_STRING ds_database;
6305 static DYNAMIC_STRING ds_port;
6306 static DYNAMIC_STRING ds_sock;
6307 static DYNAMIC_STRING ds_options;
6308 static DYNAMIC_STRING ds_default_auth;
6309 static DYNAMIC_STRING ds_shm;
6310 static DYNAMIC_STRING ds_compression_algorithm;
6311 static DYNAMIC_STRING ds_zstd_compression_level;
6312 const struct command_arg connect_args[] = {
6313 {"connection name", ARG_STRING, true, &ds_connection_name,
6314 "Name of the connection"},
6315 {"host", ARG_STRING, true, &ds_host, "Host to connect to"},
6316 {"user", ARG_STRING, false, &ds_user, "User to connect as"},
6317 {"passsword", ARG_STRING, false, &ds_password,
6318 "Password used when connecting"},
6319 {"database", ARG_STRING, false, &ds_database,
6320 "Database to select after connect"},
6321 {"port", ARG_STRING, false, &ds_port, "Port to connect to"},
6322 {"socket", ARG_STRING, false, &ds_sock, "Socket to connect with"},
6323 {"options", ARG_STRING, false, &ds_options,
6324 "Options to use while connecting"},
6325 {"default_auth", ARG_STRING, false, &ds_default_auth,
6326 "Default authentication to use"},
6327 {"default_compression_algorithm", ARG_STRING, false,
6328 &ds_compression_algorithm, "Default compression algorithm to use"},
6329 {"default_zstd_compression_level", ARG_STRING, false,
6330 &ds_zstd_compression_level,
6331 "Default compression level to use "
6332 "when using zstd compression."}};
6333
6334 DBUG_TRACE;
6335 DBUG_PRINT("enter", ("connect: %s", command->first_argument));
6336
6337 strip_parentheses(command);
6338 check_command_args(command, command->first_argument, connect_args,
6339 sizeof(connect_args) / sizeof(struct command_arg), ',');
6340
6341 /* Port */
6342 if (ds_port.length) {
6343 con_port = atoi(ds_port.str);
6344 if (con_port == 0) die("Illegal argument for port: '%s'", ds_port.str);
6345 }
6346
6347 /* Shared memory */
6348 init_dynamic_string(&ds_shm, ds_sock.str, 0, 0);
6349
6350 /* Sock */
6351 if (ds_sock.length) {
6352 /*
6353 If the socket is specified just as a name without path
6354 append tmpdir in front
6355 */
6356 if (*ds_sock.str != FN_LIBCHAR) {
6357 char buff[FN_REFLEN];
6358 fn_format(buff, ds_sock.str, TMPDIR, "", 0);
6359 dynstr_set(&ds_sock, buff);
6360 }
6361 } else {
6362 /* No socket specified, use default */
6363 dynstr_set(&ds_sock, unix_sock);
6364 }
6365 DBUG_PRINT("info", ("socket: %s", ds_sock.str));
6366
6367 /* Options */
6368 con_options = ds_options.str;
6369 bool con_socket = false, con_tcp = false;
6370 while (*con_options) {
6371 /* Step past any spaces in beginning of option */
6372 while (*con_options && my_isspace(charset_info, *con_options))
6373 con_options++;
6374
6375 /* Find end of this option */
6376 char *end = con_options;
6377 while (*end && !my_isspace(charset_info, *end)) end++;
6378
6379 size_t con_option_len = end - con_options;
6380 char cur_con_option[10] = {};
6381 strmake(cur_con_option, con_options, con_option_len);
6382
6383 if (!std::strcmp(cur_con_option, "SSL"))
6384 con_ssl = true;
6385 else if (!std::strcmp(cur_con_option, "COMPRESS"))
6386 con_compress = true;
6387 else if (!std::strcmp(cur_con_option, "PIPE"))
6388 con_pipe = true;
6389 else if (!std::strcmp(cur_con_option, "SHM"))
6390 con_shm = true;
6391 else if (!std::strcmp(cur_con_option, "CLEARTEXT"))
6392 con_cleartext_enable = true;
6393 else if (!std::strcmp(cur_con_option, "SOCKET"))
6394 con_socket = true;
6395 else if (!std::strcmp(cur_con_option, "TCP"))
6396 con_tcp = true;
6397 else
6398 die("Illegal option to connect: %s", cur_con_option);
6399
6400 /* Process next option */
6401 con_options = end;
6402 }
6403
6404 if (find_connection_by_name(ds_connection_name.str))
6405 die("Connection %s already exists", ds_connection_name.str);
6406
6407 if (next_con != connections_end)
6408 con_slot = next_con;
6409 else {
6410 if (!(con_slot = find_connection_by_name("-closed_connection-")))
6411 die("Connection limit exhausted, you can have max %d connections",
6412 opt_max_connections);
6413 }
6414
6415 if (!mysql_init(&con_slot->mysql)) die("Failed on mysql_init()");
6416
6417 if (opt_connect_timeout)
6418 mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
6419 (void *)&opt_connect_timeout);
6420
6421 if (opt_compress || con_compress)
6422 mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS);
6423 mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
6424 mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
6425 if (opt_charsets_dir)
6426 mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_DIR, opt_charsets_dir);
6427
6428 if (ds_compression_algorithm.length)
6429 mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
6430 ds_compression_algorithm.str);
6431 else if (opt_compress_algorithm)
6432 mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
6433 opt_compress_algorithm);
6434 if (ds_zstd_compression_level.length) {
6435 char *end = nullptr;
6436 opt_zstd_compress_level = strtol(ds_zstd_compression_level.str, &end, 10);
6437 }
6438 mysql_options(&con_slot->mysql, MYSQL_OPT_ZSTD_COMPRESSION_LEVEL,
6439 &opt_zstd_compress_level);
6440
6441 /*
6442 If mysqltest --ssl-mode option is set to DISABLED
6443 and connect(.., SSL) command used, set proper opt_ssl_mode.
6444
6445 So, SSL connection is used either:
6446 a) mysqltest --ssl-mode option is NOT set to DISABLED or
6447 b) connect(.., SSL) command used.
6448 */
6449 if (opt_ssl_mode == SSL_MODE_DISABLED && con_ssl) {
6450 opt_ssl_mode =
6451 (opt_ssl_ca || opt_ssl_capath) ? SSL_MODE_VERIFY_CA : SSL_MODE_REQUIRED;
6452 }
6453 if (SSL_SET_OPTIONS(&con_slot->mysql)) die("%s", SSL_SET_OPTIONS_ERROR);
6454 opt_ssl_mode = save_opt_ssl_mode;
6455
6456 if (con_pipe && !con_ssl) {
6457 opt_protocol = MYSQL_PROTOCOL_PIPE;
6458 }
6459
6460 if (opt_protocol) {
6461 mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol);
6462 /*
6463 Resetting the opt_protocol value to 0 to avoid the
6464 possible failure in the next connect() command.
6465 */
6466 opt_protocol = 0;
6467 }
6468
6469 if (con_shm) {
6470 uint protocol = MYSQL_PROTOCOL_MEMORY;
6471 if (!ds_shm.length) die("Missing shared memory base name");
6472
6473 mysql_options(&con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME, ds_shm.str);
6474 mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6475 } else if (shared_memory_base_name) {
6476 mysql_options(&con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME,
6477 shared_memory_base_name);
6478 }
6479
6480 if (con_socket) {
6481 uint protocol = MYSQL_PROTOCOL_SOCKET;
6482 mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6483 }
6484
6485 if (con_tcp) {
6486 uint protocol = MYSQL_PROTOCOL_TCP;
6487 mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6488 }
6489
6490 /* Use default db name */
6491 if (ds_database.length == 0) dynstr_set(&ds_database, opt_db);
6492
6493 if (opt_plugin_dir && *opt_plugin_dir)
6494 mysql_options(&con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);
6495
6496 if (ds_default_auth.length)
6497 mysql_options(&con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str);
6498
6499 /* Set server public_key */
6500 set_server_public_key(&con_slot->mysql);
6501
6502 set_get_server_public_key_option(&con_slot->mysql);
6503
6504 if (con_cleartext_enable)
6505 mysql_options(&con_slot->mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN,
6506 (char *)&con_cleartext_enable);
6507
6508 /* Special database to allow one to connect without a database name */
6509 if (ds_database.length && !std::strcmp(ds_database.str, "*NO-ONE*"))
6510 dynstr_set(&ds_database, "");
6511
6512 if (connect_n_handle_errors(command, &con_slot->mysql, ds_host.str,
6513 ds_user.str, ds_password.str, ds_database.str,
6514 con_port, ds_sock.str)) {
6515 DBUG_PRINT("info", ("Inserting connection %s in connection pool",
6516 ds_connection_name.str));
6517 my_free(con_slot->name);
6518 if (!(con_slot->name = my_strdup(PSI_NOT_INSTRUMENTED,
6519 ds_connection_name.str, MYF(MY_WME))))
6520 die("Out of memory");
6521 con_slot->name_len = std::strlen(con_slot->name);
6522 set_current_connection(con_slot);
6523
6524 if (con_slot == next_con)
6525 next_con++; /* if we used the next_con slot, advance the pointer */
6526 }
6527
6528 dynstr_free(&ds_connection_name);
6529 dynstr_free(&ds_host);
6530 dynstr_free(&ds_user);
6531 dynstr_free(&ds_password);
6532 dynstr_free(&ds_database);
6533 dynstr_free(&ds_port);
6534 dynstr_free(&ds_sock);
6535 dynstr_free(&ds_options);
6536 dynstr_free(&ds_default_auth);
6537 dynstr_free(&ds_shm);
6538 dynstr_free(&ds_compression_algorithm);
6539 dynstr_free(&ds_zstd_compression_level);
6540 }
6541
do_done(struct st_command * command)6542 static int do_done(struct st_command *command) {
6543 /* Check if empty block stack */
6544 if (cur_block == block_stack) {
6545 if (*command->query != '}')
6546 die("Stray 'end' command - end of block before beginning");
6547 die("Stray '}' - end of block before beginning");
6548 }
6549
6550 /* Test if inner block has been executed */
6551 if (cur_block->ok && cur_block->cmd == cmd_while) {
6552 /* Pop block from stack, re-execute outer block */
6553 cur_block--;
6554 parser.current_line = cur_block->line;
6555 } else {
6556 if (*cur_block->delim) {
6557 /* Restore "old" delimiter after false if block */
6558 strcpy(delimiter, cur_block->delim);
6559 delimiter_length = std::strlen(delimiter);
6560 }
6561 /* Pop block from stack, goto next line */
6562 cur_block--;
6563 parser.current_line++;
6564 }
6565 return 0;
6566 }
6567
6568 /* Operands available in if or while conditions */
6569
6570 enum block_op { EQ_OP, NE_OP, GT_OP, GE_OP, LT_OP, LE_OP, ILLEG_OP };
6571
find_operand(const char * start)6572 static enum block_op find_operand(const char *start) {
6573 char first = *start;
6574 char next = *(start + 1);
6575
6576 if (first == '=' && next == '=') return EQ_OP;
6577 if (first == '!' && next == '=') return NE_OP;
6578 if (first == '>' && next == '=') return GE_OP;
6579 if (first == '>') return GT_OP;
6580 if (first == '<' && next == '=') return LE_OP;
6581 if (first == '<') return LT_OP;
6582
6583 return ILLEG_OP;
6584 }
6585
6586 /*
6587 Process start of a "if" or "while" statement
6588
6589 SYNOPSIS
6590 do_block()
6591 cmd Type of block
6592 q called command
6593
6594 DESCRIPTION
6595 if ([!]<expr>)
6596 {
6597 <block statements>
6598 }
6599
6600 while ([!]<expr>)
6601 {
6602 <block statements>
6603 }
6604
6605 Evaluates the <expr> and if it evaluates to
6606 greater than zero executes the following code block.
6607 A '!' can be used before the <expr> to indicate it should
6608 be executed if it evaluates to zero.
6609
6610 <expr> can also be a simple comparison condition:
6611
6612 <variable> <op> <expr>
6613
6614 The left hand side must be a variable, the right hand side can be a
6615 variable, number, string or `query`. Operands are ==, !=, <, <=, >, >=.
6616 == and != can be used for strings, all can be used for numerical values.
6617 */
6618
do_block(enum block_cmd cmd,struct st_command * command)6619 static void do_block(enum block_cmd cmd, struct st_command *command) {
6620 const char *p = command->first_argument;
6621 const char *expr_start;
6622 const char *expr_end;
6623 VAR v;
6624 const char *cmd_name = (cmd == cmd_while ? "while" : "if");
6625 bool not_expr = false;
6626 DBUG_TRACE;
6627 DBUG_PRINT("enter", ("%s", cmd_name));
6628
6629 /* Check stack overflow */
6630 if (cur_block == block_stack_end) die("Nesting too deeply");
6631
6632 /* Set way to find outer block again, increase line counter */
6633 cur_block->line = parser.current_line++;
6634
6635 /* If this block is ignored */
6636 if (!cur_block->ok) {
6637 /* Inner block should be ignored too */
6638 cur_block++;
6639 cur_block->cmd = cmd;
6640 cur_block->ok = false;
6641 cur_block->delim[0] = '\0';
6642 return;
6643 }
6644
6645 /* Parse and evaluate test expression */
6646 expr_start = strchr(p, '(');
6647 if (!expr_start) die("missing '(' in %s", cmd_name);
6648 ++expr_start;
6649
6650 while (my_isspace(charset_info, *expr_start)) expr_start++;
6651
6652 /* Check for !<expr> */
6653 if (*expr_start == '!') {
6654 not_expr = true;
6655 expr_start++; /* Step past the '!', then any whitespace */
6656 while (*expr_start && my_isspace(charset_info, *expr_start)) expr_start++;
6657 }
6658 /* Find ending ')' */
6659 expr_end = strrchr(expr_start, ')');
6660 if (!expr_end) die("missing ')' in %s", cmd_name);
6661 p = expr_end + 1;
6662
6663 while (*p && my_isspace(charset_info, *p)) p++;
6664 if (*p && *p != '{') die("Missing '{' after %s. Found \"%s\"", cmd_name, p);
6665
6666 var_init(&v, nullptr, 0, nullptr, 0);
6667
6668 /* If expression starts with a variable, it may be a compare condition */
6669
6670 if (*expr_start == '$') {
6671 const char *curr_ptr = expr_end;
6672 eval_expr(&v, expr_start, &curr_ptr, true);
6673 while (my_isspace(charset_info, *++curr_ptr)) {
6674 }
6675 /* If there was nothing past the variable, skip condition part */
6676 if (curr_ptr == expr_end) goto NO_COMPARE;
6677
6678 enum block_op operand = find_operand(curr_ptr);
6679 if (operand == ILLEG_OP)
6680 die("Found junk '%.*s' after $variable in condition",
6681 (int)(expr_end - curr_ptr), curr_ptr);
6682
6683 /* We could silently allow this, but may be confusing */
6684 if (not_expr)
6685 die("Negation and comparison should not be combined, please rewrite");
6686
6687 /* Skip the 1 or 2 chars of the operand, then white space */
6688 if (operand == LT_OP || operand == GT_OP) {
6689 curr_ptr++;
6690 } else {
6691 curr_ptr += 2;
6692 }
6693 while (my_isspace(charset_info, *curr_ptr)) curr_ptr++;
6694 if (curr_ptr == expr_end) die("Missing right operand in comparison");
6695
6696 /* Strip off trailing white space */
6697 while (my_isspace(charset_info, expr_end[-1])) expr_end--;
6698 /* strip off ' or " around the string */
6699 if (*curr_ptr == '\'' || *curr_ptr == '"') {
6700 if (expr_end[-1] != *curr_ptr) die("Unterminated string value");
6701 curr_ptr++;
6702 expr_end--;
6703 }
6704 VAR v2;
6705 var_init(&v2, nullptr, 0, nullptr, 0);
6706 eval_expr(&v2, curr_ptr, &expr_end);
6707
6708 if ((operand != EQ_OP && operand != NE_OP) && !(v.is_int && v2.is_int))
6709 die("Only == and != are supported for string values");
6710
6711 /* Now we overwrite the first variable with 0 or 1 (for false or true) */
6712
6713 switch (operand) {
6714 case EQ_OP:
6715 if (v.is_int)
6716 v.int_val = (v2.is_int && v2.int_val == v.int_val);
6717 else
6718 v.int_val = !std::strcmp(v.str_val, v2.str_val);
6719 break;
6720
6721 case NE_OP:
6722 if (v.is_int)
6723 v.int_val = !(v2.is_int && v2.int_val == v.int_val);
6724 else
6725 v.int_val = (std::strcmp(v.str_val, v2.str_val) != 0);
6726 break;
6727
6728 case LT_OP:
6729 v.int_val = (v.int_val < v2.int_val);
6730 break;
6731 case LE_OP:
6732 v.int_val = (v.int_val <= v2.int_val);
6733 break;
6734 case GT_OP:
6735 v.int_val = (v.int_val > v2.int_val);
6736 break;
6737 case GE_OP:
6738 v.int_val = (v.int_val >= v2.int_val);
6739 break;
6740 case ILLEG_OP:
6741 die("Impossible operator, this cannot happen");
6742 }
6743
6744 v.is_int = true;
6745 var_free()(&v2);
6746 } else {
6747 if (*expr_start != '`' && !my_isdigit(charset_info, *expr_start))
6748 die("Expression in if/while must beging with $, ` or a number");
6749 eval_expr(&v, expr_start, &expr_end);
6750 }
6751
6752 NO_COMPARE:
6753 /* Define inner block */
6754 cur_block++;
6755 cur_block->cmd = cmd;
6756 if (v.is_int) {
6757 cur_block->ok = (v.int_val != 0);
6758 } else
6759 /* Any non-empty string which does not begin with 0 is also true */
6760 {
6761 p = v.str_val;
6762 /* First skip any leading white space or unary -+ */
6763 while (*p && ((my_isspace(charset_info, *p) || *p == '-' || *p == '+')))
6764 p++;
6765
6766 cur_block->ok = (*p && *p != '0') ? true : false;
6767 }
6768
6769 if (not_expr) cur_block->ok = !cur_block->ok;
6770
6771 if (cur_block->ok) {
6772 cur_block->delim[0] = '\0';
6773 } else {
6774 /* Remember "old" delimiter if entering a false if block */
6775 strcpy(cur_block->delim, delimiter);
6776 }
6777
6778 DBUG_PRINT("info", ("OK: %d", cur_block->ok));
6779
6780 var_free()(&v);
6781 }
6782
do_delimiter(struct st_command * command)6783 static void do_delimiter(struct st_command *command) {
6784 char *p = command->first_argument;
6785 DBUG_TRACE;
6786 DBUG_PRINT("enter", ("first_argument: %s", command->first_argument));
6787
6788 while (*p && my_isspace(charset_info, *p)) p++;
6789
6790 if (!(*p)) die("Can't set empty delimiter");
6791
6792 strmake(delimiter, p, sizeof(delimiter) - 1);
6793 delimiter_length = std::strlen(delimiter);
6794
6795 DBUG_PRINT("exit", ("delimiter: %s", delimiter));
6796 command->last_argument = p + delimiter_length;
6797 }
6798
6799 /*
6800 do_reset_connection
6801
6802 DESCRIPTION
6803 Reset the current session.
6804 */
do_reset_connection()6805 static void do_reset_connection() {
6806 MYSQL *mysql = &cur_con->mysql;
6807
6808 DBUG_TRACE;
6809 if (mysql_reset_connection(mysql))
6810 die("reset connection failed: %s", mysql_error(mysql));
6811 if (cur_con->stmt) {
6812 mysql_stmt_close(cur_con->stmt);
6813 cur_con->stmt = nullptr;
6814 }
6815 }
6816
match_delimiter(int c,const char * delim,size_t length)6817 bool match_delimiter(int c, const char *delim, size_t length) {
6818 uint i;
6819 char tmp[MAX_DELIMITER_LENGTH];
6820
6821 if (c != *delim) return false;
6822
6823 for (i = 1; i < length && (c = my_getc(cur_file->file)) == *(delim + i); i++)
6824 tmp[i] = c;
6825
6826 if (i == length) return true; /* Found delimiter */
6827
6828 /* didn't find delimiter, push back things that we read */
6829 my_ungetc(c);
6830 while (i > 1) my_ungetc(tmp[--i]);
6831 return false;
6832 }
6833
end_of_query(int c)6834 static bool end_of_query(int c) {
6835 return match_delimiter(c, delimiter, delimiter_length);
6836 }
6837
6838 /*
6839 Read one "line" from the file
6840
6841 SYNOPSIS
6842 read_line
6843 buf buffer for the read line
6844 size size of the buffer i.e max size to read
6845
6846 DESCRIPTION
6847 This function actually reads several lines and adds them to the
6848 buffer buf. It continues to read until it finds what it believes
6849 is a complete query.
6850
6851 Normally that means it will read lines until it reaches the
6852 "delimiter" that marks end of query. Default delimiter is ';'
6853 The function should be smart enough not to detect delimiter's
6854 found inside strings surrounded with '"' and '\'' escaped strings.
6855
6856 If the first line in a query starts with '#' or '-' this line is treated
6857 as a comment. A comment is always terminated when end of line '\n' is
6858 reached.
6859
6860 */
6861
read_line(char * buf,int size)6862 static int read_line(char *buf, int size) {
6863 char c, last_quote = 0, last_char = 0;
6864 char *p = buf, *buf_end = buf + size - 1;
6865 int skip_char = 0;
6866 int query_comment = 0, query_comment_start = 0, query_comment_end = 0;
6867 bool have_slash = false;
6868
6869 enum {
6870 R_NORMAL,
6871 R_Q,
6872 R_SLASH_IN_Q,
6873 R_COMMENT,
6874 R_LINE_START
6875 } state = R_LINE_START;
6876 DBUG_TRACE;
6877
6878 start_lineno = cur_file->lineno;
6879 DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno));
6880 for (; p < buf_end;) {
6881 skip_char = 0;
6882 c = my_getc(cur_file->file);
6883 if (feof(cur_file->file)) {
6884 found_eof:
6885 if (cur_file->file != stdin) {
6886 fclose(cur_file->file);
6887 cur_file->file = nullptr;
6888 }
6889 my_free(cur_file->file_name);
6890 cur_file->file_name = nullptr;
6891 if (cur_file == file_stack) {
6892 /* We're back at the first file, check if
6893 all { have matching }
6894 */
6895 if (cur_block != block_stack) die("Missing end of block");
6896
6897 *p = 0;
6898 DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno));
6899 return 1;
6900 }
6901 cur_file--;
6902 start_lineno = cur_file->lineno;
6903 continue;
6904 }
6905
6906 if (c == '\n') {
6907 /* Line counting is independent of state */
6908 cur_file->lineno++;
6909
6910 /* Convert cr/lf to lf */
6911 if (p != buf && *(p - 1) == '\r') p--;
6912 }
6913
6914 switch (state) {
6915 case R_NORMAL:
6916 if (end_of_query(c)) {
6917 *p = 0;
6918 DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", delimiter,
6919 cur_file->lineno));
6920 return 0;
6921 } else if ((c == '{' &&
6922 (!charset_info->coll->strnncoll(
6923 charset_info, (const uchar *)"while", 5, (uchar *)buf,
6924 std::min<ptrdiff_t>(5, p - buf), false) ||
6925 !charset_info->coll->strnncoll(
6926 charset_info, (const uchar *)"if", 2, (uchar *)buf,
6927 std::min<ptrdiff_t>(2, p - buf), false)))) {
6928 /* Only if and while commands can be terminated by { */
6929 *p++ = c;
6930 *p = 0;
6931 DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d",
6932 cur_file->lineno));
6933 return 0;
6934 } else if (c == '\'' || c == '"' || c == '`') {
6935 if (!have_slash) {
6936 last_quote = c;
6937 state = R_Q;
6938 }
6939 } else if (c == '/') {
6940 if ((query_comment_start == 0) && (query_comment == 0))
6941 query_comment_start = 1;
6942 else if (query_comment_end == 1) {
6943 query_comment = 0;
6944 query_comment_end = 0;
6945 }
6946 } else if (c == '*') {
6947 if ((query_comment == 1) && (query_comment_end == 0))
6948 query_comment_end = 1;
6949 else if (query_comment_start == 1)
6950 query_comment = 1;
6951 } else if ((c == '+') || (c == '!')) {
6952 if ((query_comment_start == 1) && (query_comment == 1)) {
6953 query_comment_start = 0;
6954 query_comment = 0;
6955 }
6956 } else if (query_comment_start == 1)
6957 query_comment_start = 0;
6958 else if (query_comment_end == 1)
6959 query_comment_end = 0;
6960
6961 have_slash = (c == '\\');
6962 break;
6963
6964 case R_COMMENT:
6965 if (c == '\n') {
6966 /* Comments are terminated by newline */
6967 *p = 0;
6968 DBUG_PRINT("exit", ("Found newline in comment at line: %d",
6969 cur_file->lineno));
6970 return 0;
6971 }
6972 break;
6973
6974 case R_LINE_START:
6975 if (c == '#' || c == '-') {
6976 /* A # or - in the first position of the line - this is a comment */
6977 state = R_COMMENT;
6978 } else if (my_isspace(charset_info, c)) {
6979 if (c == '\n') {
6980 if (last_char == '\n') {
6981 /* Two new lines in a row, return empty line */
6982 DBUG_PRINT("info", ("Found two new lines in a row"));
6983 *p++ = c;
6984 *p = 0;
6985 return 0;
6986 }
6987
6988 /* Query hasn't started yet */
6989 start_lineno = cur_file->lineno;
6990 DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d",
6991 start_lineno));
6992 }
6993
6994 /* Skip all space at begining of line */
6995 skip_char = 1;
6996 } else if (end_of_query(c)) {
6997 *p = 0;
6998 DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", delimiter,
6999 cur_file->lineno));
7000 return 0;
7001 } else if (c == '}') {
7002 /* A "}" need to be by itself in the begining of a line to terminate
7003 */
7004 *p++ = c;
7005 *p = 0;
7006 DBUG_PRINT("exit", ("Found '}' in begining of a line at line: %d",
7007 cur_file->lineno));
7008 return 0;
7009 } else if (c == '\'' || c == '"' || c == '`') {
7010 last_quote = c;
7011 state = R_Q;
7012 } else
7013 state = R_NORMAL;
7014 break;
7015
7016 case R_Q:
7017 if (c == last_quote)
7018 state = R_NORMAL;
7019 else if (c == '\\')
7020 state = R_SLASH_IN_Q;
7021 else if (query_comment)
7022 state = R_NORMAL;
7023 break;
7024
7025 case R_SLASH_IN_Q:
7026 state = R_Q;
7027 break;
7028 }
7029
7030 last_char = c;
7031
7032 if (!skip_char) {
7033 /* Could be a multibyte character */
7034 /* This code is based on the code in "sql_load.cc" */
7035 uint charlen;
7036 if (my_mbmaxlenlen(charset_info) == 1)
7037 charlen = my_mbcharlen(charset_info, (unsigned char)c);
7038 else {
7039 if (!(charlen = my_mbcharlen(charset_info, (unsigned char)c))) {
7040 int c1 = my_getc(cur_file->file);
7041 if (c1 == EOF) {
7042 *p++ = c;
7043 goto found_eof;
7044 }
7045
7046 charlen =
7047 my_mbcharlen_2(charset_info, (unsigned char)c, (unsigned char)c1);
7048 my_ungetc(c1);
7049 }
7050 }
7051 if (charlen == 0) return 1;
7052 /* We give up if multibyte character is started but not */
7053 /* completed before we pass buf_end */
7054 if ((charlen > 1) && (p + charlen) <= buf_end) {
7055 char *mb_start = p;
7056
7057 *p++ = c;
7058
7059 for (uint i = 1; i < charlen; i++) {
7060 c = my_getc(cur_file->file);
7061 if (feof(cur_file->file)) goto found_eof;
7062 *p++ = c;
7063 }
7064 if (!my_ismbchar(charset_info, mb_start, p)) {
7065 /* It was not a multiline char, push back the characters */
7066 /* We leave first 'c', i.e. pretend it was a normal char */
7067 while (p - 1 > mb_start) my_ungetc(*--p);
7068 }
7069 } else
7070 *p++ = c;
7071 }
7072 }
7073 die("The input buffer is too small for this query.x\n"
7074 "check your query or increase MAX_QUERY and recompile");
7075 return 0;
7076 }
7077
7078 /*
7079 Convert the read query to result format version 1
7080
7081 That is: After newline, all spaces need to be skipped
7082 unless the previous char was a quote
7083
7084 This is due to an old bug that has now been fixed, but the
7085 version 1 output format is preserved by using this function
7086
7087 */
7088
convert_to_format_v1(char * query)7089 static void convert_to_format_v1(char *query) {
7090 int last_c_was_quote = 0;
7091 char *p = query, *to = query;
7092 char *end = strend(query);
7093 char last_c;
7094
7095 while (p <= end) {
7096 if (*p == '\n' && !last_c_was_quote) {
7097 *to++ = *p++; /* Save the newline */
7098
7099 /* Skip any spaces on next line */
7100 while (*p && my_isspace(charset_info, *p)) p++;
7101
7102 last_c_was_quote = 0;
7103 } else if (*p == '\'' || *p == '"' || *p == '`') {
7104 last_c = *p;
7105 *to++ = *p++;
7106
7107 /* Copy anything until the next quote of same type */
7108 while (*p && *p != last_c) *to++ = *p++;
7109
7110 *to++ = *p++;
7111
7112 last_c_was_quote = 1;
7113 } else {
7114 *to++ = *p++;
7115 last_c_was_quote = 0;
7116 }
7117 }
7118 }
7119
7120 /*
7121 Check for unexpected "junk" after the end of query
7122 This is normally caused by missing delimiters or when
7123 switching between different delimiters
7124 */
7125
check_eol_junk_line(const char * line)7126 void check_eol_junk_line(const char *line) {
7127 const char *p = line;
7128 DBUG_TRACE;
7129 DBUG_PRINT("enter", ("line: %s", line));
7130
7131 /* Check for extra delimiter */
7132 if (*p && !std::strncmp(p, delimiter, delimiter_length))
7133 die("Extra delimiter \"%s\" found", delimiter);
7134
7135 /* Allow trailing # comment */
7136 if (*p && *p != '#') {
7137 if (*p == '\n') die("Missing delimiter");
7138 die("End of line junk detected: \"%s\"", p);
7139 }
7140 }
7141
check_eol_junk(const char * eol)7142 static void check_eol_junk(const char *eol) {
7143 const char *p = eol;
7144 DBUG_TRACE;
7145 DBUG_PRINT("enter", ("eol: %s", eol));
7146
7147 /* Skip past all spacing chars and comments */
7148 while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) {
7149 /* Skip past comments started with # and ended with newline */
7150 if (*p && *p == '#') {
7151 p++;
7152 while (*p && *p != '\n') p++;
7153 }
7154
7155 /* Check this line */
7156 if (*p && *p == '\n') check_eol_junk_line(p);
7157
7158 if (*p) p++;
7159 }
7160
7161 check_eol_junk_line(p);
7162 }
7163
is_delimiter(const char * p)7164 static bool is_delimiter(const char *p) {
7165 uint match = 0;
7166 char *delim = delimiter;
7167 while (*p && *p == *delim++) {
7168 match++;
7169 p++;
7170 }
7171
7172 return (match == delimiter_length);
7173 }
7174
7175 // 256K -- a test in sp-big is >128K
7176 #define MAX_QUERY (256 * 1024 * 2)
7177 static char read_command_buf[MAX_QUERY];
7178
7179 /// Create a command from a set of lines.
7180 ///
7181 /// Converts lines returned by read_line into a command, this involves
7182 /// parsing the first word in the read line to find the command type.
7183 ///
7184 /// A '`--`' comment may contain a valid query as the first word after
7185 /// the comment start. Thus it's always checked to see if that is the
7186 /// case. The advantage with this approach is to be able to execute
7187 /// commands terminated by new line '\n' regardless how many "delimiter"
7188 /// it contain.
7189 ///
7190 /// @param [in] command_ptr pointer where to return the new query
7191 ///
7192 /// @retval 0 on success, else 1
read_command(struct st_command ** command_ptr)7193 static int read_command(struct st_command **command_ptr) {
7194 char *p = read_command_buf;
7195 DBUG_TRACE;
7196
7197 if (parser.current_line < parser.read_lines) {
7198 *command_ptr = q_lines->at(parser.current_line);
7199 // Assign the current command line number
7200 start_lineno = (*command_ptr)->lineno;
7201 return 0;
7202 }
7203
7204 struct st_command *command;
7205 if (!(*command_ptr = command = (struct st_command *)my_malloc(
7206 PSI_NOT_INSTRUMENTED, sizeof(*command),
7207 MYF(MY_WME | MY_ZEROFILL))) ||
7208 q_lines->push_back(command))
7209 die("Out of memory");
7210 command->type = Q_UNKNOWN;
7211
7212 read_command_buf[0] = 0;
7213 if (read_line(read_command_buf, sizeof(read_command_buf))) {
7214 check_eol_junk(read_command_buf);
7215 return 1;
7216 }
7217
7218 // Set the line number for the command
7219 command->lineno = start_lineno;
7220
7221 if (opt_result_format_version == 1) convert_to_format_v1(read_command_buf);
7222
7223 DBUG_PRINT("info", ("query: '%s'", read_command_buf));
7224 if (*p == '#') {
7225 command->type = Q_COMMENT;
7226 } else if (p[0] == '-' && p[1] == '-') {
7227 command->type = Q_COMMENT_WITH_COMMAND;
7228 // Skip past '--'
7229 p += 2;
7230 } else if (*p == '\n') {
7231 command->type = Q_EMPTY_LINE;
7232 }
7233
7234 // Skip leading spaces
7235 while (*p && my_isspace(charset_info, *p)) p++;
7236
7237 if (!(command->query_buf = command->query =
7238 my_strdup(PSI_NOT_INSTRUMENTED, p, MYF(MY_WME))))
7239 die("Out of memory");
7240
7241 // Calculate first word length(the command), terminated
7242 // by 'space' , '(' or 'delimiter'
7243 p = command->query;
7244 while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p))
7245 p++;
7246 command->first_word_len = (uint)(p - command->query);
7247 DBUG_PRINT("info",
7248 ("first_word: %.*s", static_cast<int>(command->first_word_len),
7249 command->query));
7250
7251 // Skip spaces between command and first argument
7252 while (*p && my_isspace(charset_info, *p)) p++;
7253 command->first_argument = p;
7254
7255 command->end = strend(command->query);
7256 command->query_len = (command->end - command->query);
7257 parser.read_lines++;
7258 return 0;
7259 }
7260
7261 static struct my_option my_long_options[] = {
7262 #include "caching_sha2_passwordopt-longopts.h"
7263 #include "sslopt-longopts.h"
7264 {"basedir", 'b', "Basedir for tests.", &opt_basedir, &opt_basedir, nullptr,
7265 GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7266 {"character-sets-dir", OPT_CHARSETS_DIR,
7267 "Directory for character set files.", &opt_charsets_dir, &opt_charsets_dir,
7268 nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7269 {"colored-diff", OPT_COLORED_DIFF, "Colorize the diff outout.",
7270 &opt_colored_diff, &opt_colored_diff, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7271 nullptr, 0, nullptr},
7272 {"compress", 'C', "Use the compressed server/client protocol.",
7273 &opt_compress, &opt_compress, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
7274 0, nullptr},
7275 {"connect_timeout", OPT_CONNECT_TIMEOUT,
7276 "Number of seconds before connection timeout.", &opt_connect_timeout,
7277 &opt_connect_timeout, nullptr, GET_UINT, REQUIRED_ARG, 120, 0, 3600 * 12,
7278 nullptr, 0, nullptr},
7279 {"cursor-protocol", OPT_CURSOR_PROTOCOL,
7280 "Use cursors for prepared statements.", &cursor_protocol, &cursor_protocol,
7281 nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7282 {"database", 'D', "Database to use.", &opt_db, &opt_db, nullptr, GET_STR,
7283 REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7284 #ifdef DBUG_OFF
7285 {"debug", '#', "This is a non-debug version. Catch this and exit.", 0, 0, 0,
7286 GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
7287 {"debug-check", OPT_DEBUG_CHECK,
7288 "This is a non-debug version. Catch this and exit.", 0, 0, 0, GET_DISABLED,
7289 NO_ARG, 0, 0, 0, 0, 0, 0},
7290 {"debug-info", OPT_DEBUG_INFO,
7291 "This is a non-debug version. Catch this and exit.", 0, 0, 0, GET_DISABLED,
7292 NO_ARG, 0, 0, 0, 0, 0, 0},
7293 #else
7294 {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", nullptr,
7295 nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
7296 {"debug-check", OPT_DEBUG_CHECK,
7297 "Check memory and open file usage at exit.", &debug_check_flag,
7298 &debug_check_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7299 nullptr},
7300 {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.",
7301 &debug_info_flag, &debug_info_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7302 nullptr, 0, nullptr},
7303 #endif
7304 {"default-character-set", OPT_DEFAULT_CHARSET,
7305 "Set the default character set.", &default_charset, &default_charset,
7306 nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7307 {"explain-protocol", OPT_EXPLAIN_PROTOCOL,
7308 "Explain all SELECT/INSERT/REPLACE/UPDATE/DELETE statements",
7309 &explain_protocol, &explain_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7310 nullptr, 0, nullptr},
7311 {"help", '?', "Display this help and exit.", nullptr, nullptr, nullptr,
7312 GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7313 {"host", 'h', "Connect to host.", &opt_host, &opt_host, nullptr, GET_STR,
7314 REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7315 {"include", 'i', "Include SQL before each test case.", &opt_include,
7316 &opt_include, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7317 nullptr},
7318 {"json-explain-protocol", OPT_JSON_EXPLAIN_PROTOCOL,
7319 "Explain all SELECT/INSERT/REPLACE/UPDATE/DELETE statements with "
7320 "FORMAT=JSON",
7321 &json_explain_protocol, &json_explain_protocol, nullptr, GET_BOOL, NO_ARG,
7322 0, 0, 0, nullptr, 0, nullptr},
7323 {"logdir", OPT_LOG_DIR, "Directory for log files", &opt_logdir, &opt_logdir,
7324 nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7325 {"mark-progress", OPT_MARK_PROGRESS,
7326 "Write line number and elapsed time to <testname>.progress.",
7327 &opt_mark_progress, &opt_mark_progress, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7328 nullptr, 0, nullptr},
7329 {"max-connect-retries", OPT_MAX_CONNECT_RETRIES,
7330 "Maximum number of attempts to connect to server.",
7331 &opt_max_connect_retries, &opt_max_connect_retries, nullptr, GET_INT,
7332 REQUIRED_ARG, 500, 1, 10000, nullptr, 0, nullptr},
7333 {"max-connections", OPT_MAX_CONNECTIONS,
7334 "Max number of open connections to server", &opt_max_connections,
7335 &opt_max_connections, nullptr, GET_INT, REQUIRED_ARG, 128, 8, 5120,
7336 nullptr, 0, nullptr},
7337 {"no-skip", OPT_NO_SKIP, "Force the test to run without skip.", &no_skip,
7338 &no_skip, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7339 {"no-skip-exclude-list", 'n',
7340 "Contains comma seperated list of to be excluded inc files.",
7341 &excluded_string, &excluded_string, nullptr, GET_STR, REQUIRED_ARG, 0, 0,
7342 0, nullptr, 0, nullptr},
7343 {"offload-count-file", OPT_OFFLOAD_COUNT_FILE, "Offload count report file",
7344 &opt_offload_count_file, &opt_offload_count_file, nullptr, GET_STR,
7345 REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7346 {"opt-trace-protocol", OPT_TRACE_PROTOCOL,
7347 "Trace DML statements with optimizer trace", &opt_trace_protocol,
7348 &opt_trace_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7349 nullptr},
7350 {"password", 'p', "Password to use when connecting to server.", nullptr,
7351 nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
7352 {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
7353 &opt_plugin_dir, &opt_plugin_dir, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0,
7354 nullptr, 0, nullptr},
7355 {"port", 'P',
7356 "Port number to use for connection or 0 for default to, in "
7357 "order of preference, my.cnf, $MYSQL_TCP_PORT, "
7358 #if MYSQL_PORT_DEFAULT == 0
7359 "/etc/services, "
7360 #endif
7361 "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
7362 &opt_port, &opt_port, nullptr, GET_INT, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7363 nullptr},
7364 {"protocol", OPT_MYSQL_PROTOCOL,
7365 "The protocol of connection (tcp,socket,pipe,memory).", nullptr, nullptr,
7366 nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7367 {"ps-protocol", OPT_PS_PROTOCOL,
7368 "Use prepared-statement protocol for communication.", &ps_protocol,
7369 &ps_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7370 {"quiet", 's', "Suppress all normal output.", &silent, &silent, nullptr,
7371 GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7372 {"record", 'r', "Record output of test_file into result file.", nullptr,
7373 nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7374 {"result-file", 'R', "Read/store result from/in this file.",
7375 &result_file_name, &result_file_name, nullptr, GET_STR, REQUIRED_ARG, 0, 0,
7376 0, nullptr, 0, nullptr},
7377 {"result-format-version", OPT_RESULT_FORMAT_VERSION,
7378 "Version of the result file format to use", &opt_result_format_version,
7379 &opt_result_format_version, nullptr, GET_INT, REQUIRED_ARG, 1, 1, 2,
7380 nullptr, 0, nullptr},
7381 #ifdef _WIN32
7382 {"safe-process-pid", OPT_SAFEPROCESS_PID, "PID of safeprocess.",
7383 &opt_safe_process_pid, &opt_safe_process_pid, 0, GET_INT, REQUIRED_ARG, 0,
7384 0, 0, 0, 0, 0},
7385 #endif
7386 {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
7387 "Base name of shared memory.", &shared_memory_base_name,
7388 &shared_memory_base_name, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr,
7389 0, nullptr},
7390 {"silent", 's', "Suppress all normal output. Synonym for --quiet.", &silent,
7391 &silent, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7392 {"socket", 'S', "The socket file to use for connection.", &unix_sock,
7393 &unix_sock, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7394 {"sp-protocol", OPT_SP_PROTOCOL, "Use stored procedures for select.",
7395 &sp_protocol, &sp_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7396 nullptr},
7397 {"tail-lines", OPT_TAIL_LINES,
7398 "Number of lines of the result to include in a failure report.",
7399 &opt_tail_lines, &opt_tail_lines, nullptr, GET_INT, REQUIRED_ARG, 0, 0,
7400 10000, nullptr, 0, nullptr},
7401 {"test-file", 'x', "Read test from/in this file (default stdin).", nullptr,
7402 nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7403 {"timer-file", 'm', "File where the timing in microseconds is stored.",
7404 nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7405 nullptr},
7406 {"tmpdir", 't', "Temporary directory where sockets are put.", nullptr,
7407 nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7408 {"trace-exec", OPT_TRACE_EXEC, "Print output from exec to stdout.",
7409 &trace_exec, &trace_exec, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7410 nullptr},
7411 {"user", 'u', "User for login.", &opt_user, &opt_user, nullptr, GET_STR,
7412 REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7413 {"verbose", 'v', "Write more.", &verbose, &verbose, nullptr, GET_BOOL,
7414 NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7415 {"version", 'V', "Output version information and exit.", nullptr, nullptr,
7416 nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7417 {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select.",
7418 &view_protocol, &view_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7419 nullptr, 0, nullptr},
7420 {"async-client", '*', "Use async client.", &use_async_client,
7421 &use_async_client, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7422 nullptr},
7423 {"compression-algorithms", 0,
7424 "Use compression algorithm in server/client protocol. Valid values "
7425 "are any combination of 'zstd','zlib','uncompressed'.",
7426 &opt_compress_algorithm, &opt_compress_algorithm, nullptr, GET_STR,
7427 REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7428 {"zstd-compression-level", 0,
7429 "Use this compression level in the client/server protocol, in case "
7430 "--compression-algorithms=zstd. Valid range is between 1 and 22, "
7431 "inclusive. Default is 3.",
7432 &opt_zstd_compress_level, &opt_zstd_compress_level, nullptr, GET_UINT,
7433 REQUIRED_ARG, 3, 1, 22, nullptr, 0, nullptr},
7434
7435 {nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
7436 0, nullptr, 0, nullptr}};
7437
usage()7438 static void usage() {
7439 print_version();
7440 puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
7441 printf(
7442 "Runs a test against the mysql server and compares output with a results "
7443 "file.\n\n");
7444 printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname);
7445 my_print_help(my_long_options);
7446 printf(
7447 " --no-defaults Don't read default options from any options "
7448 "file.\n");
7449 my_print_variables(my_long_options);
7450 }
7451
get_one_option(int optid,const struct my_option * opt,char * argument)7452 static bool get_one_option(int optid, const struct my_option *opt,
7453 char *argument) {
7454 switch (optid) {
7455 case '#':
7456 #ifndef DBUG_OFF
7457 DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace");
7458 debug_check_flag = true;
7459 #endif
7460 break;
7461 case 'r':
7462 record = 1;
7463 break;
7464 case 'x': {
7465 char buff[FN_REFLEN];
7466 if (!test_if_hard_path(argument)) {
7467 strxmov(buff, opt_basedir, argument, NullS);
7468 argument = buff;
7469 }
7470 fn_format(buff, argument, "", "", MY_UNPACK_FILENAME);
7471 DBUG_ASSERT(cur_file == file_stack && cur_file->file == nullptr);
7472 if (!(cur_file->file = fopen(buff, "rb")))
7473 die("Could not open '%s' for reading, errno: %d", buff, errno);
7474 cur_file->file_name = my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE));
7475 cur_file->lineno = 1;
7476 break;
7477 }
7478 case 'm': {
7479 static char buff[FN_REFLEN];
7480 if (!test_if_hard_path(argument)) {
7481 strxmov(buff, opt_basedir, argument, NullS);
7482 argument = buff;
7483 }
7484 fn_format(buff, argument, "", "", MY_UNPACK_FILENAME);
7485 timer_file = buff;
7486 unlink(timer_file); /* Ignore error, may not exist */
7487 break;
7488 }
7489 case 'p':
7490 if (argument == disabled_my_option) {
7491 // Don't require password
7492 static char empty_password[] = {'\0'};
7493 DBUG_ASSERT(empty_password[0] ==
7494 '\0'); // Check that it has not been overwritten
7495 argument = empty_password;
7496 }
7497 if (argument) {
7498 my_free(opt_pass);
7499 opt_pass = my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE));
7500 while (*argument) *argument++ = 'x'; /* Destroy argument */
7501 tty_password = false;
7502 } else
7503 tty_password = true;
7504 break;
7505 #include "sslopt-case.h"
7506
7507 case 't':
7508 my_stpnmov(TMPDIR, argument, sizeof(TMPDIR));
7509 break;
7510 case OPT_LOG_DIR:
7511 /* Check that the file exists */
7512 if (access(opt_logdir, F_OK) != 0)
7513 die("The specified log directory does not exist: '%s'", opt_logdir);
7514 break;
7515 case OPT_RESULT_FORMAT_VERSION:
7516 set_result_format_version(opt_result_format_version);
7517 break;
7518 case 'V':
7519 print_version();
7520 exit(0);
7521 case OPT_MYSQL_PROTOCOL:
7522 opt_protocol =
7523 find_type_or_exit(argument, &sql_protocol_typelib, opt->name);
7524 break;
7525 case '?':
7526 usage();
7527 exit(0);
7528 }
7529 return false;
7530 }
7531
7532 /**
7533 Test case or the result file names may use alphanumeric characters
7534 (A-Z, a-z, 0-9), dash ('-') or underscore ('_'), but should not
7535 start with dash or underscore.
7536
7537 Check if a file name conatins any other special characters. If yes,
7538 throw an error and abort the test run.
7539
7540 @param[in] file_name File name
7541 */
7542
validate_filename(const char * file_name)7543 static void validate_filename(const char *file_name) {
7544 const char *fname = strrchr(file_name, '/');
7545
7546 if (fname == nullptr) {
7547 if (is_windows) {
7548 fname = strrchr(file_name, '\\');
7549
7550 if (fname == nullptr)
7551 fname = file_name;
7552 else
7553 fname++;
7554 } else
7555 fname = file_name;
7556 } else
7557 fname++;
7558
7559 file_name = fname;
7560
7561 // Check if first character in the file name is a alphanumeric character
7562 if (!my_isalnum(charset_info, file_name[0])) {
7563 die("Invalid file name '%s', first character must be alpha-numeric.",
7564 file_name);
7565 } else
7566 file_name++;
7567
7568 // Skip extension('.test' or '.result' or '.inc' etc) in the file name
7569 const char *file_name_end = strrchr(file_name, '.');
7570
7571 while (*file_name && (file_name != file_name_end) &&
7572 (file_name[0] == '-' || file_name[0] == '_' ||
7573 my_isalnum(charset_info, file_name[0]))) {
7574 file_name++;
7575 }
7576
7577 if (file_name != file_name_end) {
7578 die("Invalid file name '%s'. Test or result file name should "
7579 "consist of only alpha-numeric characters, dash (-) or "
7580 "underscore (_), but should not start with dash or "
7581 "underscore.",
7582 fname);
7583 }
7584 }
7585
parse_args(int argc,char ** argv)7586 static int parse_args(int argc, char **argv) {
7587 if (load_defaults("my", load_default_groups, &argc, &argv, &argv_alloc))
7588 exit(1);
7589
7590 if ((handle_options(&argc, &argv, my_long_options, get_one_option))) exit(1);
7591
7592 // Check for special characters in test case file name
7593 if (cur_file->file_name) validate_filename(cur_file->file_name);
7594
7595 // Check for special characters in result file name
7596 if (result_file_name) validate_filename(result_file_name);
7597
7598 if (argc > 1) {
7599 usage();
7600 exit(1);
7601 }
7602
7603 if (argc == 1) opt_db = *argv;
7604 if (tty_password) opt_pass = get_tty_password(NullS);
7605 if (debug_info_flag) my_end_arg = MY_CHECK_ERROR | MY_GIVE_INFO;
7606 if (debug_check_flag) my_end_arg = MY_CHECK_ERROR;
7607
7608 if (!record) {
7609 /* Check that the result file exists */
7610 if (result_file_name && access(result_file_name, F_OK) != 0)
7611 die("The specified result file '%s' does not exist", result_file_name);
7612 }
7613
7614 return 0;
7615 }
7616
7617 /*
7618 Write the content of str into file
7619
7620 SYNOPSIS
7621 str_to_file2
7622 fname - name of file to truncate/create and write to
7623 str - content to write to file
7624 size - size of content witten to file
7625 append - append to file instead of overwriting old file
7626 */
7627
str_to_file2(const char * fname,char * str,size_t size,bool append)7628 void str_to_file2(const char *fname, char *str, size_t size, bool append) {
7629 int fd;
7630 char buff[FN_REFLEN];
7631 int flags = O_WRONLY | O_CREAT;
7632 if (!test_if_hard_path(fname)) {
7633 strxmov(buff, opt_basedir, fname, NullS);
7634 fname = buff;
7635 }
7636 fn_format(buff, fname, "", "", MY_UNPACK_FILENAME);
7637
7638 if (!append) flags |= O_TRUNC;
7639 if ((fd = my_open(buff, flags, MYF(MY_WME))) < 0)
7640 die("Could not open '%s' for writing, errno: %d", buff, errno);
7641 if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR)
7642 die("Could not find end of file '%s', errno: %d", buff, errno);
7643 if (my_write(fd, (uchar *)str, size, MYF(MY_WME | MY_FNABP)))
7644 die("write failed, errno: %d", errno);
7645 my_close(fd, MYF(0));
7646 }
7647
7648 /*
7649 Write the content of str into file
7650
7651 SYNOPSIS
7652 str_to_file
7653 fname - name of file to truncate/create and write to
7654 str - content to write to file
7655 size - size of content witten to file
7656 */
7657
str_to_file(const char * fname,char * str,size_t size)7658 void str_to_file(const char *fname, char *str, size_t size) {
7659 str_to_file2(fname, str, size, false);
7660 }
7661
7662 #ifdef _WIN32
7663
7664 typedef Prealloced_array<const char *, 16> Patterns;
7665 Patterns *patterns;
7666
7667 /*
7668 init_win_path_patterns
7669
7670 DESCRIPTION
7671 Setup string patterns that will be used to detect filenames that
7672 needs to be converted from Win to Unix format
7673
7674 */
7675
init_win_path_patterns()7676 void init_win_path_patterns() {
7677 /* List of string patterns to match in order to find paths */
7678 const char *paths[] = {
7679 "$MYSQL_TEST_DIR", "$MYSQL_TMP_DIR", "$MYSQLTEST_VARDIR",
7680 "$MASTER_MYSOCK", "$MYSQL_SHAREDIR", "$MYSQL_CHARSETSDIR",
7681 "$MYSQL_LIBDIR", "./test/", ".ibd",
7682 ".\\ibdata", ".\\ibtmp", ".\\undo"};
7683 int num_paths = sizeof(paths) / sizeof(char *);
7684 int i;
7685 char *p;
7686
7687 DBUG_TRACE;
7688
7689 patterns = new Patterns(PSI_NOT_INSTRUMENTED);
7690
7691 /* Loop through all paths in the array */
7692 for (i = 0; i < num_paths; i++) {
7693 VAR *v;
7694 if (*(paths[i]) == '$') {
7695 v = var_get(paths[i], 0, 0, 0);
7696 p = my_strdup(PSI_NOT_INSTRUMENTED, v->str_val, MYF(MY_FAE));
7697 } else
7698 p = my_strdup(PSI_NOT_INSTRUMENTED, paths[i], MYF(MY_FAE));
7699
7700 /* Don't insert zero length strings in patterns array */
7701 if (std::strlen(p) == 0) {
7702 my_free(p);
7703 continue;
7704 }
7705
7706 if (patterns->push_back(p)) die("Out of memory");
7707
7708 DBUG_PRINT("info", ("p: %s", p));
7709 while (*p) {
7710 if (*p == '/') *p = '\\';
7711 p++;
7712 }
7713 }
7714 }
7715
free_win_path_patterns()7716 void free_win_path_patterns() {
7717 uint i = 0;
7718 const char **pat;
7719 for (pat = patterns->begin(); pat != patterns->end(); ++pat) {
7720 my_free(const_cast<char *>(*pat));
7721 }
7722 delete patterns;
7723 patterns = NULL;
7724 }
7725
7726 /*
7727 fix_win_paths
7728
7729 DESCRIPTION
7730 Search the string 'val' for the patterns that are known to be
7731 strings that contain filenames. Convert all \ to / in the
7732 filenames that are found.
7733
7734 Ex:
7735 val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist'
7736 => $MYSQL_TEST_DIR is found by strstr
7737 => all \ from c:\mysql\m... until next space is converted into /
7738 */
7739
fix_win_paths(const char * val,size_t len)7740 void fix_win_paths(const char *val, size_t len) {
7741 DBUG_TRACE;
7742 const char **pat;
7743 for (pat = patterns->begin(); pat != patterns->end(); ++pat) {
7744 char *p;
7745 DBUG_PRINT("info", ("pattern: %s", *pat));
7746
7747 /* Find and fix each path in this string */
7748 p = const_cast<char *>(val);
7749 while (p = strstr(p, *pat)) {
7750 DBUG_PRINT("info", ("Found %s in val p: %s", *pat, p));
7751 /* Found the pattern. Back up to the start of this path */
7752 while (p > val && !my_isspace(charset_info, *(p - 1))) {
7753 p--;
7754 }
7755
7756 while (*p && !my_isspace(charset_info, *p)) {
7757 if (*p == '\\') *p = '/';
7758 p++;
7759 }
7760 DBUG_PRINT("info", ("Converted \\ to / in %s", val));
7761 }
7762 }
7763 DBUG_PRINT("exit", (" val: %s, len: %d", val, len));
7764 }
7765 #endif
7766
7767 /*
7768 Append the result for one field to the dynamic string ds
7769 */
7770
append_field(DYNAMIC_STRING * ds,uint col_idx,MYSQL_FIELD * field,char * val,size_t len,bool is_null)7771 static void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD *field,
7772 char *val, size_t len, bool is_null) {
7773 char null[] = "NULL";
7774
7775 if (col_idx < max_replace_column && replace_column[col_idx]) {
7776 val = replace_column[col_idx];
7777 len = std::strlen(val);
7778 } else if (is_null) {
7779 val = null;
7780 len = 4;
7781 }
7782 #ifdef _WIN32
7783 else if ((field->type == MYSQL_TYPE_DOUBLE ||
7784 field->type == MYSQL_TYPE_FLOAT) &&
7785 field->decimals >= 31) {
7786 /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */
7787 char *start = strchr(val, 'e');
7788 if (start && std::strlen(start) >= 5 &&
7789 (start[1] == '-' || start[1] == '+') && start[2] == '0') {
7790 start += 2; /* Now points at first '0' */
7791 if (field->flags & ZEROFILL_FLAG) {
7792 /* Move all chars before the first '0' one step right */
7793 memmove(val + 1, val, start - val);
7794 *val = '0';
7795 } else {
7796 /* Move all chars after the first '0' one step left */
7797 memmove(start, start + 1, std::strlen(start));
7798 len--;
7799 }
7800 }
7801 }
7802 #endif
7803
7804 if (!display_result_vertically) {
7805 if (col_idx) dynstr_append_mem(ds, "\t", 1);
7806 replace_dynstr_append_mem(ds, val, len);
7807 } else {
7808 dynstr_append(ds, field->name);
7809 dynstr_append_mem(ds, "\t", 1);
7810 replace_dynstr_append_mem(ds, val, len);
7811 dynstr_append_mem(ds, "\n", 1);
7812 }
7813 }
7814
7815 /*
7816 Append all results to the dynamic string separated with '\t'
7817 Values may be converted with 'replace_column'
7818 */
7819
append_result(DYNAMIC_STRING * ds,MYSQL_RES * res)7820 static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) {
7821 MYSQL_ROW row;
7822 uint num_fields = mysql_num_fields(res);
7823 MYSQL_FIELD *fields = mysql_fetch_fields(res);
7824 ulong *lengths;
7825
7826 while ((row = mysql_fetch_row_wrapper(res))) {
7827 uint i;
7828 lengths = mysql_fetch_lengths(res);
7829 for (i = 0; i < num_fields; i++) {
7830 /* looks ugly , but put here to convince parfait */
7831 assert(lengths);
7832 append_field(ds, i, &fields[i], row[i], lengths[i], !row[i]);
7833 }
7834 if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1);
7835 }
7836 }
7837
7838 /*
7839 Append all results from ps execution to the dynamic string separated
7840 with '\t'. Values may be converted with 'replace_column'
7841 */
7842
append_stmt_result(DYNAMIC_STRING * ds,MYSQL_STMT * stmt,MYSQL_FIELD * fields,uint num_fields)7843 static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt,
7844 MYSQL_FIELD *fields, uint num_fields) {
7845 MYSQL_BIND *my_bind;
7846 bool *is_null;
7847 ulong *length;
7848 uint i;
7849
7850 /* Allocate array with bind structs, lengths and NULL flags */
7851 my_bind = (MYSQL_BIND *)my_malloc(PSI_NOT_INSTRUMENTED,
7852 num_fields * sizeof(MYSQL_BIND),
7853 MYF(MY_WME | MY_FAE | MY_ZEROFILL));
7854 length = (ulong *)my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(ulong),
7855 MYF(MY_WME | MY_FAE));
7856 is_null = (bool *)my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(bool),
7857 MYF(MY_WME | MY_FAE));
7858
7859 /* Allocate data for the result of each field */
7860 for (i = 0; i < num_fields; i++) {
7861 size_t max_length = fields[i].max_length + 1;
7862 my_bind[i].buffer_type = MYSQL_TYPE_STRING;
7863 my_bind[i].buffer =
7864 my_malloc(PSI_NOT_INSTRUMENTED, max_length, MYF(MY_WME | MY_FAE));
7865 my_bind[i].buffer_length = static_cast<ulong>(max_length);
7866 my_bind[i].is_null = &is_null[i];
7867 my_bind[i].length = &length[i];
7868
7869 DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu", i,
7870 my_bind[i].buffer_type, my_bind[i].buffer_length));
7871 }
7872
7873 if (mysql_stmt_bind_result(stmt, my_bind))
7874 die("mysql_stmt_bind_result failed: %d: %s", mysql_stmt_errno(stmt),
7875 mysql_stmt_error(stmt));
7876
7877 while (mysql_stmt_fetch(stmt) == 0) {
7878 for (i = 0; i < num_fields; i++)
7879 append_field(ds, i, &fields[i], (char *)my_bind[i].buffer,
7880 *my_bind[i].length, *my_bind[i].is_null);
7881 if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1);
7882 }
7883
7884 int rc;
7885 if ((rc = mysql_stmt_fetch(stmt)) != MYSQL_NO_DATA)
7886 die("fetch didn't end with MYSQL_NO_DATA from statement: %d: %s; rc=%d",
7887 mysql_stmt_errno(stmt), mysql_stmt_error(stmt), rc);
7888
7889 for (i = 0; i < num_fields; i++) {
7890 /* Free data for output */
7891 my_free(my_bind[i].buffer);
7892 }
7893 /* Free array with bind structs, lengths and NULL flags */
7894 my_free(my_bind);
7895 my_free(length);
7896 my_free(is_null);
7897 }
7898
7899 /*
7900 Append metadata for fields to output
7901 */
7902
append_metadata(DYNAMIC_STRING * ds,MYSQL_FIELD * field,uint num_fields)7903 static void append_metadata(DYNAMIC_STRING *ds, MYSQL_FIELD *field,
7904 uint num_fields) {
7905 MYSQL_FIELD *field_end;
7906 dynstr_append(ds,
7907 "Catalog\tDatabase\tTable\tTable_alias\tColumn\t"
7908 "Column_alias\tType\tLength\tMax length\tIs_null\t"
7909 "Flags\tDecimals\tCharsetnr\n");
7910
7911 for (field_end = field + num_fields; field < field_end; field++) {
7912 dynstr_append_mem(ds, field->catalog, field->catalog_length);
7913 dynstr_append_mem(ds, "\t", 1);
7914 dynstr_append_mem(ds, field->db, field->db_length);
7915 dynstr_append_mem(ds, "\t", 1);
7916 dynstr_append_mem(ds, field->org_table, field->org_table_length);
7917 dynstr_append_mem(ds, "\t", 1);
7918 dynstr_append_mem(ds, field->table, field->table_length);
7919 dynstr_append_mem(ds, "\t", 1);
7920 dynstr_append_mem(ds, field->org_name, field->org_name_length);
7921 dynstr_append_mem(ds, "\t", 1);
7922 dynstr_append_mem(ds, field->name, field->name_length);
7923 dynstr_append_mem(ds, "\t", 1);
7924 replace_dynstr_append_uint(ds, field->type);
7925 dynstr_append_mem(ds, "\t", 1);
7926 replace_dynstr_append_uint(ds, field->length);
7927 dynstr_append_mem(ds, "\t", 1);
7928 replace_dynstr_append_uint(ds, field->max_length);
7929 dynstr_append_mem(ds, "\t", 1);
7930 dynstr_append_mem(ds, (IS_NOT_NULL(field->flags) ? "N" : "Y"), 1);
7931 dynstr_append_mem(ds, "\t", 1);
7932 replace_dynstr_append_uint(ds, field->flags);
7933 dynstr_append_mem(ds, "\t", 1);
7934 replace_dynstr_append_uint(ds, field->decimals);
7935 dynstr_append_mem(ds, "\t", 1);
7936 replace_dynstr_append_uint(ds, field->charsetnr);
7937 dynstr_append_mem(ds, "\n", 1);
7938 }
7939 }
7940
7941 /*
7942 Append affected row count and other info to output
7943 */
7944
append_info(DYNAMIC_STRING * ds,ulonglong affected_rows,const char * info)7945 static void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows,
7946 const char *info) {
7947 char buf[40], buff2[21];
7948 sprintf(buf, "affected rows: %s\n", llstr(affected_rows, buff2));
7949 dynstr_append(ds, buf);
7950 if (info) {
7951 dynstr_append(ds, "info: ");
7952 dynstr_append(ds, info);
7953 dynstr_append_mem(ds, "\n", 1);
7954 }
7955 }
7956
7957 /**
7958 @brief Append state change information (received through Ok packet) to the
7959 output.
7960
7961 @param [in,out] ds Dynamic string to hold the content to be printed.
7962 @param [in] mysql Connection handle.
7963 */
7964
append_session_track_info(DYNAMIC_STRING * ds,MYSQL * mysql)7965 static void append_session_track_info(DYNAMIC_STRING *ds, MYSQL *mysql) {
7966 for (unsigned int type = SESSION_TRACK_BEGIN; type <= SESSION_TRACK_END;
7967 type++) {
7968 const char *data;
7969 size_t data_length;
7970
7971 if (!mysql_session_track_get_first(mysql, (enum_session_state_type)type,
7972 &data, &data_length)) {
7973 /*
7974 Append the type information. Please update the definition of APPEND_TYPE
7975 when any changes are made to enum_session_state_type.
7976 */
7977 APPEND_TYPE(type);
7978 dynstr_append(ds, "-- ");
7979 dynstr_append_mem(ds, data, data_length);
7980 } else
7981 continue;
7982 while (!mysql_session_track_get_next(mysql, (enum_session_state_type)type,
7983 &data, &data_length)) {
7984 dynstr_append(ds, "\n-- ");
7985 dynstr_append_mem(ds, data, data_length);
7986 }
7987 dynstr_append(ds, "\n\n");
7988 }
7989 }
7990
7991 /*
7992 Display the table headings with the names tab separated
7993 */
7994
append_table_headings(DYNAMIC_STRING * ds,MYSQL_FIELD * field,uint num_fields)7995 static void append_table_headings(DYNAMIC_STRING *ds, MYSQL_FIELD *field,
7996 uint num_fields) {
7997 uint col_idx;
7998 for (col_idx = 0; col_idx < num_fields; col_idx++) {
7999 if (col_idx) dynstr_append_mem(ds, "\t", 1);
8000 replace_dynstr_append(ds, field[col_idx].name);
8001 }
8002 dynstr_append_mem(ds, "\n", 1);
8003 }
8004
8005 /// Check whether a given warning is in list of disabled or enabled warnings.
8006 ///
8007 /// @param warnings List of either disabled or enabled warnings.
8008 /// @param error Error number
8009 /// @param warning_found Boolean value, should be set to true if warning
8010 /// is found in the list, false otherwise.
8011 ///
8012 /// @retval True if the given warning is present in the list, and
8013 /// ignore flag for that warning is not set, false otherwise.
match_warnings(Expected_warnings * warnings,std::uint32_t error,bool * warning_found)8014 static bool match_warnings(Expected_warnings *warnings, std::uint32_t error,
8015 bool *warning_found) {
8016 bool match_found = false;
8017 std::vector<std::unique_ptr<Warning>>::iterator warning = warnings->begin();
8018
8019 for (; warning != warnings->end(); warning++) {
8020 if ((*warning)->warning_code() == error) {
8021 *warning_found = true;
8022 if (!(*warning)->ignore_warning()) {
8023 match_found = true;
8024 break;
8025 }
8026 }
8027 }
8028
8029 return match_found;
8030 }
8031
8032 /// Handle one warning which occurred during execution of a query.
8033 ///
8034 /// @param ds DYNAMIC_STRING object to store the warnings.
8035 /// @param warning Warning string
8036 ///
8037 /// @retval True if a warning is found in the list of disabled or enabled
8038 /// warnings, false otherwise.
handle_one_warning(DYNAMIC_STRING * ds,std::string warning)8039 static bool handle_one_warning(DYNAMIC_STRING *ds, std::string warning) {
8040 // Each line of show warnings output contains information about
8041 // error level, error code and the error/warning message separated
8042 // by '\t'. Parse each line from the show warnings output to
8043 // extract the error code and compare it with list of expected
8044 // warnings.
8045 bool warning_found = false;
8046 std::string error_code;
8047 std::stringstream warn_msg(warning);
8048
8049 while (std::getline(warn_msg, error_code, '\t')) {
8050 int errcode = get_int_val(error_code.c_str());
8051 if (errcode != -1) {
8052 if (disabled_warnings->count()) {
8053 // Print the warning if it doesn't match with any of the
8054 // disabled warnings.
8055 if (!match_warnings(disabled_warnings, errcode, &warning_found)) {
8056 dynstr_append_mem(ds, warning.c_str(), warning.length());
8057 dynstr_append_mem(ds, "\n", 1);
8058 }
8059 } else if (enabled_warnings->count()) {
8060 if (match_warnings(enabled_warnings, errcode, &warning_found)) {
8061 dynstr_append_mem(ds, warning.c_str(), warning.length());
8062 dynstr_append_mem(ds, "\n", 1);
8063 }
8064 }
8065 }
8066 }
8067
8068 return warning_found;
8069 }
8070
8071 /// Handle warnings which occurred during execution of a query.
8072 ///
8073 /// @param ds DYNAMIC_STRING object to store the warnings.
8074 /// @param ds_warnings String containing all the generated warnings.
handle_warnings(DYNAMIC_STRING * ds,const char * ds_warnings)8075 static void handle_warnings(DYNAMIC_STRING *ds, const char *ds_warnings) {
8076 bool warning_found = false;
8077 std::string warning;
8078 std::stringstream warnings(ds_warnings);
8079
8080 // Set warning_found only if at least one of the warning exist
8081 // in expected list of warnings.
8082 while (std::getline(warnings, warning))
8083 if (handle_one_warning(ds, warning)) warning_found = true;
8084
8085 // Throw an error and abort the test run if a query generates warnings
8086 // which are not listed as expected.
8087 if (!warning_found) {
8088 std::string warning_list;
8089
8090 if (disabled_warnings->count())
8091 warning_list = disabled_warnings->warnings_list();
8092 else if (enabled_warnings->count())
8093 warning_list = enabled_warnings->warnings_list();
8094
8095 die("Query '%s' didn't generate any of the expected warning(s) '%s'.",
8096 curr_command->query, warning_list.c_str());
8097 }
8098 }
8099
8100 /// Fetch warnings generated by server while executing a query and
8101 /// append them to warnings buffer 'ds'.
8102 ///
8103 /// @param ds DYNAMIC_STRING object to store the warnings
8104 /// @param mysql mysql handle object
8105 ///
8106 /// @retval Number of warnings appended to ds
append_warnings(DYNAMIC_STRING * ds,MYSQL * mysql)8107 static int append_warnings(DYNAMIC_STRING *ds, MYSQL *mysql) {
8108 unsigned int count;
8109 if (!(count = mysql_warning_count(mysql))) return 0;
8110
8111 // If one day we will support execution of multi-statements
8112 // through PS API we should not issue SHOW WARNINGS until
8113 // we have not read all results.
8114 DBUG_ASSERT(!mysql_more_results(mysql));
8115
8116 MYSQL_RES *warn_res;
8117 if (mysql_real_query_wrapper(mysql, "SHOW WARNINGS", 13))
8118 die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql));
8119
8120 if (!(warn_res = mysql_store_result_wrapper(mysql)))
8121 die("Warning count is %u but didn't get any warnings", count);
8122
8123 DYNAMIC_STRING ds_warnings;
8124 init_dynamic_string(&ds_warnings, "", 1024, 1024);
8125 append_result(&ds_warnings, warn_res);
8126 mysql_free_result_wrapper(warn_res);
8127
8128 if (disable_warnings &&
8129 (disabled_warnings->count() || enabled_warnings->count()))
8130 handle_warnings(ds, ds_warnings.str);
8131 else if (!disable_warnings)
8132 dynstr_append_mem(ds, ds_warnings.str, ds_warnings.length);
8133
8134 dynstr_free(&ds_warnings);
8135 return ds->length;
8136 }
8137
8138 /// Run query using MySQL C API
8139 ///
8140 /// @param cn Connection object
8141 /// @param command Pointer to the st_command structure which holds the
8142 /// arguments and information for the command.
8143 /// @param flags Flags indicating if we should SEND and/or REAP.
8144 /// @param query Query string
8145 /// @param query_len Length of the query string
8146 /// @param ds Output buffer to store the query result.
8147 /// @param ds_warnings Buffer to store the warnings generated while
8148 /// executing the query.
run_query_normal(struct st_connection * cn,struct st_command * command,int flags,const char * query,size_t query_len,DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_warnings)8149 static void run_query_normal(struct st_connection *cn,
8150 struct st_command *command, int flags,
8151 const char *query, size_t query_len,
8152 DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) {
8153 int error = 0;
8154 std::uint32_t counter = 0;
8155 MYSQL *mysql = &cn->mysql;
8156 MYSQL_RES *res = nullptr;
8157
8158 if (flags & QUERY_SEND_FLAG) {
8159 /* Send the query */
8160 if (mysql_send_query_wrapper(&cn->mysql, query,
8161 static_cast<ulong>(query_len))) {
8162 handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8163 mysql_sqlstate(mysql), ds);
8164 goto end;
8165 }
8166 }
8167
8168 if (!(flags & QUERY_REAP_FLAG)) {
8169 cn->pending = true;
8170 return;
8171 }
8172
8173 do {
8174 /*
8175 When on first result set, call mysql_read_query_result_wrapper to
8176 retrieve answer to the query sent earlier
8177 */
8178 if ((counter == 0) && mysql_read_query_result_wrapper(&cn->mysql)) {
8179 /* we've failed to collect the result set */
8180 cn->pending = true;
8181 handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8182 mysql_sqlstate(mysql), ds);
8183 goto end;
8184 }
8185
8186 /*
8187 Store the result of the query if it will return any fields
8188 */
8189 if (mysql_field_count(mysql) &&
8190 ((res = mysql_store_result_wrapper(mysql)) == nullptr)) {
8191 handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8192 mysql_sqlstate(mysql), ds);
8193 goto end;
8194 }
8195
8196 if (!disable_result_log) {
8197 if (res) {
8198 MYSQL_FIELD *fields = mysql_fetch_fields(res);
8199 std::uint32_t num_fields = mysql_num_fields(res);
8200
8201 if (display_metadata) append_metadata(ds, fields, num_fields);
8202
8203 if (!display_result_vertically)
8204 append_table_headings(ds, fields, num_fields);
8205
8206 append_result(ds, res);
8207 }
8208
8209 // Need to call mysql_affected_rows() before the "new"
8210 // query to find the warnings.
8211 if (!disable_info)
8212 append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql));
8213
8214 if (display_session_track_info) append_session_track_info(ds, mysql);
8215
8216 // Add all warnings to the result. We can't do this if we are in
8217 // the middle of processing results from multi-statement, because
8218 // this will break protocol.
8219 if ((!disable_warnings || disabled_warnings->count() ||
8220 enabled_warnings->count()) &&
8221 !mysql_more_results(mysql)) {
8222 if (append_warnings(ds_warnings, mysql) || ds_warnings->length) {
8223 dynstr_append_mem(ds, "Warnings:\n", 10);
8224 dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length);
8225 }
8226 }
8227 }
8228
8229 if (res) {
8230 mysql_free_result_wrapper(res);
8231 res = nullptr;
8232 }
8233 counter++;
8234 } while (!(error = mysql_next_result_wrapper(mysql)));
8235 if (error > 0) {
8236 // We got an error from mysql_next_result, maybe expected.
8237 handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8238 mysql_sqlstate(mysql), ds);
8239 goto end;
8240 }
8241
8242 // Successful and there are no more results.
8243 DBUG_ASSERT(error == -1);
8244
8245 // If we come here the query is both executed and read successfully.
8246 handle_no_error(command);
8247 revert_properties();
8248
8249 end:
8250 cn->pending = false;
8251
8252 // We save the return code (mysql_errno(mysql)) from the last call sent
8253 // to the server into the mysqltest builtin variable $mysql_errno. This
8254 // variable then can be used from the test case itself.
8255 var_set_errno(mysql_errno(mysql));
8256 }
8257
8258 /// Run query using prepared statement C API
8259 ///
8260 /// @param mysql mysql handle
8261 /// @param command Pointer to the st_command structure which holds the
8262 /// arguments and information for the command.
8263 /// @param query Query string
8264 /// @param query_len Length of the query string
8265 /// @param ds Output buffer to store the query result.
8266 /// @param ds_warnings Buffer to store the warnings generated while
8267 /// executing the query.
run_query_stmt(MYSQL * mysql,struct st_command * command,const char * query,size_t query_len,DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_warnings)8268 static void run_query_stmt(MYSQL *mysql, struct st_command *command,
8269 const char *query, size_t query_len,
8270 DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) {
8271 // Init a new stmt if it's not already one created for this connection.
8272 MYSQL_STMT *stmt;
8273 if (!(stmt = cur_con->stmt)) {
8274 if (!(stmt = mysql_stmt_init(mysql))) die("unable to init stmt structure");
8275 cur_con->stmt = stmt;
8276 }
8277
8278 DYNAMIC_STRING ds_prepare_warnings;
8279 DYNAMIC_STRING ds_execute_warnings;
8280
8281 // Init dynamic strings for warnings.
8282 if (!disable_warnings || disabled_warnings->count() ||
8283 enabled_warnings->count()) {
8284 init_dynamic_string(&ds_prepare_warnings, nullptr, 0, 256);
8285 init_dynamic_string(&ds_execute_warnings, nullptr, 0, 256);
8286 }
8287
8288 // Note that here 'res' is meta data result set
8289 MYSQL_RES *res = nullptr;
8290 int err = 0;
8291
8292 // Prepare the query
8293 if (mysql_stmt_prepare(stmt, query, static_cast<ulong>(query_len))) {
8294 handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8295 mysql_stmt_sqlstate(stmt), ds);
8296 goto end;
8297 }
8298
8299 // Get the warnings from mysql_stmt_prepare and keep them in a
8300 // separate string.
8301 if (!disable_warnings || disabled_warnings->count() ||
8302 enabled_warnings->count())
8303 append_warnings(&ds_prepare_warnings, mysql);
8304
8305 // No need to call mysql_stmt_bind_param() because we have no
8306 // parameter markers.
8307 if (cursor_protocol_enabled) {
8308 // Use cursor when retrieving result.
8309 unsigned long type = CURSOR_TYPE_READ_ONLY;
8310 if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void *)&type))
8311 die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
8312 mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
8313 }
8314
8315 // Execute the query
8316 if (mysql_stmt_execute(stmt)) {
8317 handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8318 mysql_stmt_sqlstate(stmt), ds);
8319 goto end;
8320 }
8321
8322 // When running in cursor_protocol get the warnings from execute here
8323 // and keep them in a separate string for later.
8324 if (cursor_protocol_enabled &&
8325 (!disable_warnings || disabled_warnings->count() ||
8326 enabled_warnings->count()))
8327 append_warnings(&ds_execute_warnings, mysql);
8328
8329 // We instruct that we want to update the "max_length" field in
8330 // mysql_stmt_store_result(), this is our only way to know how much
8331 // buffer to allocate for result data
8332 {
8333 bool one = true;
8334 if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void *)&one))
8335 die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
8336 mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
8337 }
8338
8339 do {
8340 // If we got here the statement succeeded and was expected to do so,
8341 // get data. Note that this can still give errors found during execution.
8342 // Store the result of the query if if will return any fields
8343 if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) {
8344 handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8345 mysql_stmt_sqlstate(stmt), ds);
8346 goto end;
8347 }
8348
8349 if (!disable_result_log) {
8350 // Not all statements creates a result set. If there is one we can
8351 // now create another normal result set that contains the meta
8352 // data. This set can be handled almost like any other non prepared
8353 // statement result set.
8354 if ((res = mysql_stmt_result_metadata(stmt)) != nullptr) {
8355 // Take the column count from meta info
8356 MYSQL_FIELD *fields = mysql_fetch_fields(res);
8357 std::uint32_t num_fields = mysql_num_fields(res);
8358
8359 if (display_metadata) append_metadata(ds, fields, num_fields);
8360
8361 if (!display_result_vertically)
8362 append_table_headings(ds, fields, num_fields);
8363
8364 append_stmt_result(ds, stmt, fields, num_fields);
8365
8366 // Free normal result set with meta data
8367 mysql_free_result_wrapper(res);
8368
8369 // Clear prepare warnings if there are execute warnings,
8370 // since they are probably duplicated.
8371 if (ds_execute_warnings.length || mysql->warning_count)
8372 dynstr_set(&ds_prepare_warnings, nullptr);
8373 } else {
8374 // This is a query without resultset
8375 }
8376
8377 // Fetch info before fetching warnings, since it will be reset
8378 // otherwise.
8379 if (!disable_info)
8380 append_info(ds, mysql_affected_rows(stmt->mysql), mysql_info(mysql));
8381
8382 if (display_session_track_info) append_session_track_info(ds, mysql);
8383
8384 // Add all warnings to the result. We can't do this if we are in
8385 // the middle of processing results from multi-statement, because
8386 // this will break protocol.
8387 if ((!disable_warnings || disabled_warnings->count() ||
8388 enabled_warnings->count()) &&
8389 !mysql_more_results(stmt->mysql)) {
8390 // Get the warnings from execute. Append warnings to ds,
8391 // if there are any.
8392 append_warnings(&ds_execute_warnings, mysql);
8393 if (ds_execute_warnings.length || ds_prepare_warnings.length ||
8394 ds_warnings->length) {
8395 dynstr_append_mem(ds, "Warnings:\n", 10);
8396
8397 // Append warnings if exist any
8398 if (ds_warnings->length)
8399 dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length);
8400
8401 // Append prepare warnings if exist any
8402 if (ds_prepare_warnings.length)
8403 dynstr_append_mem(ds, ds_prepare_warnings.str,
8404 ds_prepare_warnings.length);
8405
8406 // Append execute warnings if exist any
8407 if (ds_execute_warnings.length)
8408 dynstr_append_mem(ds, ds_execute_warnings.str,
8409 ds_execute_warnings.length);
8410 }
8411 }
8412 }
8413 } while ((err = mysql_stmt_next_result(stmt)) == 0);
8414
8415 if (err > 0) {
8416 // We got an error from mysql_stmt_next_result, maybe expected.
8417 handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8418 mysql_stmt_sqlstate(stmt), ds);
8419 goto end;
8420 }
8421
8422 // If we got here the statement was both executed and read successfully.
8423 handle_no_error(command);
8424
8425 end:
8426 if (!disable_warnings || disabled_warnings->count() ||
8427 enabled_warnings->count()) {
8428 dynstr_free(&ds_prepare_warnings);
8429 dynstr_free(&ds_execute_warnings);
8430 }
8431 revert_properties();
8432
8433 // We save the return code (mysql_stmt_errno(stmt)) from the last call sent
8434 // to the server into the mysqltest builtin variable $mysql_errno. This
8435 // variable then can be used from the test case itself.
8436 var_set_errno(mysql_stmt_errno(stmt));
8437
8438 // Close the statement if no reconnect, need new prepare.
8439 if (mysql->reconnect) {
8440 mysql_stmt_close(stmt);
8441 cur_con->stmt = nullptr;
8442 }
8443 }
8444
8445 /*
8446 Create a util connection if one does not already exists
8447 and use that to run the query
8448 This is done to avoid implict commit when creating/dropping objects such
8449 as view, sp etc.
8450 */
8451
util_query(MYSQL * org_mysql,const char * query)8452 static int util_query(MYSQL *org_mysql, const char *query) {
8453 MYSQL *mysql;
8454 DBUG_TRACE;
8455
8456 if (!(mysql = cur_con->util_mysql)) {
8457 DBUG_PRINT("info", ("Creating util_mysql"));
8458 if (!(mysql = mysql_init(mysql))) die("Failed in mysql_init()");
8459
8460 if (opt_connect_timeout)
8461 mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT,
8462 (void *)&opt_connect_timeout);
8463
8464 /* enable local infile, in non-binary builds often disabled by default */
8465 mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
8466 safe_connect(mysql, "util", org_mysql->host, org_mysql->user,
8467 org_mysql->passwd, org_mysql->db, org_mysql->port,
8468 org_mysql->unix_socket);
8469
8470 cur_con->util_mysql = mysql;
8471 }
8472
8473 return mysql_query_wrapper(mysql, query);
8474 }
8475
8476 /*
8477 Run query
8478
8479 SYNPOSIS
8480 run_query()
8481 mysql mysql handle
8482 command currrent command pointer
8483
8484 flags control the phased/stages of query execution to be performed
8485 if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG
8486 is on the result will be read - for regular query, both bits must be on
8487 */
8488
run_query(struct st_connection * cn,struct st_command * command,int flags)8489 static void run_query(struct st_connection *cn, struct st_command *command,
8490 int flags) {
8491 MYSQL *mysql = &cn->mysql;
8492 DYNAMIC_STRING *ds;
8493 DYNAMIC_STRING *save_ds = nullptr;
8494 DYNAMIC_STRING ds_sorted;
8495 DYNAMIC_STRING ds_warnings;
8496 DYNAMIC_STRING eval_query;
8497 const char *query;
8498 size_t query_len;
8499 bool view_created = false, sp_created = false;
8500 bool complete_query =
8501 ((flags & QUERY_SEND_FLAG) && (flags & QUERY_REAP_FLAG));
8502 DBUG_TRACE;
8503 dynstr_set(&ds_result, "");
8504
8505 if (cn->pending && (flags & QUERY_SEND_FLAG))
8506 die("Cannot run query on connection between send and reap");
8507
8508 if (!(flags & QUERY_SEND_FLAG) && !cn->pending)
8509 die("Cannot reap on a connection without pending send");
8510
8511 init_dynamic_string(&ds_warnings, nullptr, 0, 256);
8512 ds_warn = &ds_warnings;
8513
8514 /*
8515 Evaluate query if this is an eval command
8516 */
8517 if (command->type == Q_EVAL || command->type == Q_SEND_EVAL) {
8518 init_dynamic_string(&eval_query, "", command->query_len + 256, 1024);
8519 do_eval(&eval_query, command->query, command->end, false);
8520 query = eval_query.str;
8521 query_len = eval_query.length;
8522 } else {
8523 query = command->query;
8524 query_len = std::strlen(query);
8525 }
8526
8527 /*
8528 Create a temporary dynamic string to contain the
8529 output from this query.
8530 */
8531 if (command->output_file[0])
8532 ds = &ds_result;
8533 else
8534 ds = &ds_res;
8535
8536 /*
8537 Log the query into the output buffer
8538 */
8539 if (!disable_query_log && (flags & QUERY_SEND_FLAG)) {
8540 replace_dynstr_append_mem(ds, query, query_len);
8541 dynstr_append_mem(ds, delimiter, delimiter_length);
8542 dynstr_append_mem(ds, "\n", 1);
8543 }
8544
8545 if (view_protocol_enabled && complete_query &&
8546 search_protocol_re(&view_re, query)) {
8547 /*
8548 Create the query as a view.
8549 Use replace since view can exist from a failed mysqltest run
8550 */
8551 DYNAMIC_STRING query_str;
8552 init_dynamic_string(&query_str,
8553 "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ",
8554 query_len + 64, 256);
8555 dynstr_append_mem(&query_str, query, query_len);
8556 if (util_query(mysql, query_str.str)) {
8557 /*
8558 Failed to create the view, this is not fatal
8559 just run the query the normal way
8560 */
8561 DBUG_PRINT("view_create_error",
8562 ("Failed to create view '%s': %d: %s", query_str.str,
8563 mysql_errno(mysql), mysql_error(mysql)));
8564
8565 /* Log error to create view */
8566 verbose_msg("Failed to create view '%s' %d: %s", query_str.str,
8567 mysql_errno(mysql), mysql_error(mysql));
8568 } else {
8569 /*
8570 Yes, it was possible to create this query as a view
8571 */
8572 view_created = true;
8573 query = "SELECT * FROM mysqltest_tmp_v";
8574 query_len = std::strlen(query);
8575
8576 /*
8577 Collect warnings from create of the view that should otherwise
8578 have been produced when the SELECT was executed
8579 */
8580 append_warnings(&ds_warnings, cur_con->util_mysql);
8581 }
8582
8583 dynstr_free(&query_str);
8584 }
8585
8586 if (sp_protocol_enabled && complete_query &&
8587 search_protocol_re(&sp_re, query)) {
8588 /*
8589 Create the query as a stored procedure
8590 Drop first since sp can exist from a failed mysqltest run
8591 */
8592 DYNAMIC_STRING query_str;
8593 init_dynamic_string(&query_str,
8594 "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;",
8595 query_len + 64, 256);
8596 util_query(mysql, query_str.str);
8597 dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n");
8598 dynstr_append_mem(&query_str, query, query_len);
8599 if (util_query(mysql, query_str.str)) {
8600 /*
8601 Failed to create the stored procedure for this query,
8602 this is not fatal just run the query the normal way
8603 */
8604 DBUG_PRINT("sp_create_error",
8605 ("Failed to create sp '%s': %d: %s", query_str.str,
8606 mysql_errno(mysql), mysql_error(mysql)));
8607
8608 /* Log error to create sp */
8609 verbose_msg("Failed to create sp '%s' %d: %s", query_str.str,
8610 mysql_errno(mysql), mysql_error(mysql));
8611
8612 } else {
8613 sp_created = true;
8614
8615 query = "CALL mysqltest_tmp_sp()";
8616 query_len = std::strlen(query);
8617 }
8618 dynstr_free(&query_str);
8619 }
8620
8621 if (display_result_sorted) {
8622 /*
8623 Collect the query output in a separate string
8624 that can be sorted before it's added to the
8625 global result string
8626 */
8627 init_dynamic_string(&ds_sorted, "", 1024, 1024);
8628 save_ds = ds; /* Remember original ds */
8629 ds = &ds_sorted;
8630 }
8631
8632 /*
8633 Find out how to run this query
8634
8635 Always run with normal C API if it's not a complete
8636 SEND + REAP
8637
8638 If it is a '?' in the query it may be a SQL level prepared
8639 statement already and we can't do it twice
8640 */
8641 if (ps_protocol_enabled && complete_query &&
8642 search_protocol_re(&ps_re, query))
8643 run_query_stmt(mysql, command, query, query_len, ds, &ds_warnings);
8644 else
8645 run_query_normal(cn, command, flags, query, query_len, ds, &ds_warnings);
8646
8647 dynstr_free(&ds_warnings);
8648 ds_warn = nullptr;
8649 if (command->type == Q_EVAL || command->type == Q_SEND_EVAL)
8650 dynstr_free(&eval_query);
8651
8652 if (display_result_sorted) {
8653 /* Sort the result set and append it to result */
8654 dynstr_append_sorted(save_ds, &ds_sorted, start_sort_column);
8655 ds = save_ds;
8656 dynstr_free(&ds_sorted);
8657 }
8658
8659 if (sp_created) {
8660 if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp "))
8661 die("Failed to drop sp: %d: %s", mysql_errno(mysql), mysql_error(mysql));
8662 }
8663
8664 if (view_created) {
8665 if (util_query(mysql, "DROP VIEW mysqltest_tmp_v "))
8666 die("Failed to drop view: %d: %s", mysql_errno(mysql),
8667 mysql_error(mysql));
8668 }
8669 if (command->output_file[0]) {
8670 /* An output file was specified for _this_ query */
8671 str_to_file2(command->output_file, ds_result.str, ds_result.length, false);
8672 command->output_file[0] = 0;
8673 }
8674 }
8675
8676 /**
8677 Display the optimizer trace produced by the last executed statement.
8678 */
display_opt_trace(struct st_connection * cn,struct st_command * command,int flags)8679 static void display_opt_trace(struct st_connection *cn,
8680 struct st_command *command, int flags) {
8681 if (!disable_query_log && opt_trace_protocol_enabled && !cn->pending &&
8682 !expected_errors->count() &&
8683 search_protocol_re(&opt_trace_re, command->query)) {
8684 st_command save_command = *command;
8685 DYNAMIC_STRING query_str;
8686 init_dynamic_string(&query_str,
8687 "SELECT trace FROM information_schema.optimizer_trace"
8688 " /* injected by --opt-trace-protocol */",
8689 128, 128);
8690
8691 command->query = query_str.str;
8692 command->query_len = query_str.length;
8693 command->end = strend(command->query);
8694
8695 /* Sorted trace is not readable at all, don't bother to lower case */
8696 /* No need to keep old values, will be reset anyway */
8697 display_result_sorted = false;
8698 display_result_lower = false;
8699 run_query(cn, command, flags);
8700
8701 dynstr_free(&query_str);
8702 *command = save_command;
8703 }
8704 }
8705
run_explain(struct st_connection * cn,struct st_command * command,int flags,bool json)8706 static void run_explain(struct st_connection *cn, struct st_command *command,
8707 int flags, bool json) {
8708 if ((flags & QUERY_REAP_FLAG) && !expected_errors->count() &&
8709 search_protocol_re(&explain_re, command->query)) {
8710 st_command save_command = *command;
8711 DYNAMIC_STRING query_str;
8712 DYNAMIC_STRING ds_warning_messages;
8713
8714 init_dynamic_string(&ds_warning_messages, "", 0, 2048);
8715 init_dynamic_string(&query_str, json ? "EXPLAIN FORMAT=JSON " : "EXPLAIN ",
8716 256, 256);
8717 dynstr_append_mem(&query_str, command->query,
8718 command->end - command->query);
8719
8720 command->query = query_str.str;
8721 command->query_len = query_str.length;
8722 command->end = strend(command->query);
8723
8724 run_query(cn, command, flags);
8725
8726 dynstr_free(&query_str);
8727 dynstr_free(&ds_warning_messages);
8728
8729 *command = save_command;
8730 }
8731 }
8732
get_command_type(struct st_command * command)8733 static void get_command_type(struct st_command *command) {
8734 char save;
8735 uint type;
8736 DBUG_TRACE;
8737
8738 if (*command->query == '}') {
8739 command->type = Q_END_BLOCK;
8740 return;
8741 }
8742
8743 save = command->query[command->first_word_len];
8744 command->query[command->first_word_len] = 0;
8745 type = find_type(command->query, &command_typelib, FIND_TYPE_NO_PREFIX);
8746 command->query[command->first_word_len] = save;
8747 if (type > 0) {
8748 command->type = (enum enum_commands)type; /* Found command */
8749
8750 /*
8751 Look for case where "query" was explicitly specified to
8752 force command being sent to server
8753 */
8754 if (type == Q_QUERY) {
8755 /* Skip the "query" part */
8756 command->query = command->first_argument;
8757 }
8758 } else {
8759 /* No mysqltest command matched */
8760
8761 if (command->type != Q_COMMENT_WITH_COMMAND) {
8762 /* A query that will sent to mysqld */
8763 command->type = Q_QUERY;
8764 } else {
8765 /* -- "comment" that didn't contain a mysqltest command */
8766 die("Found line '%s' beginning with -- that didn't contain "
8767 "a valid mysqltest command, check your syntax or "
8768 "use # if you intended to write a comment",
8769 command->query);
8770 }
8771 }
8772 }
8773
8774 /// Record how many milliseconds it took to execute the test file
8775 /// up until the current line and write it to .progress file.
8776 ///
8777 /// @param progress_file Logfile object to store the progress information
8778 /// @param line Line number of the progress file where the progress
8779 /// information should be recorded.
mark_progress(Logfile * progress_file,int line)8780 static void mark_progress(Logfile *progress_file, int line) {
8781 static unsigned long long int progress_start = 0;
8782 unsigned long long int timer = timer_now();
8783
8784 if (!progress_start) progress_start = timer;
8785 timer = timer - progress_start;
8786
8787 std::string str_progress;
8788
8789 // Milliseconds since start
8790 std::string str_timer = std::to_string(timer);
8791 str_progress.append(str_timer);
8792 str_progress.append("\t");
8793
8794 // Parse the line number
8795 std::string str_line = std::to_string(line);
8796 str_progress.append(str_line);
8797 str_progress.append("\t");
8798
8799 // Filename
8800 str_progress.append(cur_file->file_name);
8801 str_progress.append(":");
8802
8803 // Line in file
8804 str_line = std::to_string(cur_file->lineno);
8805 str_progress.append(str_line);
8806 str_progress.append("\n");
8807
8808 if (progress_file->write(str_progress.c_str(), str_progress.length()) ||
8809 progress_file->flush()) {
8810 cleanup_and_exit(1);
8811 }
8812 }
8813
8814 #ifdef HAVE_STACKTRACE
dump_backtrace()8815 static void dump_backtrace() {
8816 struct st_connection *conn = cur_con;
8817
8818 fprintf(stderr, "mysqltest: ");
8819
8820 // Print the query and the line number
8821 if (start_lineno > 0) fprintf(stderr, "At line %u: ", start_lineno);
8822 fprintf(stderr, "%s\n", curr_command->query);
8823
8824 // Print the file stack
8825 if (cur_file && cur_file != file_stack) {
8826 fprintf(stderr, "In included ");
8827 print_file_stack();
8828 }
8829
8830 if (conn) fprintf(stderr, "conn->name: %s\n", conn->name);
8831
8832 fprintf(stderr, "Attempting backtrace.\n");
8833 fflush(stderr);
8834 my_print_stacktrace(nullptr, my_thread_stack_size);
8835 }
8836
8837 #else
dump_backtrace()8838 static void dump_backtrace() { fputs("Backtrace not available.\n", stderr); }
8839
8840 #endif
8841
signal_handler(int sig)8842 static void signal_handler(int sig) {
8843 fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n", sig);
8844 dump_backtrace();
8845
8846 fprintf(stderr, "Writing a core file.\n");
8847 fflush(stderr);
8848 my_write_core(sig);
8849 #ifndef _WIN32
8850 // Shouldn't get here but just in case
8851 exit(1);
8852 #endif
8853 }
8854
8855 #ifdef _WIN32
8856
exception_filter(EXCEPTION_POINTERS * exp)8857 LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) {
8858 __try {
8859 my_set_exception_pointers(exp);
8860 signal_handler(exp->ExceptionRecord->ExceptionCode);
8861 } __except (EXCEPTION_EXECUTE_HANDLER) {
8862 fputs("Got exception in exception handler!\n", stderr);
8863 }
8864
8865 return EXCEPTION_CONTINUE_SEARCH;
8866 }
8867
init_signal_handling(void)8868 static void init_signal_handling(void) {
8869 UINT mode;
8870
8871 mysqltest_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId());
8872 if (mysqltest_thread == NULL)
8873 die("OpenThread failed, err = %d.", GetLastError());
8874
8875 /* Set output destination of messages to the standard error stream. */
8876 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
8877 _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
8878 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
8879 _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
8880 _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
8881 _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
8882
8883 /* Do not not display the a error message box. */
8884 mode = SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX;
8885 SetErrorMode(mode);
8886
8887 SetUnhandledExceptionFilter(exception_filter);
8888 }
8889
8890 /// Function to handle the stacktrace request event.
8891 ///
8892 /// - Suspend the thread running the test
8893 /// - Fetch CONTEXT record from the thread handle
8894 /// - Initialize EXCEPTION_RECORD structure
8895 /// - Use EXCEPTION_POINTERS and EXCEPTION_RECORD to set EXCEPTION_POINTERS
8896 /// structure
8897 /// - Call exception_filter() method to generate to stack trace
8898 /// - Resume the suspended test thread
handle_wait_stacktrace_request_event()8899 static void handle_wait_stacktrace_request_event() {
8900 fprintf(stderr, "Test case timeout failure.\n");
8901
8902 // Suspend the thread running the test
8903 if (SuspendThread(mysqltest_thread) == -1) {
8904 DWORD error = GetLastError();
8905 CloseHandle(mysqltest_thread);
8906 die("Error suspending thread, err = %d.\n", error);
8907 }
8908
8909 // Fetch the thread context
8910 CONTEXT test_thread_ctx = {0};
8911 test_thread_ctx.ContextFlags = CONTEXT_FULL;
8912
8913 if (GetThreadContext(mysqltest_thread, &test_thread_ctx) == FALSE) {
8914 DWORD error = GetLastError();
8915 CloseHandle(mysqltest_thread);
8916 die("Error while fetching thread conext information, err = %d.\n", error);
8917 }
8918
8919 EXCEPTION_POINTERS exp = {0};
8920 exp.ContextRecord = &test_thread_ctx;
8921
8922 // Set up an Exception record with EXCEPTION_BREAKPOINT code
8923 EXCEPTION_RECORD exc_rec = {0};
8924 exc_rec.ExceptionCode = EXCEPTION_BREAKPOINT;
8925 exp.ExceptionRecord = &exc_rec;
8926
8927 exception_filter(&exp);
8928
8929 // Resume the suspended test thread
8930 if (ResumeThread(mysqltest_thread) == -1) {
8931 DWORD error = GetLastError();
8932 CloseHandle(mysqltest_thread);
8933 die("Error resuming thread, err = %d.\n", error);
8934 }
8935
8936 my_set_exception_pointers(nullptr);
8937 }
8938
8939 /// Thread waiting for timeout event to occur. If the event occurs,
8940 /// this method will trigger signal_handler() function.
wait_stacktrace_request_event()8941 static void wait_stacktrace_request_event() {
8942 DWORD wait_res = WaitForSingleObject(stacktrace_request_event, INFINITE);
8943 switch (wait_res) {
8944 case WAIT_OBJECT_0:
8945 handle_wait_stacktrace_request_event();
8946 break;
8947 default:
8948 die("Unexpected result %d from WaitForSingleObject.", wait_res);
8949 break;
8950 }
8951 CloseHandle(stacktrace_request_event);
8952 }
8953
8954 /// Create an event name from the safeprocess PID value of the form
8955 /// mysqltest[%d]stacktrace and spawn thread waiting for that event
8956 /// to occur.
8957 ///
8958 /// When this event occurs, signal_handler() method is called and
8959 /// stacktrace for the mysqltest client process is printed in the
8960 /// log file.
create_stacktrace_request_event()8961 static void create_stacktrace_request_event() {
8962 char event_name[64];
8963 std::sprintf(event_name, "mysqltest[%d]stacktrace", opt_safe_process_pid);
8964
8965 // Create an event for the signal handler
8966 if ((stacktrace_request_event = CreateEvent(NULL, TRUE, FALSE, event_name)) ==
8967 NULL)
8968 die("Failed to create timeout_event.");
8969
8970 wait_for_stacktrace_request_event_thread =
8971 std::thread(wait_stacktrace_request_event);
8972 }
8973
8974 #else /* _WIN32 */
8975
init_signal_handling(void)8976 static void init_signal_handling(void) {
8977 struct sigaction sa;
8978 DBUG_TRACE;
8979
8980 #ifdef HAVE_STACKTRACE
8981 my_init_stacktrace();
8982 #endif
8983
8984 sa.sa_flags = SA_RESETHAND | SA_NODEFER;
8985 sigemptyset(&sa.sa_mask);
8986 sigprocmask(SIG_SETMASK, &sa.sa_mask, nullptr);
8987
8988 sa.sa_handler = signal_handler;
8989
8990 sigaction(SIGSEGV, &sa, nullptr);
8991 sigaction(SIGABRT, &sa, nullptr);
8992 #ifdef SIGBUS
8993 sigaction(SIGBUS, &sa, nullptr);
8994 #endif
8995 sigaction(SIGILL, &sa, nullptr);
8996 sigaction(SIGFPE, &sa, nullptr);
8997 }
8998
8999 #endif /* !_WIN32 */
9000
main(int argc,char ** argv)9001 int main(int argc, char **argv) {
9002 struct st_command *command;
9003 bool abort_flag = false;
9004 int q_send_flag = 0;
9005 uint command_executed = 0, last_command_executed = 0;
9006 char output_file[FN_REFLEN];
9007 MY_INIT(argv[0]);
9008
9009 output_file[0] = 0;
9010 TMPDIR[0] = 0;
9011
9012 init_signal_handling();
9013
9014 /* Init file stack */
9015 memset(file_stack, 0, sizeof(file_stack));
9016 file_stack_end =
9017 file_stack + (sizeof(file_stack) / sizeof(struct st_test_file)) - 1;
9018 cur_file = file_stack;
9019
9020 /* Init block stack */
9021 memset(block_stack, 0, sizeof(block_stack));
9022 block_stack_end =
9023 block_stack + (sizeof(block_stack) / sizeof(struct st_block)) - 1;
9024 cur_block = block_stack;
9025 cur_block->ok = true; /* Outer block should always be executed */
9026 cur_block->cmd = cmd_none;
9027
9028 q_lines = new Q_lines(PSI_NOT_INSTRUMENTED);
9029
9030 var_hash =
9031 new collation_unordered_map<std::string, std::unique_ptr<VAR, var_free>>(
9032 charset_info, PSI_NOT_INSTRUMENTED);
9033
9034 {
9035 char path_separator[] = {FN_LIBCHAR, 0};
9036 var_set_string("SYSTEM_PATH_SEPARATOR", path_separator);
9037 }
9038 var_set_string("MYSQL_SERVER_VERSION", MYSQL_SERVER_VERSION);
9039 var_set_string("MYSQL_SYSTEM_TYPE", SYSTEM_TYPE);
9040 var_set_string("MYSQL_MACHINE_TYPE", MACHINE_TYPE);
9041 if (sizeof(void *) == 8) {
9042 var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "64");
9043 } else {
9044 var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "32");
9045 }
9046
9047 memset(&master_pos, 0, sizeof(master_pos));
9048
9049 parser.current_line = parser.read_lines = 0;
9050 memset(&var_reg, 0, sizeof(var_reg));
9051
9052 init_builtin_echo();
9053 #ifdef _WIN32
9054 is_windows = 1;
9055 init_win_path_patterns();
9056 #endif
9057
9058 init_dynamic_string(&ds_res, "", 2048, 2048);
9059 init_dynamic_string(&ds_result, "", 1024, 1024);
9060
9061 parse_args(argc, argv);
9062
9063 #ifdef _WIN32
9064 // Create an event to request stack trace when timeout occurs
9065 if (opt_safe_process_pid) create_stacktrace_request_event();
9066 #endif
9067
9068 /* Init connections, allocate 1 extra as buffer + 1 for default */
9069 connections = (struct st_connection *)my_malloc(
9070 PSI_NOT_INSTRUMENTED,
9071 (opt_max_connections + 2) * sizeof(struct st_connection),
9072 MYF(MY_WME | MY_ZEROFILL));
9073 connections_end = connections + opt_max_connections + 1;
9074 next_con = connections + 1;
9075
9076 var_set_int("$PS_PROTOCOL", ps_protocol);
9077 var_set_int("$SP_PROTOCOL", sp_protocol);
9078 var_set_int("$VIEW_PROTOCOL", view_protocol);
9079 var_set_int("$OPT_TRACE_PROTOCOL", opt_trace_protocol);
9080 var_set_int("$EXPLAIN_PROTOCOL", explain_protocol);
9081 var_set_int("$JSON_EXPLAIN_PROTOCOL", json_explain_protocol);
9082 var_set_int("$CURSOR_PROTOCOL", cursor_protocol);
9083
9084 var_set_int("$ENABLE_QUERY_LOG", 1);
9085 var_set_int("$ENABLE_ABORT_ON_ERROR", 1);
9086 var_set_int("$ENABLE_RESULT_LOG", 1);
9087 var_set_int("$ENABLE_CONNECT_LOG", 0);
9088 var_set_int("$ENABLE_WARNINGS", 1);
9089 var_set_int("$ENABLE_INFO", 0);
9090 var_set_int("$ENABLE_METADATA", 0);
9091 var_set_int("$ENABLE_ASYNC_CLIENT", 0);
9092
9093 DBUG_PRINT("info",
9094 ("result_file: '%s'", result_file_name ? result_file_name : ""));
9095 verbose_msg("Results saved in '%s'.",
9096 result_file_name ? result_file_name : "");
9097 if (mysql_server_init(0, nullptr, nullptr))
9098 die("Can't initialize MySQL server");
9099 server_initialized = true;
9100 if (cur_file == file_stack && cur_file->file == nullptr) {
9101 cur_file->file = stdin;
9102 cur_file->file_name =
9103 my_strdup(PSI_NOT_INSTRUMENTED, "<stdin>", MYF(MY_WME));
9104 cur_file->lineno = 1;
9105 }
9106
9107 if (log_file.open(opt_logdir, result_file_name, ".log")) cleanup_and_exit(1);
9108
9109 verbose_msg("Logging to '%s'.", log_file.file_name());
9110 enable_async_client = use_async_client;
9111
9112 // Creating a log file using current file name if result file doesn't exist.
9113 if (result_file_name) {
9114 if (log_file.open(opt_logdir, result_file_name, ".log"))
9115 cleanup_and_exit(1);
9116 } else {
9117 if (std::strcmp(cur_file->file_name, "<stdin>")) {
9118 if (log_file.open(opt_logdir, cur_file->file_name, ".log"))
9119 cleanup_and_exit(1);
9120 } else {
9121 if (log_file.open(opt_logdir, "stdin", ".log")) cleanup_and_exit(1);
9122 }
9123 }
9124
9125 if (opt_mark_progress) {
9126 if (result_file_name) {
9127 if (progress_file.open(opt_logdir, result_file_name, ".progress"))
9128 cleanup_and_exit(1);
9129 } else {
9130 if (std::strcmp(cur_file->file_name, "<stdin>")) {
9131 if (progress_file.open(opt_logdir, cur_file->file_name, ".progress"))
9132 cleanup_and_exit(1);
9133 } else {
9134 if (progress_file.open(opt_logdir, "stdin", ".progress"))
9135 cleanup_and_exit(1);
9136 }
9137 }
9138 verbose_msg("Tracing progress in '%s'.", progress_file.file_name());
9139 }
9140
9141 var_set_string("MYSQLTEST_FILE", cur_file->file_name);
9142
9143 /* Cursor protcol implies ps protocol */
9144 if (cursor_protocol) ps_protocol = true;
9145
9146 ps_protocol_enabled = ps_protocol;
9147 sp_protocol_enabled = sp_protocol;
9148 view_protocol_enabled = view_protocol;
9149 opt_trace_protocol_enabled = opt_trace_protocol;
9150 explain_protocol_enabled = explain_protocol;
9151 json_explain_protocol_enabled = json_explain_protocol;
9152 cursor_protocol_enabled = cursor_protocol;
9153
9154 st_connection *con = connections;
9155 if (!(mysql_init(&con->mysql))) die("Failed in mysql_init()");
9156 if (opt_connect_timeout)
9157 mysql_options(&con->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
9158 (void *)&opt_connect_timeout);
9159 if (opt_compress) mysql_options(&con->mysql, MYSQL_OPT_COMPRESS, NullS);
9160 mysql_options(&con->mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
9161 if (std::strcmp(default_charset, charset_info->csname) &&
9162 !(charset_info =
9163 get_charset_by_csname(default_charset, MY_CS_PRIMARY, MYF(MY_WME))))
9164 die("Invalid character set specified.");
9165 mysql_options(&con->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
9166 if (opt_charsets_dir)
9167 mysql_options(&con->mysql, MYSQL_SET_CHARSET_DIR, opt_charsets_dir);
9168
9169 if (opt_protocol)
9170 mysql_options(&con->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol);
9171
9172 /* Turn on VERIFY_IDENTITY mode only if host=="localhost". */
9173 if (opt_ssl_mode == SSL_MODE_VERIFY_IDENTITY) {
9174 if (!opt_host || std::strcmp(opt_host, "localhost"))
9175 opt_ssl_mode = SSL_MODE_VERIFY_CA;
9176 }
9177
9178 if (SSL_SET_OPTIONS(&con->mysql)) die("%s", SSL_SET_OPTIONS_ERROR);
9179 #if defined(_WIN32)
9180 if (shared_memory_base_name)
9181 mysql_options(&con->mysql, MYSQL_SHARED_MEMORY_BASE_NAME,
9182 shared_memory_base_name);
9183
9184 if (opt_ssl_mode == SSL_MODE_DISABLED)
9185 mysql_options(&con->mysql, MYSQL_OPT_PROTOCOL,
9186 (char *)&opt_protocol_for_default_connection);
9187 #endif
9188
9189 if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "default", MYF(MY_WME))))
9190 die("Out of memory");
9191
9192 safe_connect(&con->mysql, con->name, opt_host, opt_user, opt_pass, opt_db,
9193 opt_port, unix_sock);
9194
9195 /* Use all time until exit if no explicit 'start_timer' */
9196 timer_start = timer_now();
9197
9198 /*
9199 Initialize $mysql_errno with -1, so we can
9200 - distinguish it from valid values ( >= 0 ) and
9201 - detect if there was never a command sent to the server
9202 */
9203 var_set_errno(-1);
9204
9205 set_current_connection(con);
9206
9207 if (opt_include) {
9208 open_file(opt_include);
9209 }
9210
9211 if (opt_offload_count_file) {
9212 secondary_engine = new Secondary_engine();
9213 // Save the initial value of secondary engine execution status.
9214 if (secondary_engine->offload_count(&cur_con->mysql, "before"))
9215 cleanup_and_exit(1);
9216 }
9217
9218 verbose_msg("Start processing test commands from '%s' ...",
9219 cur_file->file_name);
9220 while (!read_command(&command) && !abort_flag) {
9221 int current_line_inc = 1, processed = 0;
9222 if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND)
9223 get_command_type(command);
9224
9225 if (command->type == Q_ERROR && expected_errors->count())
9226 // Delete all the error codes from previous 'error' command.
9227 expected_errors->clear_list();
9228
9229 if (testcase_disabled && command->type != Q_ENABLE_TESTCASE &&
9230 command->type != Q_DISABLE_TESTCASE) {
9231 // Test case is disabled, silently convert this line to a comment
9232 command->type = Q_COMMENT;
9233 }
9234
9235 /* (Re-)set abort_on_error for this command */
9236 command->abort_on_error = (expected_errors->count() == 0 && abort_on_error);
9237
9238 /* delimiter needs to be executed so we can continue to parse */
9239 bool ok_to_do = cur_block->ok || command->type == Q_DELIMITER;
9240
9241 /*
9242 'source' command needs to be "done" the first time if it may get
9243 re-iterated over in a true context. This can only happen if there's
9244 a while loop at some level above the current block.
9245 */
9246 if (!ok_to_do && command->type == Q_SOURCE) {
9247 for (struct st_block *stb = cur_block - 1; stb >= block_stack; stb--) {
9248 if (stb->cmd == cmd_while) {
9249 ok_to_do = true;
9250 break;
9251 }
9252 }
9253 }
9254
9255 /*
9256 Some commands need to be parsed in false context also to
9257 avoid any parsing errors.
9258 */
9259 if (!ok_to_do &&
9260 (command->type == Q_APPEND_FILE || command->type == Q_PERL ||
9261 command->type == Q_WRITE_FILE)) {
9262 ok_to_do = true;
9263 }
9264
9265 if (ok_to_do) {
9266 command->last_argument = command->first_argument;
9267 processed = 1;
9268 /* Need to remember this for handle_error() */
9269 curr_command = command;
9270 switch (command->type) {
9271 case Q_CONNECT:
9272 do_connect(command);
9273 break;
9274 case Q_CONNECTION:
9275 select_connection(command);
9276 break;
9277 case Q_DISCONNECT:
9278 case Q_DIRTY_CLOSE:
9279 do_close_connection(command);
9280 break;
9281 case Q_ENABLE_QUERY_LOG:
9282 set_property(command, P_QUERY, false);
9283 break;
9284 case Q_DISABLE_QUERY_LOG:
9285 set_property(command, P_QUERY, true);
9286 break;
9287 case Q_ENABLE_ABORT_ON_ERROR:
9288 set_property(command, P_ABORT, true);
9289 break;
9290 case Q_DISABLE_ABORT_ON_ERROR:
9291 set_property(command, P_ABORT, false);
9292 break;
9293 case Q_ENABLE_RESULT_LOG:
9294 set_property(command, P_RESULT, false);
9295 break;
9296 case Q_DISABLE_RESULT_LOG:
9297 set_property(command, P_RESULT, true);
9298 break;
9299 case Q_ENABLE_CONNECT_LOG:
9300 set_property(command, P_CONNECT, false);
9301 break;
9302 case Q_DISABLE_CONNECT_LOG:
9303 set_property(command, P_CONNECT, true);
9304 break;
9305 case Q_ENABLE_WARNINGS:
9306 do_enable_warnings(command);
9307 break;
9308 case Q_DISABLE_WARNINGS:
9309 do_disable_warnings(command);
9310 break;
9311 case Q_ENABLE_INFO:
9312 set_property(command, P_INFO, false);
9313 break;
9314 case Q_DISABLE_INFO:
9315 set_property(command, P_INFO, true);
9316 break;
9317 case Q_ENABLE_SESSION_TRACK_INFO:
9318 set_property(command, P_SESSION_TRACK, true);
9319 break;
9320 case Q_DISABLE_SESSION_TRACK_INFO:
9321 set_property(command, P_SESSION_TRACK, false);
9322 break;
9323 case Q_ENABLE_METADATA:
9324 set_property(command, P_META, true);
9325 break;
9326 case Q_DISABLE_METADATA:
9327 set_property(command, P_META, false);
9328 break;
9329 case Q_SOURCE:
9330 do_source(command);
9331 break;
9332 case Q_SLEEP:
9333 do_sleep(command);
9334 break;
9335 case Q_WAIT_FOR_SLAVE_TO_STOP:
9336 do_wait_for_slave_to_stop(command);
9337 break;
9338 case Q_INC:
9339 do_modify_var(command, DO_INC);
9340 break;
9341 case Q_DEC:
9342 do_modify_var(command, DO_DEC);
9343 break;
9344 case Q_ECHO:
9345 do_echo(command);
9346 command_executed++;
9347 break;
9348 case Q_REMOVE_FILE:
9349 do_remove_file(command);
9350 break;
9351 case Q_REMOVE_FILES_WILDCARD:
9352 do_remove_files_wildcard(command);
9353 break;
9354 case Q_COPY_FILES_WILDCARD:
9355 do_copy_files_wildcard(command);
9356 break;
9357 case Q_MKDIR:
9358 do_mkdir(command);
9359 break;
9360 case Q_RMDIR:
9361 do_rmdir(command, false);
9362 break;
9363 case Q_FORCE_RMDIR:
9364 do_rmdir(command, true);
9365 break;
9366 case Q_FORCE_CPDIR:
9367 do_force_cpdir(command);
9368 break;
9369 case Q_LIST_FILES:
9370 do_list_files(command);
9371 break;
9372 case Q_LIST_FILES_WRITE_FILE:
9373 do_list_files_write_file_command(command, false);
9374 break;
9375 case Q_LIST_FILES_APPEND_FILE:
9376 do_list_files_write_file_command(command, true);
9377 break;
9378 case Q_FILE_EXIST:
9379 do_file_exist(command);
9380 break;
9381 case Q_WRITE_FILE:
9382 do_write_file(command);
9383 break;
9384 case Q_APPEND_FILE:
9385 do_append_file(command);
9386 break;
9387 case Q_DIFF_FILES:
9388 do_diff_files(command);
9389 break;
9390 case Q_SEND_QUIT:
9391 do_send_quit(command);
9392 break;
9393 case Q_CHANGE_USER:
9394 do_change_user(command);
9395 break;
9396 case Q_CAT_FILE:
9397 do_cat_file(command);
9398 break;
9399 case Q_COPY_FILE:
9400 do_copy_file(command);
9401 break;
9402 case Q_MOVE_FILE:
9403 do_move_file(command);
9404 break;
9405 case Q_CHMOD_FILE:
9406 do_chmod_file(command);
9407 break;
9408 case Q_PERL:
9409 do_perl(command);
9410 break;
9411 case Q_RESULT_FORMAT_VERSION:
9412 do_result_format_version(command);
9413 break;
9414 case Q_DELIMITER:
9415 do_delimiter(command);
9416 break;
9417 case Q_DISPLAY_VERTICAL_RESULTS:
9418 display_result_vertically = true;
9419 break;
9420 case Q_DISPLAY_HORIZONTAL_RESULTS:
9421 display_result_vertically = false;
9422 break;
9423 case Q_SORTED_RESULT:
9424 /*
9425 Turn on sorting of result set, will be reset after next
9426 command
9427 */
9428 display_result_sorted = true;
9429 start_sort_column = 0;
9430 break;
9431 case Q_PARTIALLY_SORTED_RESULT:
9432 /*
9433 Turn on sorting of result set, will be reset after next
9434 command
9435 */
9436 display_result_sorted = true;
9437 start_sort_column = atoi(command->first_argument);
9438 command->last_argument = command->end;
9439 break;
9440 case Q_LOWERCASE:
9441 /*
9442 Turn on lowercasing of result, will be reset after next
9443 command
9444 */
9445 display_result_lower = true;
9446 break;
9447 case Q_LET:
9448 do_let(command);
9449 break;
9450 case Q_EXPR:
9451 do_expr(command);
9452 break;
9453 case Q_EVAL:
9454 case Q_QUERY_VERTICAL:
9455 case Q_QUERY_HORIZONTAL:
9456 if (command->query == command->query_buf) {
9457 /* Skip the first part of command, i.e query_xxx */
9458 command->query = command->first_argument;
9459 command->first_word_len = 0;
9460 }
9461 /* fall through */
9462 case Q_QUERY:
9463 case Q_REAP: {
9464 bool old_display_result_vertically = display_result_vertically;
9465 /* Default is full query, both reap and send */
9466 int flags = QUERY_REAP_FLAG | QUERY_SEND_FLAG;
9467
9468 if (q_send_flag) {
9469 // Last command was an empty 'send' or 'send_eval'
9470 flags = QUERY_SEND_FLAG;
9471 if (q_send_flag == 2)
9472 // Last command was an empty 'send_eval' command. Set the command
9473 // type to Q_SEND_EVAL so that the variable gets replaced with its
9474 // value before executing.
9475 command->type = Q_SEND_EVAL;
9476 q_send_flag = 0;
9477 } else if (command->type == Q_REAP) {
9478 flags = QUERY_REAP_FLAG;
9479 }
9480
9481 /* Check for special property for this query */
9482 display_result_vertically |= (command->type == Q_QUERY_VERTICAL);
9483
9484 /*
9485 We run EXPLAIN _before_ the query. If query is UPDATE/DELETE is
9486 matters: a DELETE may delete rows, and then EXPLAIN DELETE will
9487 usually terminate quickly with "no matching rows". To make it more
9488 interesting, EXPLAIN is now first.
9489 */
9490 if (explain_protocol_enabled)
9491 run_explain(cur_con, command, flags, false);
9492 if (json_explain_protocol_enabled)
9493 run_explain(cur_con, command, flags, true);
9494
9495 if (*output_file) {
9496 strmake(command->output_file, output_file, sizeof(output_file) - 1);
9497 *output_file = 0;
9498 }
9499 run_query(cur_con, command, flags);
9500 display_opt_trace(cur_con, command, flags);
9501 command_executed++;
9502 command->last_argument = command->end;
9503
9504 /* Restore settings */
9505 display_result_vertically = old_display_result_vertically;
9506
9507 break;
9508 }
9509 case Q_SEND:
9510 case Q_SEND_EVAL:
9511 if (!*command->first_argument) {
9512 // This is a 'send' or 'send_eval' command without arguments, it
9513 // indicates that _next_ query should be send only.
9514 if (command->type == Q_SEND)
9515 q_send_flag = 1;
9516 else if (command->type == Q_SEND_EVAL)
9517 q_send_flag = 2;
9518 break;
9519 }
9520
9521 /* Remove "send" if this is first iteration */
9522 if (command->query == command->query_buf)
9523 command->query = command->first_argument;
9524
9525 /*
9526 run_query() can execute a query partially, depending on the flags.
9527 QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send
9528 the query and read the result some time later when reap instruction
9529 is given on this connection.
9530 */
9531 run_query(cur_con, command, QUERY_SEND_FLAG);
9532 command_executed++;
9533 command->last_argument = command->end;
9534 break;
9535 case Q_ERROR:
9536 do_error(command);
9537 break;
9538 case Q_REPLACE:
9539 do_get_replace(command);
9540 break;
9541 case Q_REPLACE_REGEX:
9542 do_get_replace_regex(command);
9543 break;
9544 case Q_REPLACE_COLUMN:
9545 do_get_replace_column(command);
9546 break;
9547 case Q_REPLACE_NUMERIC_ROUND:
9548 do_get_replace_numeric_round(command);
9549 break;
9550 case Q_SAVE_MASTER_POS:
9551 do_save_master_pos();
9552 break;
9553 case Q_SYNC_WITH_MASTER:
9554 do_sync_with_master(command);
9555 break;
9556 case Q_SYNC_SLAVE_WITH_MASTER: {
9557 do_save_master_pos();
9558 if (*command->first_argument)
9559 select_connection(command);
9560 else
9561 select_connection_name("slave");
9562 do_sync_with_master2(command, 0);
9563 break;
9564 }
9565 case Q_COMMENT: {
9566 command->last_argument = command->end;
9567
9568 /* Don't output comments in v1 */
9569 if (opt_result_format_version == 1) break;
9570
9571 /* Don't output comments if query logging is off */
9572 if (disable_query_log) break;
9573
9574 /* Write comment's with two starting #'s to result file */
9575 const char *p = command->query;
9576 if (p && *p == '#' && *(p + 1) == '#') {
9577 dynstr_append_mem(&ds_res, command->query, command->query_len);
9578 dynstr_append(&ds_res, "\n");
9579 }
9580 break;
9581 }
9582 case Q_EMPTY_LINE:
9583 /* Don't output newline in v1 */
9584 if (opt_result_format_version == 1) break;
9585
9586 /* Don't output newline if query logging is off */
9587 if (disable_query_log) break;
9588
9589 dynstr_append(&ds_res, "\n");
9590 break;
9591 case Q_PING:
9592 handle_command_error(command, mysql_ping(&cur_con->mysql));
9593 break;
9594 case Q_RESET_CONNECTION:
9595 do_reset_connection();
9596 break;
9597 case Q_SEND_SHUTDOWN:
9598 if (opt_offload_count_file) {
9599 // Save the value of secondary engine execution status
9600 // before shutting down the server.
9601 if (secondary_engine->offload_count(&cur_con->mysql, "after"))
9602 cleanup_and_exit(1);
9603 }
9604
9605 handle_command_error(
9606 command, mysql_query_wrapper(&cur_con->mysql, "shutdown"));
9607 break;
9608 case Q_SHUTDOWN_SERVER:
9609 do_shutdown_server(command);
9610 break;
9611 case Q_EXEC:
9612 case Q_EXECW:
9613 do_exec(command, false);
9614 command_executed++;
9615 break;
9616 case Q_EXEC_BACKGROUND:
9617 do_exec(command, true);
9618 command_executed++;
9619 break;
9620 case Q_START_TIMER:
9621 /* Overwrite possible earlier start of timer */
9622 timer_start = timer_now();
9623 break;
9624 case Q_END_TIMER:
9625 /* End timer before ending mysqltest */
9626 timer_output();
9627 break;
9628 case Q_CHARACTER_SET:
9629 do_set_charset(command);
9630 break;
9631 case Q_DISABLE_PS_PROTOCOL:
9632 set_property(command, P_PS, false);
9633 /* Close any open statements */
9634 close_statements();
9635 break;
9636 case Q_ENABLE_PS_PROTOCOL:
9637 set_property(command, P_PS, ps_protocol);
9638 break;
9639 case Q_DISABLE_RECONNECT:
9640 set_reconnect(&cur_con->mysql, 0);
9641 break;
9642 case Q_ENABLE_RECONNECT:
9643 set_reconnect(&cur_con->mysql, 1);
9644 enable_async_client = false;
9645 /* Close any open statements - no reconnect, need new prepare */
9646 close_statements();
9647 break;
9648 case Q_ENABLE_ASYNC_CLIENT:
9649 set_property(command, P_ASYNC, true);
9650 break;
9651 case Q_DISABLE_ASYNC_CLIENT:
9652 set_property(command, P_ASYNC, false);
9653 break;
9654 case Q_DISABLE_TESTCASE:
9655 if (testcase_disabled == 0)
9656 do_disable_testcase(command);
9657 else
9658 die("Test case is already disabled.");
9659 break;
9660 case Q_ENABLE_TESTCASE:
9661 // Ensure we don't get testcase_disabled < 0 as this would
9662 // accidentally disable code we don't want to have disabled.
9663 if (testcase_disabled == 1)
9664 testcase_disabled = false;
9665 else
9666 die("Test case is already enabled.");
9667 break;
9668 case Q_DIE:
9669 /* Abort test with error code and error message */
9670 die("%s", command->first_argument);
9671 break;
9672 case Q_EXIT:
9673 /* Stop processing any more commands */
9674 abort_flag = true;
9675 break;
9676 case Q_SKIP: {
9677 DYNAMIC_STRING ds_skip_msg;
9678 init_dynamic_string(&ds_skip_msg, nullptr, command->query_len, 256);
9679
9680 // Evaluate the skip message
9681 do_eval(&ds_skip_msg, command->first_argument, command->end, false);
9682
9683 char skip_msg[FN_REFLEN];
9684 strmake(skip_msg, ds_skip_msg.str, FN_REFLEN - 1);
9685 dynstr_free(&ds_skip_msg);
9686
9687 if (!no_skip) {
9688 // --no-skip option is disabled, skip the test case
9689 abort_not_supported_test("%s", skip_msg);
9690 } else {
9691 const char *path = cur_file->file_name;
9692 const char *fn = get_filename_from_path(path);
9693
9694 // Check if the file is in excluded list
9695 if (excluded_string && strstr(excluded_string, fn)) {
9696 // File is present in excluded list, skip the test case
9697 abort_not_supported_test("%s", skip_msg);
9698 } else {
9699 // File is not present in excluded list, ignore the skip
9700 // and continue running the test case
9701 command->last_argument = command->end;
9702 }
9703 }
9704 } break;
9705 case Q_OUTPUT: {
9706 static DYNAMIC_STRING ds_to_file;
9707 const struct command_arg output_file_args[] = {
9708 {"to_file", ARG_STRING, true, &ds_to_file, "Output filename"}};
9709 check_command_args(command, command->first_argument, output_file_args,
9710 1, ' ');
9711 strmake(output_file, ds_to_file.str, FN_REFLEN);
9712 dynstr_free(&ds_to_file);
9713 break;
9714 }
9715
9716 default:
9717 processed = 0;
9718 break;
9719 }
9720 }
9721
9722 if (!processed) {
9723 current_line_inc = 0;
9724 switch (command->type) {
9725 case Q_WHILE:
9726 do_block(cmd_while, command);
9727 break;
9728 case Q_IF:
9729 do_block(cmd_if, command);
9730 break;
9731 case Q_END_BLOCK:
9732 do_done(command);
9733 break;
9734 default:
9735 current_line_inc = 1;
9736 break;
9737 }
9738 } else
9739 check_eol_junk(command->last_argument);
9740
9741 if (command->type != Q_ERROR && command->type != Q_COMMENT &&
9742 command->type != Q_IF && command->type != Q_END_BLOCK) {
9743 // As soon as any non "error" command or comment has been executed,
9744 // the array with expected errors should be cleared
9745 expected_errors->clear_list();
9746 }
9747
9748 if (command_executed != last_command_executed || command->used_replace) {
9749 /*
9750 As soon as any command has been executed,
9751 the replace structures should be cleared
9752 */
9753 free_all_replace();
9754
9755 /* Also reset "sorted_result" and "lowercase"*/
9756 display_result_sorted = false;
9757 display_result_lower = false;
9758 }
9759 last_command_executed = command_executed;
9760
9761 parser.current_line += current_line_inc;
9762 if (opt_mark_progress) mark_progress(&progress_file, parser.current_line);
9763
9764 // Write result from command to log file immediately.
9765 if (log_file.write(ds_res.str, ds_res.length) || log_file.flush())
9766 cleanup_and_exit(1);
9767
9768 if (!result_file_name) {
9769 std::fwrite(ds_res.str, 1, ds_res.length, stdout);
9770 std::fflush(stdout);
9771 }
9772
9773 dynstr_set(&ds_res, nullptr);
9774 }
9775
9776 start_lineno = 0;
9777 verbose_msg("... Done processing test commands.");
9778
9779 if (testcase_disabled) die("Test ended with test case execution disabled.");
9780
9781 if (disable_warnings || disabled_warnings->count())
9782 die("The test didn't enable all the disabled warnings, enable "
9783 "all of them before end of the test.");
9784
9785 bool empty_result = false;
9786
9787 /*
9788 The whole test has been executed _sucessfully_.
9789 Time to compare result or save it to record file.
9790 The entire output from test is in the log file
9791 */
9792 if (log_file.bytes_written()) {
9793 if (result_file_name) {
9794 /* A result file has been specified */
9795
9796 if (record) {
9797 /* Recording */
9798
9799 /* save a copy of the log to result file */
9800 if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0)
9801 die("Failed to copy '%s' to '%s', errno: %d", log_file.file_name(),
9802 result_file_name, errno);
9803
9804 } else {
9805 /* Check that the output from test is equal to result file */
9806 check_result();
9807 }
9808 }
9809 } else {
9810 /* Empty output is an error *unless* we also have an empty result file */
9811 if (!result_file_name || record ||
9812 compare_files(log_file.file_name(), result_file_name)) {
9813 die("The test didn't produce any output");
9814 } else {
9815 empty_result = true; /* Meaning empty was expected */
9816 }
9817 }
9818
9819 if (!command_executed && result_file_name && !empty_result)
9820 die("No queries executed but non-empty result file found!");
9821
9822 verbose_msg("Test has succeeded!");
9823 timer_output();
9824 /* Yes, if we got this far the test has suceeded! Sakila smiles */
9825 cleanup_and_exit(0);
9826 return 0; /* Keep compiler happy too */
9827 }
9828
9829 /*
9830 A primitive timer that give results in milliseconds if the
9831 --timer-file=<filename> is given. The timer result is written
9832 to that file when the result is available. To not confuse
9833 mysql-test-run with an old obsolete result, we remove the file
9834 before executing any commands. The time we measure is
9835
9836 - If no explicit 'start_timer' or 'end_timer' is given in the
9837 test case, the timer measure how long we execute in mysqltest.
9838
9839 - If only 'start_timer' is given we measure how long we execute
9840 from that point until we terminate mysqltest.
9841
9842 - If only 'end_timer' is given we measure how long we execute
9843 from that we enter mysqltest to the 'end_timer' is command is
9844 executed.
9845
9846 - If both 'start_timer' and 'end_timer' are given we measure
9847 the time between executing the two commands.
9848 */
9849
timer_output(void)9850 void timer_output(void) {
9851 if (timer_file) {
9852 char buf[32], *end;
9853 ulonglong timer = timer_now() - timer_start;
9854 end = longlong10_to_str(timer, buf, 10);
9855 str_to_file(timer_file, buf, (int)(end - buf));
9856 /* Timer has been written to the file, don't use it anymore */
9857 timer_file = nullptr;
9858 }
9859 }
9860
timer_now(void)9861 ulonglong timer_now(void) { return my_micro_time() / 1000; }
9862
9863 /*
9864 Get arguments for replace_columns. The syntax is:
9865 replace-column column_number to_string [column_number to_string ...]
9866 Where each argument may be quoted with ' or "
9867 A argument may also be a variable, in which case the value of the
9868 variable is replaced.
9869 */
9870
do_get_replace_column(struct st_command * command)9871 void do_get_replace_column(struct st_command *command) {
9872 const char *from = command->first_argument;
9873 char *buff, *start;
9874 DBUG_TRACE;
9875
9876 free_replace_column();
9877 if (!*from) die("Missing argument in %s", command->query);
9878
9879 /* Allocate a buffer for results */
9880 start = buff = (char *)my_malloc(PSI_NOT_INSTRUMENTED, std::strlen(from) + 1,
9881 MYF(MY_WME | MY_FAE));
9882 while (*from) {
9883 char *to;
9884 uint column_number;
9885 to = get_string(&buff, &from, command);
9886 if (!(column_number = atoi(to)) || column_number > MAX_COLUMNS)
9887 die("Wrong column number to replace_column in '%s'", command->query);
9888 if (!*from)
9889 die("Wrong number of arguments to replace_column in '%s'",
9890 command->query);
9891 to = get_string(&buff, &from, command);
9892 my_free(replace_column[column_number - 1]);
9893 replace_column[column_number - 1] =
9894 my_strdup(PSI_NOT_INSTRUMENTED, to, MYF(MY_WME | MY_FAE));
9895 max_replace_column = std::max(max_replace_column, column_number);
9896 }
9897 my_free(start);
9898 command->last_argument = command->end;
9899 }
9900
free_replace_column()9901 void free_replace_column() {
9902 uint i;
9903 for (i = 0; i < max_replace_column; i++) {
9904 if (replace_column[i]) {
9905 my_free(replace_column[i]);
9906 replace_column[i] = nullptr;
9907 }
9908 }
9909 max_replace_column = 0;
9910 }
9911
9912 /*
9913 Functions to round numeric results.
9914
9915 SYNOPSIS
9916 do_get_replace_numeric_round()
9917 command - command handle
9918
9919 DESCRIPTION
9920 replace_numeric_round <precision>
9921
9922 where precision is the number of digits after the decimal point
9923 that the result will be rounded off to. The precision can only
9924 be a number between 0 and 16.
9925 eg. replace_numeric_round 10;
9926 Numbers which are > 1e10 or < -1e10 are represented using the
9927 exponential notation after they are rounded off.
9928 Trailing zeroes after the decimal point are removed from the
9929 numbers.
9930 If the precision is 0, then the value is rounded off to the
9931 nearest whole number.
9932 */
do_get_replace_numeric_round(struct st_command * command)9933 void do_get_replace_numeric_round(struct st_command *command) {
9934 DYNAMIC_STRING ds_round;
9935 const struct command_arg numeric_arg = {
9936 "precision", ARG_STRING, true, &ds_round, "Number of decimal precision"};
9937 DBUG_TRACE;
9938
9939 check_command_args(command, command->first_argument, &numeric_arg,
9940 sizeof(numeric_arg) / sizeof(struct command_arg), ' ');
9941
9942 // Parse the argument string to get the precision
9943 long int v = 0;
9944 if (str2int(ds_round.str, 10, 0, REPLACE_ROUND_MAX, &v) == NullS)
9945 die("A number between 0 and %d is required for the precision "
9946 "in replace_numeric_round",
9947 REPLACE_ROUND_MAX);
9948
9949 glob_replace_numeric_round = (int)v;
9950 dynstr_free(&ds_round);
9951 }
9952
free_replace_numeric_round()9953 void free_replace_numeric_round() { glob_replace_numeric_round = -1; }
9954
9955 /*
9956 Round the digits after the decimal point to the specified precision
9957 by iterating through the result set element, identifying the part to
9958 be rounded off, and rounding that part off.
9959 */
replace_numeric_round_append(int round,DYNAMIC_STRING * result,const char * from,size_t len)9960 void replace_numeric_round_append(int round, DYNAMIC_STRING *result,
9961 const char *from, size_t len) {
9962 while (len > 0) {
9963 // Move pointer to the start of the numeric values
9964 size_t size = strcspn(from, "0123456789");
9965 if (size > 0) {
9966 dynstr_append_mem(result, from, size);
9967 from += size;
9968 len -= size;
9969 }
9970
9971 /*
9972 Move the pointer to the end of the numeric values and the
9973 the start of the non-numeric values such as "." and "e"
9974 */
9975 size = strspn(from, "0123456789");
9976 int r = round;
9977
9978 /*
9979 If result from one of the rows of the result set is null,
9980 break the loop
9981 */
9982 if (*(from + size) == 0) {
9983 dynstr_append_mem(result, from, size);
9984 break;
9985 }
9986
9987 switch (*(from + size)) {
9988 // double/float
9989 case '.':
9990 size_t size1;
9991 size1 = strspn(from + size + 1, "0123456789");
9992
9993 /*
9994 Restrict rounding to less than the
9995 the existing precision to avoid 1.2 being replaced
9996 to 1.2000000
9997 */
9998 if (size1 < (size_t)r) r = size1;
9999 // fallthrough: all cases till next break are executed
10000 case 'e':
10001 case 'E':
10002 if (isdigit(*(from + size + 1))) {
10003 char *end;
10004 double val = strtod(from, &end);
10005 if (end != nullptr) {
10006 const char *format = (val < 1e10 && val > -1e10) ? "%.*f" : "%.*e";
10007 char buf[40];
10008
10009 size = snprintf(buf, sizeof(buf), format, r, val);
10010 if (val < 1e10 && val > -1e10 && r > 0) {
10011 /*
10012 2.0000000 need to be represented as 2 for consistency
10013 2.0010000 also becomes 2.001
10014 */
10015 while (buf[size - 1] == '0') size--;
10016
10017 // don't leave 100. trailing
10018 if (buf[size - 1] == '.') size--;
10019 }
10020 dynstr_append_mem(result, buf, size);
10021 len -= (end - from);
10022 from = end;
10023 break;
10024 }
10025 }
10026
10027 /*
10028 This is because strtod didn't convert or there wasn't digits after
10029 [.eE] so output without changing
10030 */
10031 dynstr_append_mem(result, from, size);
10032 from += size;
10033 len -= size;
10034 break;
10035 // int
10036 default:
10037 dynstr_append_mem(result, from, size);
10038 from += size;
10039 len -= size;
10040 break;
10041 }
10042 }
10043 }
10044
10045 /****************************************************************************/
10046 /*
10047 Replace functions
10048 */
10049
10050 /* Definitions for replace result */
10051
10052 struct POINTER_ARRAY { /* when using array-strings */
10053 TYPELIB typelib; /* Pointer to strings */
10054 uchar *str{nullptr}; /* Strings is here */
10055 uint8 *flag{nullptr}; /* Flag about each var. */
10056 uint array_allocs{0}, max_count{0}, length{0}, max_length{0};
10057 };
10058
10059 REPLACE *init_replace(const char **from, const char **to, uint count,
10060 const char *word_end_chars);
10061 int insert_pointer_name(POINTER_ARRAY *pa, char *name);
10062 void free_pointer_array(POINTER_ARRAY *pa);
10063
10064 /*
10065 Get arguments for replace. The syntax is:
10066 replace from to [from to ...]
10067 Where each argument may be quoted with ' or "
10068 A argument may also be a variable, in which case the value of the
10069 variable is replaced.
10070 */
10071
do_get_replace(struct st_command * command)10072 void do_get_replace(struct st_command *command) {
10073 uint i;
10074 const char *from = command->first_argument;
10075 char *buff, *start;
10076 char word_end_chars[256], *pos;
10077 POINTER_ARRAY to_array, from_array;
10078 DBUG_TRACE;
10079
10080 free_replace();
10081
10082 if (!*from) die("Missing argument in %s", command->query);
10083 start = buff = (char *)my_malloc(PSI_NOT_INSTRUMENTED, std::strlen(from) + 1,
10084 MYF(MY_WME | MY_FAE));
10085 while (*from) {
10086 char *to = buff;
10087 to = get_string(&buff, &from, command);
10088 if (!*from)
10089 die("Wrong number of arguments to replace_result in '%s'",
10090 command->query);
10091 #ifdef _WIN32
10092 fix_win_paths(to, from - to);
10093 #endif
10094 insert_pointer_name(&from_array, to);
10095 to = get_string(&buff, &from, command);
10096 insert_pointer_name(&to_array, to);
10097 }
10098 for (i = 1, pos = word_end_chars; i < 256; i++)
10099 if (my_isspace(charset_info, i)) *pos++ = i;
10100 *pos = 0; /* End pointer */
10101 if (!(glob_replace = init_replace(
10102 from_array.typelib.type_names, to_array.typelib.type_names,
10103 (uint)from_array.typelib.count, word_end_chars)))
10104 die("Can't initialize replace from '%s'", command->query);
10105 free_pointer_array(&from_array);
10106 free_pointer_array(&to_array);
10107 my_free(start);
10108 command->last_argument = command->end;
10109 }
10110
free_replace()10111 void free_replace() {
10112 DBUG_TRACE;
10113 my_free(glob_replace);
10114 glob_replace = nullptr;
10115 }
10116
10117 struct REPLACE {
10118 int found;
10119 REPLACE *next[256];
10120 };
10121
10122 struct REPLACE_STRING {
10123 int found;
10124 const char *replace_string;
10125 uint to_offset;
10126 int from_offset;
10127 };
10128
replace_strings_append(REPLACE * rep,DYNAMIC_STRING * ds,const char * str,size_t len MY_ATTRIBUTE ((unused)))10129 void replace_strings_append(REPLACE *rep, DYNAMIC_STRING *ds, const char *str,
10130 size_t len MY_ATTRIBUTE((unused))) {
10131 REPLACE *rep_pos;
10132 REPLACE_STRING *rep_str;
10133 const char *start, *from;
10134 DBUG_TRACE;
10135
10136 start = from = str;
10137 rep_pos = rep + 1;
10138 for (;;) {
10139 /* Loop through states */
10140 DBUG_PRINT("info", ("Looping through states"));
10141 while (!rep_pos->found) rep_pos = rep_pos->next[(uchar)*from++];
10142
10143 /* Does this state contain a string to be replaced */
10144 if (!(rep_str = ((REPLACE_STRING *)rep_pos))->replace_string) {
10145 /* No match found */
10146 dynstr_append_mem(ds, start, from - start - 1);
10147 DBUG_PRINT("exit",
10148 ("Found no more string to replace, appended: %s", start));
10149 return;
10150 }
10151
10152 /* Found a string that needs to be replaced */
10153 DBUG_PRINT("info", ("found: %d, to_offset: %d, from_offset: %d, string: %s",
10154 rep_str->found, rep_str->to_offset,
10155 rep_str->from_offset, rep_str->replace_string));
10156
10157 /* Append part of original string before replace string */
10158 dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start);
10159
10160 /* Append replace string */
10161 dynstr_append_mem(ds, rep_str->replace_string,
10162 std::strlen(rep_str->replace_string));
10163
10164 if (!*(from -= rep_str->from_offset) && rep_pos->found != 2) {
10165 /* End of from string */
10166 DBUG_PRINT("exit", ("Found end of from string"));
10167 return;
10168 }
10169 DBUG_ASSERT(from <= str + len);
10170 start = from;
10171 rep_pos = rep;
10172 }
10173 }
10174
10175 /*
10176 Regex replace functions
10177 */
10178
10179 /*
10180 Finds the next (non-escaped) '/' in the expression.
10181 (If the character '/' is needed, it can be escaped using '\'.)
10182 */
10183
10184 #define PARSE_REGEX_ARG \
10185 while (p < expr_end) { \
10186 char c = *p; \
10187 if (c == '/') { \
10188 if (last_c == '\\') { \
10189 buf_p[-1] = '/'; \
10190 } else { \
10191 *buf_p++ = 0; \
10192 break; \
10193 } \
10194 } else \
10195 *buf_p++ = c; \
10196 \
10197 last_c = c; \
10198 p++; \
10199 }
10200
10201 /**
10202 Initializes the regular substitution expression to be used in the
10203 result output of test.
10204
10205 @param expr Pointer to string having regular expression to be used
10206 for substitution.
10207 @retval st_replace_regex structure with pairs of substitutions.
10208 */
init_replace_regex(const char * expr)10209 static struct st_replace_regex *init_replace_regex(const char *expr) {
10210 struct st_replace_regex *res;
10211 char *buf;
10212 const char *expr_end;
10213 const char *p;
10214 char *buf_p;
10215 size_t expr_len = std::strlen(expr);
10216 char last_c = 0;
10217 struct st_regex reg;
10218
10219 /* my_malloc() will die on fail with MY_FAE */
10220 void *rawmem = my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*res) + expr_len,
10221 MYF(MY_FAE + MY_WME));
10222 res = new (rawmem) st_replace_regex;
10223
10224 buf = (char *)res + sizeof(*res);
10225 expr_end = expr + expr_len;
10226 p = expr;
10227 buf_p = buf;
10228
10229 /* for each regexp substitution statement */
10230 while (p < expr_end) {
10231 memset(®, 0, sizeof(reg));
10232 /* find the start of the statement */
10233 while (p < expr_end) {
10234 if (*p == '/') break;
10235 p++;
10236 }
10237
10238 if (p == expr_end || ++p == expr_end) {
10239 if (res->regex_arr.size())
10240 break;
10241 else
10242 goto err;
10243 }
10244 /* we found the start */
10245 reg.pattern = buf_p;
10246
10247 /* Find first argument -- pattern string to be removed */
10248 PARSE_REGEX_ARG
10249
10250 if (p == expr_end || ++p == expr_end) goto err;
10251
10252 /* buf_p now points to the replacement pattern terminated with \0 */
10253 reg.replace = buf_p;
10254
10255 /* Find second argument -- replace string to replace pattern */
10256 PARSE_REGEX_ARG
10257
10258 if (p == expr_end) goto err;
10259
10260 /* skip the ending '/' in the statement */
10261 p++;
10262
10263 /* Check if we should do matching case insensitive */
10264 if (p < expr_end && *p == 'i') reg.icase = 1;
10265
10266 /* done parsing the statement, now place it in regex_arr */
10267 if (res->regex_arr.push_back(reg)) die("Out of memory");
10268 }
10269 res->odd_buf_len = res->even_buf_len = 8192;
10270 res->even_buf = (char *)my_malloc(PSI_NOT_INSTRUMENTED, res->even_buf_len,
10271 MYF(MY_WME + MY_FAE));
10272 res->odd_buf = (char *)my_malloc(PSI_NOT_INSTRUMENTED, res->odd_buf_len,
10273 MYF(MY_WME + MY_FAE));
10274 res->buf = res->even_buf;
10275
10276 return res;
10277
10278 err:
10279 my_free(res);
10280 die("Error parsing replace_regex \"%s\"", expr);
10281 return nullptr;
10282 }
10283
10284 /*
10285 Parse the regular expression to be used in all result files
10286 from now on.
10287
10288 The syntax is --replace_regex /from/to/i /from/to/i ...
10289 i means case-insensitive match. If omitted, the match is
10290 case-sensitive
10291
10292 */
do_get_replace_regex(struct st_command * command)10293 void do_get_replace_regex(struct st_command *command) {
10294 const char *expr = command->first_argument;
10295 free_replace_regex();
10296 /* Allow variable for the *entire* list of replacements */
10297 if (*expr == '$') {
10298 VAR *val = var_get(expr, nullptr, false, true);
10299 expr = val ? val->str_val : nullptr;
10300 }
10301 if (expr && *expr && !(glob_replace_regex = init_replace_regex(expr)))
10302 die("Could not init replace_regex");
10303 command->last_argument = command->end;
10304 }
10305
free_replace_regex()10306 void free_replace_regex() {
10307 if (glob_replace_regex) {
10308 my_free(glob_replace_regex->even_buf);
10309 my_free(glob_replace_regex->odd_buf);
10310 glob_replace_regex->~st_replace_regex();
10311 my_free(glob_replace_regex);
10312 glob_replace_regex = nullptr;
10313 }
10314 }
10315
10316 #ifndef WORD_BIT
10317 #define WORD_BIT (8 * sizeof(uint))
10318 #endif
10319
10320 #define SET_MALLOC_HUNC 64
10321 #define LAST_CHAR_CODE 259
10322
10323 struct REP_SET {
10324 uint *bits; /* Pointer to used sets */
10325 short next[LAST_CHAR_CODE]; /* Pointer to next sets */
10326 uint found_len; /* Best match to date */
10327 int found_offset;
10328 uint table_offset;
10329 uint size_of_bits; /* For convinience */
10330 };
10331
10332 struct REP_SETS {
10333 uint count; /* Number of sets */
10334 uint extra; /* Extra sets in buffer */
10335 uint invisible; /* Sets not chown */
10336 uint size_of_bits;
10337 REP_SET *set, *set_buffer;
10338 uint *bit_buffer;
10339 };
10340
10341 struct FOUND_SET {
10342 uint table_offset;
10343 int found_offset;
10344 };
10345
10346 struct FOLLOWS {
10347 int chr;
10348 uint table_offset;
10349 uint len;
10350 };
10351
10352 int init_sets(REP_SETS *sets, uint states);
10353 REP_SET *make_new_set(REP_SETS *sets);
10354 void make_sets_invisible(REP_SETS *sets);
10355 void free_last_set(REP_SETS *sets);
10356 void free_sets(REP_SETS *sets);
10357 void internal_set_bit(REP_SET *set, uint bit);
10358 void internal_clear_bit(REP_SET *set, uint bit);
10359 void or_bits(REP_SET *to, REP_SET *from);
10360 void copy_bits(REP_SET *to, REP_SET *from);
10361 int cmp_bits(REP_SET *set1, REP_SET *set2);
10362 int get_next_bit(REP_SET *set, uint lastpos);
10363 int find_set(REP_SETS *sets, REP_SET *find);
10364 int find_found(FOUND_SET *found_set, uint table_offset, int found_offset);
10365 uint start_at_word(const char *pos);
10366 uint end_of_word(const char *pos);
10367
10368 static uint found_sets = 0;
10369
replace_len(const char * str)10370 static uint replace_len(const char *str) {
10371 uint len = 0;
10372 while (*str) {
10373 str++;
10374 len++;
10375 }
10376 return len;
10377 }
10378
10379 /* Init a replace structure for further calls */
10380
init_replace(const char ** from,const char ** to,uint count,const char * word_end_chars)10381 REPLACE *init_replace(const char **from, const char **to, uint count,
10382 const char *word_end_chars) {
10383 static const int SPACE_CHAR = 256;
10384 static const int END_OF_LINE = 258;
10385
10386 uint i, j, states, set_nr, len, result_len, max_length, found_end, bits_set,
10387 bit_nr;
10388 int used_sets, chr, default_state;
10389 char used_chars[LAST_CHAR_CODE], is_word_end[256];
10390 const char *pos, **to_array;
10391 char *to_pos;
10392 REP_SETS sets;
10393 REP_SET *set, *start_states, *word_states, *new_set;
10394 FOLLOWS *follow, *follow_ptr;
10395 REPLACE *replace;
10396 FOUND_SET *found_set;
10397 REPLACE_STRING *rep_str;
10398 DBUG_TRACE;
10399
10400 /* Count number of states */
10401 for (i = result_len = max_length = 0, states = 2; i < count; i++) {
10402 len = replace_len(from[i]);
10403 if (!len) {
10404 errno = EINVAL;
10405 return nullptr;
10406 }
10407 states += len + 1;
10408 result_len += (uint)std::strlen(to[i]) + 1;
10409 if (len > max_length) max_length = len;
10410 }
10411 memset(is_word_end, 0, sizeof(is_word_end));
10412 for (i = 0; word_end_chars[i]; i++) is_word_end[(uchar)word_end_chars[i]] = 1;
10413
10414 if (init_sets(&sets, states)) return nullptr;
10415 found_sets = 0;
10416 if (!(found_set = (FOUND_SET *)my_malloc(
10417 PSI_NOT_INSTRUMENTED, sizeof(FOUND_SET) * max_length * count,
10418 MYF(MY_WME)))) {
10419 free_sets(&sets);
10420 return nullptr;
10421 }
10422 (void)make_new_set(&sets); /* Set starting set */
10423 make_sets_invisible(&sets); /* Hide previus sets */
10424 used_sets = -1;
10425 word_states = make_new_set(&sets); /* Start of new word */
10426 start_states = make_new_set(&sets); /* This is first state */
10427 if (!(follow = (FOLLOWS *)my_malloc(PSI_NOT_INSTRUMENTED,
10428 (states + 2) * sizeof(FOLLOWS),
10429 MYF(MY_WME)))) {
10430 free_sets(&sets);
10431 my_free(found_set);
10432 return nullptr;
10433 }
10434
10435 /* Init follow_ptr[] */
10436 for (i = 0, states = 1, follow_ptr = follow + 1; i < count; i++) {
10437 if (from[i][0] == '\\' && from[i][1] == '^') {
10438 internal_set_bit(start_states, states + 1);
10439 if (!from[i][2]) {
10440 start_states->table_offset = i;
10441 start_states->found_offset = 1;
10442 }
10443 } else if (from[i][0] == '\\' && from[i][1] == '$') {
10444 internal_set_bit(start_states, states);
10445 internal_set_bit(word_states, states);
10446 if (!from[i][2] && start_states->table_offset == (uint)~0) {
10447 start_states->table_offset = i;
10448 start_states->found_offset = 0;
10449 }
10450 } else {
10451 internal_set_bit(word_states, states);
10452 if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2]))
10453 internal_set_bit(start_states, states + 1);
10454 else
10455 internal_set_bit(start_states, states);
10456 }
10457 for (pos = from[i], len = 0; *pos; pos++) {
10458 follow_ptr->chr = (uchar)*pos;
10459 follow_ptr->table_offset = i;
10460 follow_ptr->len = ++len;
10461 follow_ptr++;
10462 }
10463 follow_ptr->chr = 0;
10464 follow_ptr->table_offset = i;
10465 follow_ptr->len = len;
10466 follow_ptr++;
10467 states += (uint)len + 1;
10468 }
10469
10470 for (set_nr = 0, pos = nullptr; set_nr < sets.count; set_nr++) {
10471 set = sets.set + set_nr;
10472 default_state = 0; /* Start from beginning */
10473
10474 /* If end of found-string not found or start-set with current set */
10475
10476 for (i = (uint)~0; (i = get_next_bit(set, i));) {
10477 if (!follow[i].chr) {
10478 if (!default_state)
10479 default_state =
10480 find_found(found_set, set->table_offset, set->found_offset + 1);
10481 }
10482 }
10483 copy_bits(sets.set + used_sets, set); /* Save set for changes */
10484 if (!default_state)
10485 or_bits(sets.set + used_sets, sets.set); /* Can restart from start */
10486
10487 /* Find all chars that follows current sets */
10488 memset(used_chars, 0, sizeof(used_chars));
10489 for (i = (uint)~0; (i = get_next_bit(sets.set + used_sets, i));) {
10490 used_chars[follow[i].chr] = 1;
10491 if ((follow[i].chr == SPACE_CHAR && !follow[i + 1].chr &&
10492 follow[i].len > 1) ||
10493 follow[i].chr == END_OF_LINE)
10494 used_chars[0] = 1;
10495 }
10496
10497 /* Mark word_chars used if \b is in state */
10498 if (used_chars[SPACE_CHAR])
10499 for (pos = word_end_chars; *pos; pos++) used_chars[(int)(uchar)*pos] = 1;
10500
10501 /* Handle other used characters */
10502 for (chr = 0; chr < 256; chr++) {
10503 if (!used_chars[chr])
10504 set->next[chr] = chr ? default_state : -1;
10505 else {
10506 new_set = make_new_set(&sets);
10507 assert(new_set);
10508 set = sets.set + set_nr; /* if realloc */
10509 new_set->table_offset = set->table_offset;
10510 new_set->found_len = set->found_len;
10511 new_set->found_offset = set->found_offset + 1;
10512 found_end = 0;
10513
10514 for (i = (uint)~0; (i = get_next_bit(sets.set + used_sets, i));) {
10515 if (!follow[i].chr || follow[i].chr == chr ||
10516 (follow[i].chr == SPACE_CHAR &&
10517 (is_word_end[chr] ||
10518 (!chr && follow[i].len > 1 && !follow[i + 1].chr))) ||
10519 (follow[i].chr == END_OF_LINE && !chr)) {
10520 if ((!chr || (follow[i].chr && !follow[i + 1].chr)) &&
10521 follow[i].len > found_end)
10522 found_end = follow[i].len;
10523 if (chr && follow[i].chr)
10524 internal_set_bit(new_set, i + 1); /* To next set */
10525 else
10526 internal_set_bit(new_set, i);
10527 }
10528 }
10529 if (found_end) {
10530 new_set->found_len = 0; /* Set for testing if first */
10531 bits_set = 0;
10532 for (i = (uint)~0; (i = get_next_bit(new_set, i));) {
10533 if ((follow[i].chr == SPACE_CHAR || follow[i].chr == END_OF_LINE) &&
10534 !chr)
10535 bit_nr = i + 1;
10536 else
10537 bit_nr = i;
10538 if (follow[bit_nr - 1].len < found_end ||
10539 (new_set->found_len && (chr == 0 || !follow[bit_nr].chr)))
10540 internal_clear_bit(new_set, i);
10541 else {
10542 if (chr == 0 || !follow[bit_nr].chr) { /* best match */
10543 new_set->table_offset = follow[bit_nr].table_offset;
10544 if (chr || (follow[i].chr == SPACE_CHAR ||
10545 follow[i].chr == END_OF_LINE))
10546 new_set->found_offset = found_end; /* New match */
10547 new_set->found_len = found_end;
10548 }
10549 bits_set++;
10550 }
10551 }
10552 if (bits_set == 1) {
10553 set->next[chr] = find_found(found_set, new_set->table_offset,
10554 new_set->found_offset);
10555 free_last_set(&sets);
10556 } else
10557 set->next[chr] = find_set(&sets, new_set);
10558 } else
10559 set->next[chr] = find_set(&sets, new_set);
10560 }
10561 }
10562 }
10563
10564 /* Alloc replace structure for the replace-state-machine */
10565
10566 if ((replace =
10567 (REPLACE *)my_malloc(PSI_NOT_INSTRUMENTED,
10568 sizeof(REPLACE) * (sets.count) +
10569 sizeof(REPLACE_STRING) * (found_sets + 1) +
10570 sizeof(char *) * count + result_len,
10571 MYF(MY_WME | MY_ZEROFILL)))) {
10572 rep_str = (REPLACE_STRING *)(replace + sets.count);
10573 to_array = pointer_cast<const char **>(rep_str + found_sets + 1);
10574 to_pos = (char *)(to_array + count);
10575 for (i = 0; i < count; i++) {
10576 to_array[i] = to_pos;
10577 to_pos = my_stpcpy(to_pos, to[i]) + 1;
10578 }
10579 rep_str[0].found = 1;
10580 rep_str[0].replace_string = nullptr;
10581 for (i = 1; i <= found_sets; i++) {
10582 pos = from[found_set[i - 1].table_offset];
10583 rep_str[i].found = !memcmp(pos, "\\^", 3) ? 2 : 1;
10584 rep_str[i].replace_string = to_array[found_set[i - 1].table_offset];
10585 rep_str[i].to_offset = found_set[i - 1].found_offset - start_at_word(pos);
10586 rep_str[i].from_offset =
10587 found_set[i - 1].found_offset - replace_len(pos) + end_of_word(pos);
10588 }
10589 for (i = 0; i < sets.count; i++) {
10590 for (j = 0; j < 256; j++)
10591 if (sets.set[i].next[j] >= 0)
10592 replace[i].next[j] = replace + sets.set[i].next[j];
10593 else
10594 replace[i].next[j] =
10595 (REPLACE *)(rep_str + (-sets.set[i].next[j] - 1));
10596 }
10597 }
10598 my_free(follow);
10599 free_sets(&sets);
10600 my_free(found_set);
10601 DBUG_PRINT("exit", ("Replace table has %d states", sets.count));
10602 return replace;
10603 }
10604
init_sets(REP_SETS * sets,uint states)10605 int init_sets(REP_SETS *sets, uint states) {
10606 memset(sets, 0, sizeof(*sets));
10607 sets->size_of_bits = ((states + 7) / 8);
10608 if (!(sets->set_buffer = (REP_SET *)my_malloc(
10609 PSI_NOT_INSTRUMENTED, sizeof(REP_SET) * SET_MALLOC_HUNC,
10610 MYF(MY_WME))))
10611 return 1;
10612 if (!(sets->bit_buffer = (uint *)my_malloc(
10613 PSI_NOT_INSTRUMENTED,
10614 sizeof(uint) * sets->size_of_bits * SET_MALLOC_HUNC,
10615 MYF(MY_WME)))) {
10616 my_free(sets->set);
10617 return 1;
10618 }
10619 return 0;
10620 }
10621
10622 /* Make help sets invisible for nicer codeing */
10623
make_sets_invisible(REP_SETS * sets)10624 void make_sets_invisible(REP_SETS *sets) {
10625 sets->invisible = sets->count;
10626 sets->set += sets->count;
10627 sets->count = 0;
10628 }
10629
make_new_set(REP_SETS * sets)10630 REP_SET *make_new_set(REP_SETS *sets) {
10631 uint i, count, *bit_buffer;
10632 REP_SET *set;
10633 if (sets->extra) {
10634 sets->extra--;
10635 set = sets->set + sets->count++;
10636 memset(set->bits, 0, sizeof(uint) * sets->size_of_bits);
10637 memset(&set->next[0], 0, sizeof(set->next[0]) * LAST_CHAR_CODE);
10638 set->found_offset = 0;
10639 set->found_len = 0;
10640 set->table_offset = (uint)~0;
10641 set->size_of_bits = sets->size_of_bits;
10642 return set;
10643 }
10644 count = sets->count + sets->invisible + SET_MALLOC_HUNC;
10645 if (!(set = (REP_SET *)my_realloc(PSI_NOT_INSTRUMENTED,
10646 (uchar *)sets->set_buffer,
10647 sizeof(REP_SET) * count, MYF(MY_WME))))
10648 return nullptr;
10649 sets->set_buffer = set;
10650 sets->set = set + sets->invisible;
10651 if (!(bit_buffer = (uint *)my_realloc(
10652 PSI_NOT_INSTRUMENTED, (uchar *)sets->bit_buffer,
10653 (sizeof(uint) * sets->size_of_bits) * count, MYF(MY_WME))))
10654 return nullptr;
10655 sets->bit_buffer = bit_buffer;
10656 for (i = 0; i < count; i++) {
10657 sets->set_buffer[i].bits = bit_buffer;
10658 bit_buffer += sets->size_of_bits;
10659 }
10660 sets->extra = SET_MALLOC_HUNC;
10661 return make_new_set(sets);
10662 }
10663
free_last_set(REP_SETS * sets)10664 void free_last_set(REP_SETS *sets) {
10665 sets->count--;
10666 sets->extra++;
10667 return;
10668 }
10669
free_sets(REP_SETS * sets)10670 void free_sets(REP_SETS *sets) {
10671 my_free(sets->set_buffer);
10672 my_free(sets->bit_buffer);
10673 return;
10674 }
10675
internal_set_bit(REP_SET * set,uint bit)10676 void internal_set_bit(REP_SET *set, uint bit) {
10677 set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT);
10678 return;
10679 }
10680
internal_clear_bit(REP_SET * set,uint bit)10681 void internal_clear_bit(REP_SET *set, uint bit) {
10682 set->bits[bit / WORD_BIT] &= ~(1 << (bit % WORD_BIT));
10683 return;
10684 }
10685
or_bits(REP_SET * to,REP_SET * from)10686 void or_bits(REP_SET *to, REP_SET *from) {
10687 uint i;
10688 for (i = 0; i < to->size_of_bits; i++) to->bits[i] |= from->bits[i];
10689 return;
10690 }
10691
copy_bits(REP_SET * to,REP_SET * from)10692 void copy_bits(REP_SET *to, REP_SET *from) {
10693 memcpy((uchar *)to->bits, (uchar *)from->bits,
10694 (size_t)(sizeof(uint) * to->size_of_bits));
10695 }
10696
cmp_bits(REP_SET * set1,REP_SET * set2)10697 int cmp_bits(REP_SET *set1, REP_SET *set2) {
10698 return memcmp(set1->bits, set2->bits, sizeof(uint) * set1->size_of_bits);
10699 }
10700
10701 /* Get next set bit from set. */
10702
get_next_bit(REP_SET * set,uint lastpos)10703 int get_next_bit(REP_SET *set, uint lastpos) {
10704 uint pos, *start, *end, bits;
10705
10706 start = set->bits + ((lastpos + 1) / WORD_BIT);
10707 end = set->bits + set->size_of_bits;
10708 bits = start[0] & ~((1 << ((lastpos + 1) % WORD_BIT)) - 1U);
10709
10710 while (!bits && ++start < end) bits = start[0];
10711 if (!bits) return 0;
10712 pos = (uint)(start - set->bits) * WORD_BIT;
10713 while (!(bits & 1)) {
10714 bits >>= 1;
10715 pos++;
10716 }
10717 return pos;
10718 }
10719
10720 /* find if there is a same set in sets. If there is, use it and
10721 free given set, else put in given set in sets and return its
10722 position */
10723
find_set(REP_SETS * sets,REP_SET * find)10724 int find_set(REP_SETS *sets, REP_SET *find) {
10725 uint i;
10726 for (i = 0; i < sets->count - 1; i++) {
10727 if (!cmp_bits(sets->set + i, find)) {
10728 free_last_set(sets);
10729 return i;
10730 }
10731 }
10732 return i; /* return new position */
10733 }
10734
10735 /* find if there is a found_set with same table_offset & found_offset
10736 If there is return offset to it, else add new offset and return pos.
10737 Pos returned is -offset-2 in found_set_structure because it is
10738 saved in set->next and set->next[] >= 0 points to next set and
10739 set->next[] == -1 is reserved for end without replaces.
10740 */
10741
find_found(FOUND_SET * found_set,uint table_offset,int found_offset)10742 int find_found(FOUND_SET *found_set, uint table_offset, int found_offset) {
10743 int i;
10744 for (i = 0; (uint)i < found_sets; i++)
10745 if (found_set[i].table_offset == table_offset &&
10746 found_set[i].found_offset == found_offset)
10747 return -i - 2;
10748 found_set[i].table_offset = table_offset;
10749 found_set[i].found_offset = found_offset;
10750 found_sets++;
10751 return -i - 2; /* return new position */
10752 }
10753
10754 /* Return 1 if regexp starts with \b or ends with \b*/
10755
start_at_word(const char * pos)10756 uint start_at_word(const char *pos) {
10757 return (((!memcmp(pos, "\\b", 2) && pos[2]) || !memcmp(pos, "\\^", 2)) ? 1
10758 : 0);
10759 }
10760
end_of_word(const char * pos)10761 uint end_of_word(const char *pos) {
10762 const char *end = strend(pos);
10763 return ((end > pos + 2 && !memcmp(end - 2, "\\b", 2)) ||
10764 (end >= pos + 2 && !memcmp(end - 2, "\\$", 2)))
10765 ? 1
10766 : 0;
10767 }
10768
10769 /****************************************************************************
10770 * Handle replacement of strings
10771 ****************************************************************************/
10772
10773 #define PC_MALLOC 256 /* Bytes for pointers */
10774 #define PS_MALLOC 512 /* Bytes for data */
10775
insert_pointer_name(POINTER_ARRAY * pa,char * name)10776 int insert_pointer_name(POINTER_ARRAY *pa, char *name) {
10777 uint i, length, old_count;
10778 uchar *new_pos;
10779 const char **new_array;
10780 DBUG_TRACE;
10781
10782 if (!pa->typelib.count) {
10783 if (!(pa->typelib.type_names =
10784 (const char **)my_malloc(PSI_NOT_INSTRUMENTED,
10785 ((PC_MALLOC - MALLOC_OVERHEAD) /
10786 (sizeof(char *) + sizeof(*pa->flag)) *
10787 (sizeof(char *) + sizeof(*pa->flag))),
10788 MYF(MY_WME))))
10789 return -1;
10790 if (!(pa->str = (uchar *)my_malloc(PSI_NOT_INSTRUMENTED,
10791 (uint)(PS_MALLOC - MALLOC_OVERHEAD),
10792 MYF(MY_WME)))) {
10793 my_free(pa->typelib.type_names);
10794 return -1;
10795 }
10796 pa->max_count =
10797 (PC_MALLOC - MALLOC_OVERHEAD) / (sizeof(uchar *) + sizeof(*pa->flag));
10798 pa->flag = (uint8 *)(pa->typelib.type_names + pa->max_count);
10799 pa->length = 0;
10800 pa->max_length = PS_MALLOC - MALLOC_OVERHEAD;
10801 pa->array_allocs = 1;
10802 }
10803 length = (uint)std::strlen(name) + 1;
10804 if (pa->length + length >= pa->max_length) {
10805 if (!(new_pos = (uchar *)my_realloc(PSI_NOT_INSTRUMENTED, (uchar *)pa->str,
10806 (uint)(pa->length + length + PS_MALLOC),
10807 MYF(MY_WME))))
10808 return 1;
10809 if (new_pos != pa->str) {
10810 ptrdiff_t diff = new_pos - pa->str;
10811 for (i = 0; i < pa->typelib.count; i++)
10812 pa->typelib.type_names[i] = pa->typelib.type_names[i] + diff;
10813 pa->str = new_pos;
10814 }
10815 pa->max_length = pa->length + length + PS_MALLOC;
10816 }
10817 if (pa->typelib.count >= pa->max_count - 1) {
10818 int len;
10819 pa->array_allocs++;
10820 len = (PC_MALLOC * pa->array_allocs - MALLOC_OVERHEAD);
10821 if (!(new_array = (const char **)my_realloc(
10822 PSI_NOT_INSTRUMENTED, (uchar *)pa->typelib.type_names,
10823 (uint)len / (sizeof(uchar *) + sizeof(*pa->flag)) *
10824 (sizeof(uchar *) + sizeof(*pa->flag)),
10825 MYF(MY_WME))))
10826 return 1;
10827 pa->typelib.type_names = new_array;
10828 old_count = pa->max_count;
10829 pa->max_count = len / (sizeof(uchar *) + sizeof(*pa->flag));
10830 pa->flag = (uint8 *)(pa->typelib.type_names + pa->max_count);
10831 memcpy((uchar *)pa->flag, (char *)(pa->typelib.type_names + old_count),
10832 old_count * sizeof(*pa->flag));
10833 }
10834 pa->flag[pa->typelib.count] = 0; /* Reset flag */
10835 pa->typelib.type_names[pa->typelib.count++] = (char *)pa->str + pa->length;
10836 pa->typelib.type_names[pa->typelib.count] = NullS; /* Put end-mark */
10837 (void)my_stpcpy((char *)pa->str + pa->length, name);
10838 pa->length += length;
10839 return 0;
10840 } /* insert_pointer_name */
10841
10842 /* free pointer array */
10843
free_pointer_array(POINTER_ARRAY * pa)10844 void free_pointer_array(POINTER_ARRAY *pa) {
10845 if (pa->typelib.count) {
10846 pa->typelib.count = 0;
10847 my_free(pa->typelib.type_names);
10848 pa->typelib.type_names = nullptr;
10849 my_free(pa->str);
10850 }
10851 } /* free_pointer_array */
10852
10853 /* Functions that uses replace and replace_regex */
10854
10855 /* Append the string to ds, with optional replace */
replace_dynstr_append_mem(DYNAMIC_STRING * ds,const char * val,size_t len)10856 void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val,
10857 size_t len) {
10858 #ifdef _WIN32
10859 fix_win_paths(val, len);
10860 #endif
10861
10862 if (display_result_lower) {
10863 /* Convert to lower case, and do this first */
10864 my_casedn_str(charset_info, const_cast<char *>(val));
10865 }
10866
10867 if (glob_replace_regex) {
10868 size_t orig_len = len;
10869 // Regex replace
10870 if (!multi_reg_replace(glob_replace_regex, const_cast<char *>(val), &len)) {
10871 val = glob_replace_regex->buf;
10872 } else {
10873 len = orig_len;
10874 }
10875 }
10876
10877 DYNAMIC_STRING ds_temp;
10878 init_dynamic_string(&ds_temp, "", 512, 512);
10879
10880 /* Store result from replace_result in ds_temp */
10881 if (glob_replace) {
10882 /* Normal replace */
10883 replace_strings_append(glob_replace, &ds_temp, val, len);
10884 }
10885
10886 /*
10887 Call the replace_numeric_round function with the specified
10888 precision. It may be used along with replace_result, so use the
10889 output from replace_result as the input for replace_numeric_round.
10890 */
10891 if (glob_replace_numeric_round >= 0) {
10892 /* Copy the result from replace_result if it was used, into buffer */
10893 if (ds_temp.length > 0) {
10894 char buffer[512];
10895 strcpy(buffer, ds_temp.str);
10896 dynstr_free(&ds_temp);
10897 init_dynamic_string(&ds_temp, "", 512, 512);
10898 replace_numeric_round_append(glob_replace_numeric_round, &ds_temp, buffer,
10899 std::strlen(buffer));
10900 } else
10901 replace_numeric_round_append(glob_replace_numeric_round, &ds_temp, val,
10902 len);
10903 }
10904
10905 if (!glob_replace && glob_replace_numeric_round < 0)
10906 dynstr_append_mem(ds, val, len);
10907 else
10908 dynstr_append_mem(ds, ds_temp.str, std::strlen(ds_temp.str));
10909 dynstr_free(&ds_temp);
10910 }
10911
10912 /* Append zero-terminated string to ds, with optional replace */
replace_dynstr_append(DYNAMIC_STRING * ds,const char * val)10913 void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) {
10914 replace_dynstr_append_mem(ds, val, std::strlen(val));
10915 }
10916
10917 /* Append uint to ds, with optional replace */
replace_dynstr_append_uint(DYNAMIC_STRING * ds,uint val)10918 void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) {
10919 char buff[22]; /* This should be enough for any int */
10920 char *end = longlong10_to_str(val, buff, 10);
10921 replace_dynstr_append_mem(ds, buff, end - buff);
10922 }
10923
10924 /*
10925 Build a list of pointer to each line in ds_input, sort
10926 the list and use the sorted list to append the strings
10927 sorted to the output ds
10928
10929 SYNOPSIS
10930 dynstr_append_sorted
10931 ds - string where the sorted output will be appended
10932 ds_input - string to be sorted
10933 start_sort_column - column to start sorting from (0 for sorting
10934 the entire line); a stable sort will be used
10935 */
10936
10937 class Comp_lines {
10938 public:
operator ()(const char * a,const char * b)10939 bool operator()(const char *a, const char *b) {
10940 return std::strcmp(a, b) < 0;
10941 }
10942 };
10943
length_of_n_first_columns(std::string str,int start_sort_column)10944 static size_t length_of_n_first_columns(std::string str,
10945 int start_sort_column) {
10946 std::stringstream columns(str);
10947 std::string temp;
10948 size_t size_of_columns = 0;
10949
10950 int i = 0;
10951 while (getline(columns, temp, '\t') && i < start_sort_column) {
10952 size_of_columns = size_of_columns + temp.length();
10953 i++;
10954 }
10955
10956 return size_of_columns;
10957 }
10958
dynstr_append_sorted(DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_input,int start_sort_column)10959 void dynstr_append_sorted(DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_input,
10960 int start_sort_column) {
10961 char *start = ds_input->str;
10962 char *end = ds_input->str + ds_input->length;
10963 std::vector<std::string> sorted;
10964 DBUG_TRACE;
10965
10966 if (!*start) return; /* No input */
10967
10968 /* First line is result header, skip past it */
10969 while (*start && *start != '\n') start++;
10970 start++; /* Skip past \n */
10971 dynstr_append_mem(ds, ds_input->str, start - ds_input->str);
10972
10973 /*
10974 Traverse through the result set from start to end to avoid
10975 ignoring null characters (0x00), and insert line by line
10976 into array.
10977 */
10978 size_t first_unsorted_row = 0;
10979 while (start < end) {
10980 char *line_end = (char *)start;
10981
10982 /* Find end of line */
10983 while (*line_end != '\n') line_end++;
10984 *line_end = 0;
10985
10986 std::string result_row = std::string(start, line_end - start);
10987 if (!sorted.empty() && start_sort_column > 0) {
10988 /*
10989 If doing partial sorting, and the prefix is different from that of the
10990 previous line, the group is done. Sort it and start another one.
10991 */
10992 size_t prev_line_prefix_len =
10993 length_of_n_first_columns(sorted.back(), start_sort_column);
10994 if (sorted.back().compare(0, prev_line_prefix_len, result_row, 0,
10995 prev_line_prefix_len) != 0) {
10996 std::sort(sorted.begin() + first_unsorted_row, sorted.end());
10997 first_unsorted_row = sorted.size();
10998 }
10999 }
11000
11001 /* Insert line into the array */
11002 sorted.push_back(result_row);
11003 start = line_end + 1;
11004 }
11005
11006 /* Sort array */
11007 std::stable_sort(sorted.begin() + first_unsorted_row, sorted.end());
11008
11009 /* Create new result */
11010 for (auto i : sorted) {
11011 dynstr_append_mem(ds, i.c_str(), i.length());
11012 dynstr_append(ds, "\n");
11013 }
11014 }
11015