YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense alongwiththisprogram;ifnot,writetotheFreeSoftware
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/* Info on properties that can be set with --enable_X and --disable_X */
struct property {
my_bool *var; /* Actual variable */
my_bool set; /* Has been set for ONE command */
my_bool old; /* If set, thus is the old value */
my_bool reverse; /* Variable is true if disabled */ constchar *env_name; /* Env. variable name */
};
/* Precompiled re's */ static regex_t ps_re; /* the query can be run using PS protocol */ static regex_t ps2_re; /* the query can be run using PS protocol with second execution*/ static regex_t sp_re; /* the query can be run as a SP */ static regex_t view_re; /* the query can be run as a view*/ static regex_t cursor_re; /* the query can be run with cursor protocol*/
/* if set, all results are concated and compared against this file */ constchar *result_file_name= 0;
typedefstruct
{ char *name;
size_t name_len; char *str_val;
size_t str_val_len; int int_val;
size_t alloced_len; bool int_dirty; /* do not update string if int is updated until first read */ bool is_int; bool alloced;
} VAR;
/*Perl/shell-like variable registers */
VAR var_reg[10];
HASH var_hash;
struct st_connection
{
MYSQL *mysql; /* Used when creating views and sp, to avoid implicit commit */
MYSQL* util_mysql; char *name;
size_t name_len;
MYSQL_STMT* stmt;
MYSQL_BIND *ps_params; /* Set after send to disallow other queries before reap */
my_bool pending;
#ifdef EMBEDDED_LIBRARY
pthread_t tid; constchar *cur_query; int cur_query_len; int command, result;
pthread_mutex_t query_mutex;
pthread_cond_t query_cond;
pthread_mutex_t result_mutex;
pthread_cond_t result_cond; int query_done;
my_bool has_thread; #endif/*EMBEDDED_LIBRARY*/
};
longlong to_int() const
{ if (is_numeric) return (longlong)int_val;
errno= 0; longlong val= strtoll(str_val.ptr(), NULL, 10); if (errno == ERANGE)
die("Range error: %.*s value out of range for Integer type",
(int)str_val.length(), str_val.ptr()); return val;
}
unsignedlonglong to_uint() const
{ if (is_numeric) return int_val;
errno= 0; unsignedlonglong val= strtoull(str_val.ptr(), NULL, 10); if (errno == ERANGE)
die("Range error: %.*s value out of range for Integer type",
(int)str_val.length(), str_val.ptr()); return val;
}
// Check for special prefixes (0x, 0b) if (token_len >= 3 && token_start[0] == '0')
{ if (token_start[1] == 'x')
{
base= 16;
} elseif (token_start[1] == 'b')
{
base= 2;
token_start+= 2; // Skip "0b" prefix for strtol
token_len-= 2;
}
}
errno= 0;
parsed_int= strtoull(token_start, &endptr, base); if (errno == ERANGE)
die("Range error: %.*s value out of range for Integer type",
(int)token_len, token_start);
// If the entire token was parsed as an integer, set the type to integer if (endptr == token_start + token_len)
{ if (base == 16 || base == 2 || parsed_int > LLONG_MAX)
set_uint(parsed_int); else
set_int((longlong)parsed_int); return;
}
if (!m_file || m_file == stdout)
DBUG_VOID_RETURN;
if (lines == 0)
DBUG_VOID_RETURN;
lines++;
int show_offset= 0; char buf[256+1]; /* + zero termination for DBUG_PRINT */
size_t bytes; bool found_bof= false;
/* Search backward in file until "lines" newline has been found */ while (lines && !found_bof)
{
show_offset-= sizeof(buf)-1; while(fseek(m_file, show_offset, SEEK_END) != 0 && show_offset < 0)
{
found_bof= true; // Seeking before start of file
show_offset++;
}
if ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) <= 0)
{ // ferror=0 will happen here if no queries executed yet if (ferror(m_file))
fprintf(stderr, "Failed to read from '%s', errno: %d, feof:%d, ferror:%d\n",
m_file_name, errno, feof(m_file), ferror(m_file));
DBUG_VOID_RETURN;
}
if (fseek(m_file, show_offset, SEEK_END) != 0)
{
fprintf(stderr, "Failed to seek to position %d in '%s', errno: %d",
show_offset, m_file_name, errno);
DBUG_VOID_RETURN;
}
} else {
DBUG_PRINT("info", ("Showing the whole file")); if (fseek(m_file, 0L, SEEK_SET) != 0)
{
fprintf(stderr, "Failed to seek to pos 0 in '%s', errno: %d",
m_file_name, errno);
DBUG_VOID_RETURN;
}
}
while ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) > 0) if (bytes != fwrite(buf, 1, bytes, stderr))
die("Failed to write to '%s', errno: %d",
m_file_name, errno);
if (!lines)
{
fprintf(stderr, "\nMore results from queries before failure can be found in %s\n",
m_file_name);
}
fflush(stderr);
/* Wrapper for fgets.Strips \r off newlines on Windows. Shouldbeusedwithtogetherwithmy_popen().
*/ staticchar *my_fgets(char * s, int n, FILE * stream, int *len)
{ char *buf = fgets(s, n, stream); if (!buf)
{
*len= 0; return buf;
}
if ((res= mysql_store_result(mysql)) == NULL)
{ /* No result set returned */
DBUG_VOID_RETURN;
}
if (mysql_num_rows(res) <= 1)
{ /* Don't display the last row, it's "last error" */
} else
{
MYSQL_ROW row; unsignedint row_num= 0; unsignedint num_fields= mysql_num_fields(res);
fprintf(stderr, "\nWarnings from just before the error:\n"); while ((row= mysql_fetch_row(res)))
{ unsignedint i; unsignedlong *lengths= mysql_fetch_lengths(res);
if (++row_num >= mysql_num_rows(res))
{ /* Don't display the last row, it's "last error" */ break;
}
for (i= 0; i < num_args; i++)
{ conststruct command_arg *arg= &args[i]; char delimiter;
switch (arg->type) { /* A string */ case ARG_STRING: /* Skip leading spaces */ while (*ptr && *ptr == ' ')
ptr++;
start= ptr;
delimiter = delimiter_arg; /* If start of arg is ' ` or " search to matching quote end instead */ if (*ptr && strchr ("'`\"", *ptr))
{
delimiter= *ptr;
start= ++ptr;
} /* Find end of arg, terminated by "delimiter" */ while (*ptr && *ptr != delimiter)
ptr++; if (ptr > start)
{
init_dynamic_string(arg->ds, 0, ptr-start, 32);
do_eval(arg->ds, start, ptr, FALSE);
} else
{ /* Empty string */
init_dynamic_string(arg->ds, "", 0, 0);
} /* Find real end of arg, terminated by "delimiter_arg" */ /* This will do nothing if arg was not closed by quotes */ while (*ptr && *ptr != delimiter_arg)
ptr++;
command->last_argument= (char*)ptr;
/* Step past the delimiter */ if (*ptr && *ptr == delimiter_arg)
ptr++;
DBUG_PRINT("info", ("val: %s", arg->ds->str)); break;
/* Rest of line */ case ARG_REST:
start= ptr;
init_dynamic_string(arg->ds, 0, command->query_len, 256);
do_eval(arg->ds, start, command->end, FALSE);
command->last_argument= command->end;
DBUG_PRINT("info", ("val: %s", arg->ds->str)); break;
for (;;)
{
err_file--;
s+= my_snprintf(s, end - s, "included from %s at line %d:\n",
err_file->file_name, err_file->lineno); if (err_file == file_stack) break;
} return s - start;
}
staticvoid make_error_message(char *buf, size_t len, constchar *fmt, va_list args)
{ char *s= buf, *end= buf + len;
s+= my_snprintf(s, end - s, "mysqltest: "); if (cur_file && cur_file != file_stack)
{
s+= my_snprintf(s, end - s, "In included file \"%s\": \n",
cur_file->file_name);
s+= print_file_stack(s, end);
}
if (start_lineno > 0)
s+= my_snprintf(s, end -s, "At line %u: ", start_lineno); if (!fmt)
fmt= "unknown error";
s+= my_vsnprintf(s, end - s, fmt, args);
s+= my_snprintf(s, end -s, "\n");
}
/* Print include filestack */
fflush(stdout);
fprintf(stderr, "The test '%s' is not supported by this installation\n",
file_stack->file_name);
fprintf(stderr, "Detected in file %s at line %d\n",
cur_file->file_name, cur_file->lineno);
int len; while (my_fgets(buf, sizeof(buf), res_file, &len))
{
DBUG_PRINT("info", ("buf: %s", buf)); if(ds_res)
{ /* Save the output of this command in the supplied string */
dynstr_append_mem(ds_res, buf,len);
} else
{ /* Print it directly on screen */
fprintf(stdout, "%s", buf);
}
}
if (init_dynamic_string(&ds_tmp, "", 256, 256))
die("Out of memory");
/* determine if we have diff on Windows needsspecialprocessingduetoreturnvalues onthatOS ThistestisonlydoneonWindowssinceit'sonlyneededthere inordertocorrectlydetectnon-availabilityof'diff',and thewayit'simplementeddoesnotworkwithdefault'diff'onSolaris.
*/ #ifdef _WIN32 if (diff_check("diff"))
diff_name = "diff"; elseif (diff_check("mtrdiff"))
diff_name = "mtrdiff"; else
diff_name = 0; #else
diff_name = "diff"; /* Otherwise always assume it's called diff */ #endif
if (diff_name)
{ /* First try with unified diff */ if (run_tool(diff_name,
&ds_tmp, /* Get output from diff in ds_tmp */ "-u",
filename1,
filename2, "2>&1",
NULL) > 1) /* Most "diff" tools return >1 if error */
{
dynstr_set(&ds_tmp, "");
/* Fallback to context diff with "diff -c" */ if (run_tool(diff_name,
&ds_tmp, /* Get output from diff in ds_tmp */ "-c",
filename1,
filename2, "2>&1",
NULL) > 1) /* Most "diff" tools return >1 if error */
{
dynstr_set(&ds_tmp, "");
/* Fallback to simple diff with "diff" */ if (run_tool(diff_name,
&ds_tmp, /* Get output from diff in ds_tmp */
filename1,
filename2, "2>&1",
NULL) > 1) /* Most "diff" tools return >1 if error */
{
diff_name= 0;
}
}
}
}
if (! diff_name)
{ /* Fallbacktodumpbothfilestoresultfileandinform aboutinstalling"diff"
*/ char message[]= "\n" "\n" "The two files differ but it was not possible to execute 'diff' in\n" "order to show only the difference. Instead the whole content of the\n" "two files was shown for you to diff manually.\n\n" "To get a better report you should install 'diff' on your system, which you\n" "for example can get from http://www.gnu.org/software/diffutils/diffutils.html\n" #ifdef _WIN32 "or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n" #endif "\n";
dynstr_append_mem(&ds_tmp, message, sizeof(message));
if (init_dynamic_string(&fd1_result, 0, fd1_length, 0) ||
init_dynamic_string(&fd2_result, 0, fd2_length, 0))
die("Out of memory when allocating data for result");
(void) my_seek(fd1, 0, SEEK_SET, MYF(0));
(void) my_seek(fd2, 0, SEEK_SET, MYF(0)); if (my_read(fd1, (uchar*) fd1_result.str, fd1_length, MYF(MY_WME | MY_NABP)))
die("Error when reading data from result file"); if (my_read(fd2, (uchar*) fd2_result.str, fd2_length, MYF(MY_WME | MY_NABP)))
die("Error when reading data from result file");
if (global_subst &&
(fd1_length != fd2_length ||
memcmp(fd1_result.str, fd2_result.str, fd1_length)))
{ /** @todoMARIA_HACK Thisservesforwhenatestisrunwith--default-storage-engine=X whereXisnotMyISAM:testsusingSHOWCREATETABLEwillalwaysfail becauseSHOWCREATETABLEprintsXinsteadofMyISAM.With --global-subst=X,MyISAM,suchtrivialdifferencesareeliminatedand testmaybereportedaspassing. --global-substisonlyaquickwaytorunalotofexistingtests withMariaandfindbugs;itisnotgoodenoughforreachingthemain treeswhenMariaismergedintothem. --global-substshouldberemoved.
*/
size_t global_subst_from_len= strlen(global_subst_from);
size_t global_subst_to_len= strlen(global_subst_to); while (replace(&fd1_result,
global_subst_from, global_subst_from_len,
global_subst_to, global_subst_to_len) == 0) /* do nothing */ ; /* let's compare again to see if it is ok now */
}
/* Check if the first non space character is c1 */ while(*ptr && my_isspace(charset_info, *ptr))
ptr++; if (*ptr == c1)
{ /* Replace it with a space */
*ptr= ' ';
/* Last non space character should be c2 */
ptr= strend(str)-1; while(*ptr && my_isspace(charset_info, *ptr))
ptr--; if (*ptr == c2)
{ /* Replace it with \0 */
*ptr= 0;
} else
{ /* Mismatch detected */ return1;
}
} return0;
}
staticvoid strip_parentheses(struct st_command *command)
{ if (strip_surrounding(command->first_argument, '(', ')'))
die("%.*sB - argument list started with '%c' must be ended with '%c'",
command->first_word_len, command->query, '(', ')');
}
/* Initially assume not a number */
v->int_val= 0;
v->is_int= false;
v->int_dirty= false; if (!str) return;
v->int_val = (int) strtol(str, &endptr, 10); /* It is an int if strtol consumed something up to end/space/tab */ if (endptr > str && (!*endptr || *endptr == ' ' || *endptr == '\t'))
v->is_int= true;
}
VAR *var_init(VAR *v, constchar *name, size_t name_len, constchar *val, size_t val_len)
{
size_t val_alloc_len;
VAR *tmp_var; if (!name_len && name)
name_len = strlen(name); if (!val_len && val)
val_len = strlen(val) ; if (!val)
val_len= 0;
val_alloc_len = val_len + 16; /* room to grow */ if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*tmp_var)
+ name_len+2, MYF(MY_WME|MY_FAE))))
die("Out of memory");
init_dynamic_string(&result, "", 512, 512);
lengths= mysql_fetch_lengths(res); for (i= 0; i < mysql_num_fields(res); i++)
{ if (row[i])
{ /* Add column to tab separated string */ char *val= row[i];
size_t len= lengths[i];
if (glob_replace_regex)
{ /* Regex replace */ if (!multi_reg_replace(glob_replace_regex, (char*)val))
{
val= glob_replace_regex->buf;
len= strlen(val);
}
}
if (glob_replace)
replace_strings_append(glob_replace, &result, val); else
dynstr_append_mem(&result, val, len);
}
dynstr_append_mem(&result, "\t", 1);
}
end= result.str + result.length-1; /* Evaluation should not recurse via backtick */
eval_expr(var, result.str, (constchar**) &end, false, false);
dynstr_free(&result);
} else
eval_expr(var, "", 0);
mysql_free_result(res);
DBUG_VOID_RETURN;
}
staticvoid
set_result_format_version(ulong new_version)
{ switch (new_version){ case1: /* The first format */ break; case2: /* New format that also writes comments and empty lines
from test file to result */ break; default:
die("Version format %lu has not yet been implemented", new_version); break;
}
opt_result_format_version= new_version;
}
/* Convert version number to int */ if (!str2int(ds_version.str, ds_version.length, 10, &version))
die("Invalid version number: '%s'", ds_version.str);
/* Convert row number to int */ if (!str2int(ds_row.str, ds_row.length, 10, &row_no))
die("Invalid row number: '%s'", ds_row.str);
DBUG_PRINT("info", ("row: %s, row_no: %u", ds_row.str, row_no));
dynstr_free(&ds_row);
/* Remove any surrounding "'s from the query - if there is any */ if (strip_surrounding(ds_query.str, '"', '"'))
die("Mismatched \"'s around query '%s'", ds_query.str);
/* Run the query */ if (mysql_real_query(mysql, ds_query.str, (ulong)ds_query.length))
{
handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), &ds_res); /* If error was acceptable, return empty string */
dynstr_free(&ds_query);
dynstr_free(&ds_col);
eval_expr(var, "", 0);
DBUG_VOID_RETURN;
}
if (!(res= mysql_store_result(mysql)))
{
report_or_die("Query '%s' didn't return a result set", ds_query.str);
dynstr_free(&ds_query);
dynstr_free(&ds_col);
eval_expr(var, "", 0);
DBUG_VOID_RETURN;
}
{ /* Find column number from the given column name */
uint i;
uint num_fields= mysql_num_fields(res);
MYSQL_FIELD *fields= mysql_fetch_fields(res);
for (i= 0; i < num_fields; i++)
{ if (strcmp(fields[i].name, ds_col.str) == 0 &&
strlen(fields[i].name) == ds_col.length)
{
col_no= i; break;
}
} if (col_no == -1)
{
mysql_free_result(res);
report_or_die("Could not find column '%s' in the result of '%s'",
ds_col.str, ds_query.str);
dynstr_free(&ds_query);
dynstr_free(&ds_col);
DBUG_VOID_RETURN;
}
DBUG_PRINT("info", ("Found column %d with name '%s'",
i, fields[i].name));
}
dynstr_free(&ds_col);
{ /* Get the value */
MYSQL_ROW row;
uint rows= 0; constchar* value= "No such row";
while ((row= mysql_fetch_row(res)))
{ if (++rows == row_no)
{
DBUG_PRINT("info", ("At row %u, column %d is '%s'",
row_no, col_no, row[col_no])); /* Found the row to get */ if (row[col_no])
value= row[col_no]; else
value= "NULL";
/* Alloc/realloc data for str_val in dest */ if (dest->alloced_len < src->alloced_len &&
!(dest->str_val= dest->str_val
? (char*)my_realloc(PSI_NOT_INSTRUMENTED, dest->str_val, src->alloced_len,
MYF(MY_WME|MY_FAE))
: (char*)my_malloc(PSI_NOT_INSTRUMENTED, src->alloced_len,
MYF(MY_WME|MY_FAE))))
die("Out of memory"); else
dest->alloced_len= src->alloced_len;
/* Copy str_val data to dest */
dest->str_val_len= src->str_val_len; if (src->str_val_len)
memcpy(dest->str_val, src->str_val, src->str_val_len);
}
/* Skip to treat as pure string if no evaluation */ if (! do_eval) goto NO_EVAL;
if (*p == '$')
{
VAR *vp; constchar* expected_end= *p_end; // Remember var end if ((vp= var_get(p, p_end, 0, 0)))
var_copy(v, vp);
/* Apparently it is not safe to assume null-terminated string */
v->str_val[v->str_val_len]= 0;
/* Make sure there was just a $variable and nothing else */ constchar* end= *p_end + 1; if (end < expected_end && !open_end)
die("Found junk '%.*sB' after $variable in expression",
(int)(expected_end - end - 1), end);
DBUG_VOID_RETURN;
}
if (*p == '`')
{
var_query_set(v, p, p_end);
DBUG_VOID_RETURN;
}
/* 2. if that failed, we look in the suite dir */
strxnmov(buff, sizeof(buff), opt_suite_dir, suffix, name, NullS);
/* buff can not be equal to curname, as a file can never include itself */ if (open_and_set_current(buff))
DBUG_VOID_RETURN;
} else
{ /* 1. try in current file dirname */
dirname_part(buff, curname, &length);
strxnmov(buff, sizeof(buff), buff, name, NullS); if (open_and_set_current(buff))
DBUG_VOID_RETURN;
}
/* 3. now, look in the overlay dir */ if (opt_overlay_dir)
{
strxmov(buff, opt_overlay_dir, name, NullS); if (open_and_set_current(buff))
DBUG_VOID_RETURN;
}
/* 4. if that failed - look in the suite dir */
strxmov(buff, opt_suite_dir, name, NullS); if (open_and_set_current(buff))
DBUG_VOID_RETURN;
/* 5. the last resort - look in the base dir */
strxnmov(buff, sizeof(buff), opt_basedir, name, NullS); if (open_and_set_current(buff))
DBUG_VOID_RETURN;
}
die("Could not open '%s' for reading, errno: %d", name, errno);
DBUG_VOID_RETURN;
}
/* Look for "echo.exe" in same dir as mysqltest was started from */
dirname_part(builtin_echo, my_progname, &echo_length);
fn_format(builtin_echo, ".\\echo.exe",
builtin_echo, "", MYF(MY_REPLACE_DIR));
/* Make sure echo.exe exists */ if (access(builtin_echo, F_OK) != 0)
builtin_echo[0]= 0; return;
/* Skip leading space */ while (*cmd && my_isspace(charset_info, *cmd))
cmd++; if (!*cmd)
{
report_or_die("Missing argument in exec");
DBUG_VOID_RETURN;
}
command->last_argument= command->end;
init_dynamic_string(&ds_cmd, 0, command->query_len+256, 256); /* Eval the command, thus replacing all environment variables */
do_eval(&ds_cmd, cmd, command->end, !is_windows);
/* Check if echo should be replaced with "builtin" echo */ if (builtin_echo[0] && strncmp(cmd, "echo", 4) == 0)
{ /* Replace echo with our "builtin" echo */
replace(&ds_cmd, "echo", 4, builtin_echo, strlen(builtin_echo));
}
if (disable_result_log)
{ /* Collect stderr output as well, for the case app. crashes or returns error.*/
dynstr_append_mem(&ds_cmd, STRING_WITH_LEN(" 2>&1"));
}
DBUG_PRINT("info", ("Executing '%s' as '%s'",
command->first_argument, ds_cmd.str));
if (!(res_file= my_popen(ds_cmd.str, "r")))
{
dynstr_free(&ds_cmd); if (command->abort_on_error)
report_or_die("popen(\"%s\", \"r\") failed", command->first_argument);
DBUG_VOID_RETURN;
}
ds_result= &ds_res; if (display_result_sorted)
{
init_dynamic_string(&ds_sorted, "", 1024, 1024);
ds_result= &ds_sorted;
} int len; while (my_fgets(buf, sizeof(buf), res_file,&len))
{
replace_dynstr_append_mem(ds_result, buf, len);
}
error= my_pclose(res_file);
if (display_result_sorted)
{
dynstr_append_sorted(&ds_res, &ds_sorted);
dynstr_free(&ds_sorted);
} #ifdef _WIN32
end: #endif if (error)
{
uint status; int i;
#ifdef _WIN32
status= WEXITSTATUS(error); #else /* WEXITSTATUS() is only valid for a normal exit; a process killed by an uncaughtsignalmustbetranslatedusingtheshell's128+signal
convention, or the real error is silently lost as status 0. */ if (WIFEXITED(error))
status= WEXITSTATUS(error); elseif (WIFSIGNALED(error))
status= 128 + WTERMSIG(error); else
status= error;
#endif
if (command->abort_on_error)
{
report_or_die("exec of '%s' failed, error: %d, status: %d, errno: %d\n" "Output from before failure:\n%s\n",
ds_cmd.str, error, status, errno,
ds_res.str);
dynstr_free(&ds_cmd);
DBUG_VOID_RETURN;
}
/* Set default wild chars for wild_compare, is changed in embedded mode */
set_wild_chars(1);
for (i= 0; i < dir_info->number_of_files; i++)
{
file= dir_info->dir_entry + i; /* Remove only regular files, i.e. no directories etc. */ /* if (!MY_S_ISREG(file->mystat->st_mode)) */ /* MY_S_ISREG does not work here on Windows, just skip directories */ if (MY_S_ISDIR(file->mystat->st_mode)) continue; if (ds_wild.length &&
wild_compare(file->name, ds_wild.str, 0)) continue;
ds_file_to_remove.length= directory_length;
dynstr_append_mem(&ds_file_to_remove, file->name, strlen(file->name));
DBUG_PRINT("info", ("removing file: %s", ds_file_to_remove.str)); if ((error= (my_delete(ds_file_to_remove.str, MYF(MY_WME)) != 0)))
sys_errno= my_errno; if (error) break;
}
set_wild_chars(0);
my_dirend(dir_info);
if (!((is_sub_path(ds_from_file.str, from_plen, vardir) &&
is_sub_path(ds_to_file.str, to_plen, vardir)) ||
(is_sub_path(ds_from_file.str, from_plen, tmpdir) &&
is_sub_path(ds_to_file.str, to_plen, tmpdir)))) {
report_or_die("Paths '%s' and '%s' are not both under " "MYSQLTEST_VARDIR '%s' or both under MYSQL_TMP_DIR '%s'",
ds_from_file.str, ds_to_file.str, vardir, tmpdir);
DBUG_VOID_RETURN;
}
/* Parse what mode to set */ if (ds_mode.length != 4 ||
str2int(ds_mode.str, ds_mode.length, 8, &mode) == NullS)
die("You must write a 4 digit octal number for mode");
staticvoid do_list_files(struct st_command *command)
{ int error; static DYNAMIC_STRING ds_dirname;
msg_warnerror looking upfor m, conststruct state))
{java.lang.StringIndexOutOfBoundsException: Range [40, 38) out of bounds for length 63
;
DBUG_ENTER("do_list_files");
command>sed_replace 1java.lang.StringIndexOutOfBoundsException: Index 27 out of bounds for length 27
check_command_args,java.lang.StringIndexOutOfBoundsException: Range [38, 37) out of bounds for length 54
java.lang.StringIndexOutOfBoundsException: Range [37, 36) out of bounds for length 37 sizeof(list_files_args)/sizeof(struct command_arg), ' ');
/* SYNOPSIS do_list_files_write_file_command
java.lang.StringIndexOutOfBoundsException: Range [23, 22) out of bounds for length 30 appendappendfile,orcreatenew
DESCRIPTION {write|ppend}filefilename><>[<atch_file>java.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 69 Listfilesanddirectoriesindirectory<dir_name>(like`ls`) [Matching<match_file>,wherewild-cardsareallowed]
if (ds_delimiter->length > MAX_DELIMITER_LENGTH)
die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH);
/* Read from file until delimiter is found */ while (1)
{
c= my_getc(cur_file->file); if (c == '\r')
c= my_getc(cur_file->file); if (c == '\n')
{
cur_file->lineno++;
/* Skip newline from the same line as the command */ if (start_lineno == (cur_file->lineno - 1)) continue;
} elseif (start_lineno == cur_file->lineno)
{ /* Nocharactersexcept\nareallowedon thesamelineasthecommand
*/
report_or_die("Trailing characters found after command");
}
if (feof(cur_file->file))
report_or_die("End of file encountered before '%s' delimiter was found",
ds_delimiter->str);
if ((eval_content= (ds_eval_flag.length != 0)) &&
strcasecmp(ds_eval_flag.str, "eval"))
{
report_or_die("Invalid argument '%s' to '%.*s', only 'eval' is allowed",
ds_eval_flag.str, command->first_word_len, command->query);
DBUG_VOID_RETURN;
}
if (!append && access(ds_filename.str, F_OK) == 0)
{ /* The file should not be overwritten */
die("File already exist: '%s'", ds_filename.str);
}
ds_content= command->content; /* If it hasn't been done already by a loop iteration, fill it in */ if (! ds_content.str)
{ /* If no delimiter was provided, use EOF */ if (ds_delimiter.length == 0)
dynstr_set(&ds_delimiter, "EOF");
init_dynamic_string(&ds_content, "", 1024, 1024);
read_until_delimiter(&ds_content, &ds_delimiter);
command->content= ds_content;
} /* This function could be called even if "false", so check before printing */ if (cur_block->ok)
{
DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str)); if (eval_content)
{ /* Evaluate on every execution, keep command->content unevaluated */
DYNAMIC_STRING ds_eval_content; if (init_dynamic_string(&ds_eval_content, "", ds_content.length + 256, 256))
die("Out of memory");
do_eval(&ds_eval_content, ds_content.str,
ds_content.str + ds_content.length, FALSE);
str_to_file2(ds_filename.str, ds_eval_content.str, ds_eval_content.length,
append);
dynstr_free(&ds_eval_content);
} else
str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append);
}
dynstr_free(&ds_filename);
dynstr_free(&ds_delimiter);
dynstr_free(&ds_eval_flag);
DBUG_VOID_RETURN;
}
ds_script= command->content; /* If it hasn't been done already by a loop iteration, fill it in */ if (! ds_script.str)
{ /* If no delimiter was provided, use EOF */ if (ds_delimiter.length == 0)
dynstr_set(&ds_delimiter, "EOF");
/* This function could be called even if "false", so check before doing */ if (cur_block->ok)
{
DBUG_PRINT("info", ("Executing perl: %s", ds_script.str));
/* Create temporary file name */ if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), "tmp", O_SHARE, MYF(MY_WME))) < 0)
die("Failed to create temporary file for perl command");
my_close(fd, MYF(0));
/* Use the same perl executable as the one that runs mysql-test-run.pl */ constchar *mtr_perl=getenv("MTR_PERL"); if (!mtr_perl)
mtr_perl="perl";
/* Format the "perl <filename>" command */ if (strchr(mtr_perl, ' '))
my_snprintf(buf, sizeof(buf), "\"%s\" %s", mtr_perl, temp_file_path); else
my_snprintf(buf, sizeof(buf), "%s %s", mtr_perl, temp_file_path);
if (!(res_file= my_popen(buf, "r")))
{ if (command->abort_on_error)
die("popen(\"%s\", \"r\") failed", buf);
dynstr_free(&ds_delimiter);
DBUG_VOID_RETURN;
}
int len; while (my_fgets(buf, sizeof(buf), res_file,&len))
{ if (disable_result_log)
{
buf[len - 1] = 0;
DBUG_PRINT("exec_result", ("%s", buf));
} else
{
replace_dynstr_append_mem(&ds_res, buf, len);
}
}
error= my_pclose(res_file);
/* Remove the temporary file, but keep it if perl failed */ if (!error)
my_delete(temp_file_path, MYF(MY_WME));
/* Check for error code that indicates perl could not be started */ int exstat= WEXITSTATUS(error); #ifndef _WIN32 if (exstat == 127)
abort_not_supported_test("perl not found in path"); else #endif
handle_command_error(command, exstat, my_errno);
}
dynstr_free(&ds_delimiter);
DBUG_VOID_RETURN;
}
void do_wait_for_slave_to_stop(struct st_command *c __attribute__((unused)))
{ staticint SLAVE_POLL_INTERVAL= 300000;
MYSQL* mysql = cur_con->mysql; for (;;)
{
MYSQL_RES *UNINIT_VAR(res);
MYSQL_ROW row; int done;
if (mysql_query(mysql,"show status like 'Slave_running'") ||
!(res=mysql_store_result(mysql)))
die("Query failed while probing slave for stop: %s",
mysql_error(mysql)); if (!(row=mysql_fetch_row(res)) || !row[1])
{
mysql_free_result(res);
die("Strange result from query while probing slave for stop");
}
done = !strcmp(row[1],"OFF");
mysql_free_result(res); if (done) break;
my_sleep(SLAVE_POLL_INTERVAL);
} return;
}
if (mysql_query(mysql, query_buf))
die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql),
mysql_error(mysql));
if (!(res= mysql_store_result(mysql)))
die("mysql_store_result() returned NULL for '%s'", query_buf); if (!(row= mysql_fetch_row(res)))
{
mysql_free_result(res);
die("empty result in %s", query_buf);
}
int result= -99; constchar* result_str= row[0]; if (result_str)
result= atoi(result_str);
mysql_free_result(res);
if (!result_str || result < 0)
{ /* master_pos_wait returned NULL or < 0 */
fprintf(stderr, "analyze: sync_with_master\n");
snprintf(query_buf2, sizeof(query_buf2)-1, "select Relay_Master_Log_File, Read_Master_Log_Pos, Gtid_IO_Pos, " "Gtid_Slave_Pos from information_schema.slave_status where " "Connection_name='%.64s'", connection_name);
while (*p && my_isspace(charset_info, *p))
p++; if (*p == ',')
{
p++; while (*p && my_isspace(charset_info, *p))
p++;
start= buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, strlen(p)+1,
MYF(MY_WME|MY_FAE));
get_string(&buff, &p, command);
}
command->last_argument= p;
}
do_sync_with_master2(command, offset, start); if (buff)
my_free(start); return;
}
int do_save_master_pos()
{
MYSQL_RES *res;
MYSQL_ROW row;
MYSQL *mysql = cur_con->mysql; constchar *query;
DBUG_ENTER("do_save_master_pos");
if (mysql_query(mysql, query= "show master status"))
die("failed in 'show master status': %d %s",
mysql_errno(mysql), mysql_error(mysql));
if (!(res = mysql_store_result(mysql)))
die("mysql_store_result() returned NULL for '%s'", query); if (!(row = mysql_fetch_row(res)))
die("empty result in show master status");
strmake(master_pos.file, row[0], sizeof(master_pos.file)-1);
master_pos.pos= strtoul(row[1], (char**) 0, 10);
strmake(master_pos.gtid, row[4], sizeof(master_pos.gtid)-1);
mysql_free_result(res);
DBUG_RETURN(0);
}
/**
@brief Parse and evaluate a mathematical/logical expression
@param[out] result Pointer to Expression_value structure to store the result
@param[in] s Pointer to string pointer containing the expression to parse
@details
This is the main entry point for the recursive descent expression parser.
It parses expressions with the following grammar hierarchy (by precedence):
The parser advances the string pointer *s as it consumes tokens.
@note
The parser does not handle overflow/underflow of large numbers after
the initial parsing. It is the responsibility of the caller to ensure that
the result of the operations is within the range of the data type (2^64-1).
@note Dies with error message if syntax error or type mismatch detected
*/
static void convert_base_helper(const My_string &str, int from_base, int to_base,
Expression_value *value)
{
char temp_buffer[66]; // should be enough for any base
long long result;
char *endptr;
int err;
size_t str_len= str.length();
if (from_base < 0) // Negative base = treat input as SIGNED
result= my_strntoll_8bit(charset_info, str.ptr(), str_len, -from_base,
&endptr, &err);
else // Positive base = treat input as UNSIGNED
result= (long long) my_strntoull_8bit(charset_info, str.ptr(), str_len,
from_base, &endptr, &err);
if (err == ERANGE)
die("Range error: value out of range for Integer type");
if (err != 0 || endptr != str.ptr() + str_len)
die("invalid number '%.*s' for base %d", (int)str_len, str.ptr(), from_base);
endptr= longlong2str(result, temp_buffer, to_base);
if (!endptr)
die("could not convert number '%.*s' for base %d",
(int)str_len, str.ptr(), to_base);
if (*b_ptr == '-') b_negative= true, b_ptr++, b_len--;
else if (*b_ptr == '+') b_ptr++, b_len--;
// Skip leading zeros
while (a_len > 0 && *a_ptr == '0') a_ptr++, a_len--;
while (b_len > 0 && *b_ptr == '0') b_ptr++, b_len--;
// Find actual numeric length (digits only)
size_t a_digits= 0, b_digits= 0;
for (size_t i= 0; i < a_len && my_isdigit(charset_info, a_ptr[i]); i++) a_digits++;
for (size_t i= 0; i < b_len && my_isdigit(charset_info, b_ptr[i]); i++) b_digits++;
// Handle zero cases after removing leading zeros
bool a_is_zero= (a_digits == 0);
bool b_is_zero= (b_digits == 0);
if (a_is_zero && b_is_zero) return 0;
if (a_is_zero) return b_negative ? 1 : -1;
if (b_is_zero) return a_negative ? -1 : 1;
// Different signs: negative < positive
if (a_negative && !b_negative) return -1;
if (!a_negative && b_negative) return 1;
// Same sign: compare absolute values
int abs_cmp= 0;
// First compare by number of digits
if (a_digits != b_digits)
{
abs_cmp= (a_digits > b_digits) ? 1 : -1;
}
else
{
// Same number of digits: compare digit by digit
for (size_t i= 0; i < a_digits; i++)
{
if (a_ptr[i] != b_ptr[i])
{
abs_cmp= (a_ptr[i] > b_ptr[i]) ? 1 : -1;
break;
}
}
}
// If both negative, reverse the comparison result
return a_negative ? -abs_cmp : abs_cmp;
}
/* Expression Built-in Function Implementations */
/**
@brief Absolute value function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
ABS(X) returns the absolute (positive) value of X.
@note Dies if argument count != 1 or argument is not numeric
*/
void func_abs(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("abs() expects 1 argument, got %d", count);
if (!args[0].is_numeric)
die("abs() requires numeric argument");
result->set_int(llabs(args[0].to_int()));
}
/**
@brief Base conversion function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
Converts numbers between different bases.
CONV(N, from_base, to_base) converts number N from base from_base to base to_base.
Bases can be from 2 to 62. Negative bases treat input as signed.
@note Dies if argument count != 3, bases not numeric, or bases out of range
*/
void func_conv(Expression_value args[], int count, Expression_value *result)
{
int from_base;
int to_base;
if (!args[1].is_numeric || !args[2].is_numeric)
die("conv() bases must be numeric");
if (abs(from_base) < 2 || abs(from_base) > 62)
die("conv() from_base must be between 2 and 62, got %d", from_base);
if (abs(to_base) < 2 || abs(to_base) > 62)
die("conv() to_base must be between 2 and 62, got %d", to_base);
/**
@brief Binary conversion function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
Converts a number to binary representation.
BIN(N) returns a string representation of the binary value of N.
This is equivalent to CONV(N, 10, 2).
@note Dies if argument count != 1
*/
void func_bin(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("bin() expects 1 argument, got %d", count);
/**
@brief Octal conversion function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
Converts a number to octal representation.
OCT(N) returns a string representation of the octal value of N.
This is equivalent to CONV(N, 10, 8).
@note Dies if argument count != 1
*/
void func_oct(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("oct() expects 1 argument, got %d", count);
/**
@brief Hexadecimal conversion function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
HEX(N) returns a string representation of the hexadecimal value of N.
This is equivalent to CONV(N, 10, 16).
HEX(str) returns a hexadecimal representation of the bytes of str.
@note Dies if argument count != 1
*/
void func_hex(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("hex() expects 1 argument, got %d", count);
/**
@brief String search function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
INSTR(str, substr) returns position of first occurrence of substr in str.
Returns 0 if not found. Positions start at 1. Performs a case-insensitive
search.
/**
@brief Locate substring function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LOCATE(substr, str [, start]) returns position of substr in str starting from
start (default 1). Returns 0 if substr is not in str. Positions start at 1.
Performs a case-insensitive search.
@note Dies if wrong argument count or non-numeric start position
*/
void func_locate(Expression_value args[], int count, Expression_value *result)
{
if (count < 2 || count > 3)
die("locate() expects 2 or 3 arguments (substr, str [, start]), got %d", count);
if (count == 3 && !args[2].is_numeric)
die("locate() start position must be numeric");
/**
@brief String replacement function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REPLACE(str, from_str, to_str) replaces all occurrences of from_str with
to_str. Performs a case-sensitive match when searching for from_str.
@note Dies if argument count != 3 or arguments are numeric
*/
/**
@brief Substring extraction function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
SUBSTR(str, pos [, len]) returns substring starting at position pos.
Positions start at 1. Negative positions count from end.
@note Dies if wrong argument count or non-numeric position/length
*/
void func_substr(Expression_value args[], int count, Expression_value *result)
{
if (count < 2 || count > 3)
die("substr() expects 2 or 3 arguments (str, start [, length]), got %d", count);
if (args[0].is_numeric)
die("substr() first argument must be a string");
if (!args[1].is_numeric)
die("substr() start position must be numeric");
if (count == 3 && !args[2].is_numeric)
die("substr() length must be numeric");
My_string str= args[0].to_string();
int start= (int)args[1].to_int();
int length= count == 3 ? (int)args[2].to_int() : str.length();
if (start < 0)
start= str.length() + start;
else
start--;
int end= str.length();
if (count == 3)
{
end= start + length;
if (end > (int)str.length())
end= str.length();
}
if (start >= end || start >= (int)str.length() || start < 0)
{
result->set_string("", 0);
return;
}
result->set_string(str.ptr() + start, end - start);
}
/**
@brief String concatenation function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
CONCAT(str1, str2, ...) returns the string that results from concatenating
arguments.
@note Dies if no arguments provided
*/
void func_concat(Expression_value args[], int count, Expression_value *result)
{
if (count == 0)
die("concat() expects at least 1 argument");
My_string result_str= args[0].to_string();
for (int i= 1; i < count; ++i)
{
result_str.append(args[i].to_string());
}
/**
@brief String concatenation with separator function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
CONCAT_WS(separator, str1, str2, ...) concatenates strings using separator.
If separator is NULL, returns NULL; all other NULL values are skipped.
@note Dies if less than 2 arguments provided
*/
void func_concat_ws(Expression_value args[], int count, Expression_value *result)
{
if (count < 2)
die("concat_ws() expects at least 2 arguments");
if (args[0].type == EXPR_NULL)
{
result->reset();
return;
}
/**
@brief String case conversion to lowercase
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LOWER(str) returns string str with all characters converted to lowercase.
LCASE(str) is an alias for LOWER(str).
@note Dies if argument count != 1
*/
void func_lower(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("lower() expects 1 argument, got %d", count);
/**
@brief String case conversion to uppercase
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
UPPER(str) returns string str with all characters converted to uppercase.
UCASE(str) is an alias for UPPER(str).
@note Dies if argument count != 1
*/
void func_upper(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("upper() expects 1 argument, got %d", count);
/**
@brief String reversal function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REVERSE(str) returns the string str with the order of the characters reversed.
@note Dies if argument count != 1
*/
void func_reverse(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("reverse() expects 1 argument, got %d", count);
My_string str= args[0].to_string();
size_t len= str.length();
for (size_t i= 0; i < len / 2; ++i)
{
char temp= str.c_ptr()[i];
str.c_ptr()[i]= str.c_ptr()[len - 1 - i];
str.c_ptr()[len - 1 - i]= temp;
}
result->set_string(str.ptr(), str.length());
}
/**
@brief String trimming function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
TRIM(str) returns string str with leading and trailing space removed.
@note Dies if argument count != 1
*/
void func_trim(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("trim() expects 1 argument, got %d", count);
My_string str= args[0].to_string();
int start= 0;
while (start < (int)str.length() && my_isspace(charset_info, str.ptr()[start]))
start++;
int end= (int)str.length();
while (end > start && my_isspace(charset_info, str.ptr()[end - 1]))
end--;
result->set_string(str.ptr() + start, end - start);
}
/**
@brief Left string trimming function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LTRIM(str) returns string str with leading space removed.
@note Dies if argument count != 1
*/
void func_ltrim(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("ltrim() expects 1 argument, got %d", count);
My_string str= args[0].to_string();
int start= 0;
while (start < (int)str.length() && my_isspace(charset_info, str.ptr()[start]))
start++;
/**
@brief Right string trimming function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
RTRIM(str) returns string str with trailing space removed.
@note Dies if argument count != 1
*/
void func_rtrim(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("rtrim() expects 1 argument, got %d", count);
My_string str= args[0].to_string();
int end= (int)str.length();
while (end > 0 && my_isspace(charset_info, str.ptr()[end - 1]))
end--;
result->set_string(str.ptr(), end);
}
/**
@brief Left padding function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LPAD(str, len [, padstr]) returns the string str, left-padded with the
string padstr to a length of len characters. If str is longer than len,
the return value is shortened to len characters. If padstr is omitted,
the LPAD function pads spaces.
@note Dies if wrong argument count, non-numeric length, or negative length
*/
void func_lpad(Expression_value args[], int count, Expression_value *result)
{
if (count != 2 && count != 3)
die("lpad() expects 2 or 3 arguments (str, length [, padstr]), got %d", count);
if (!args[1].is_numeric)
die("lpad() length must be numeric");
/**
@brief Right padding function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
RPAD(str, len [, padstr]) returns the string str, right-padded with the
string padstr to a length of len characters. If str is longer than len,
the return value is shortened to len characters. If padstr is omitted,
the RPAD function pads spaces.
@note Dies if wrong argument count, non-numeric length, or negative length
*/
void func_rpad(Expression_value args[], int count, Expression_value *result)
{
if (count != 2 && count != 3)
die("rpad() expects 2 or 3 arguments (str, length [, padstr]), got %d", count);
if (!args[1].is_numeric)
die("rpad() length must be numeric");
if (length <= (int)str.length())
{
result->set_string(str.ptr(), length);
return;
}
int padding_needed= length - (int)str.length();
if (count == 2)
{
for (int i= 0; i < padding_needed; ++i)
str.append(" ", 1);
}
else
{
for (int i= 0; i < padding_needed; ++i)
str.append(&padstr.ptr()[i % padstr.length()], 1);
}
result->set_string(str.ptr(), str.length());
}
/**
@brief String length function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LENGTH(str) returns the length of the string str.
@note Dies if argument count != 1
*/
void func_length(Expression_value args[], int count, Expression_value *result)
{
if (count != 1)
die("length() expects 1 argument, got %d", count);
/**
@brief Substring index function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
SUBSTRING_INDEX(str, delim, count) returns substring before count occurrences
of delim (counting from the left). If count is negative, returns substring
after count occurrences from end (counting from the right). Performs a
case-sensitive match when searching for delim.
@note Dies if argument count != 3 or count is not numeric
*/
/**
@brief String repetition function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REPEAT(str, count) returns string consisting of str repeated count times.
If count is less than 1, returns an empty string.
@note Dies if argument count != 2 or count is not numeric
*/
/**
@brief String insertion function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
INSERT(str, pos, len, newstr) replaces len characters starting at pos with
newstr. Returns the original string if pos is not within the length of the string.
Replaces the rest of the string from position pos if len is not
within the length of the rest of the string.
@note Dies if argument count != 4 or position/length not numeric
*/
/**
@brief Regular expression position function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REGEXP_INSTR(subject, pattern) returns position of first match of pattern in
subject. Uses POSIX extended regular expressions with case-insensitive
matching.
@note
The collation case sensitivity can be overwritten using the (?i) and (?-i) PCRE flags.
@note
The $ literal is used as a special character for variable replacement.
To use it as a regex End of Line/String Anchor, escape it with a backslash.
@code
$(regexp_instr('ABCC','C\$')) -> 4
@endcode
To use it as a literal, escape it with three backslashes.
@code
$(regexp_instr('ABC\$DE','\\\$')) -> 4
@endcode
@note Dies if argument count != 2 or regex compilation fails
*/
/**
@brief Regular expression substring extraction function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REGEXP_SUBSTR(subject, pattern) returns substring that matches pattern.
Uses POSIX extended regular expressions with case-insensitive matching.
@note
The collation case sensitivity can be overwritten using the (?i) and (?-i) PCRE flags.
@note
The $ literal is used as a special character for variable replacement.
To use it as a regex End of Line/String Anchor, escape it with a backslash.
@code
$(regexp_substr('ABCC','C\$')) -> C
@endcode
To use it as a literal, escape it with three backslashes.
@code
$(regexp_substr('ABC\$','\\\$')) -> $
@endcode
@note Dies if argument count != 2 or regex compilation fails
*/
/**
@brief Regular expression replacement function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
REGEXP_REPLACE(subject, pattern, replace) replaces all matches of pattern
with replace. Supports backreferences \0-\9 in replace string. Uses
POSIX extended regex.
@note
The collation case sensitivity can be overwritten using the (?i) and (?-i) PCRE flags.
@note
The $ literal is used as a special character for variable replacement.
To use it as a regex End of Line/String Anchor, escape it with a backslash.
@code
$(regexp_replace('ABCC','C\$','D')) -> ABCD
@endcode
To use it as a literal, escape it with three backslashes.
@code
$(regexp_replace('ABC\$','\\\$','D')) -> ABCD
@endcode
@note Dies if argument count != 3 or regex compilation fails
*/
/**
@brief NULL-aware conditional function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
IFNULL(expr1, expr2) returns expr1 if it is not NULL, otherwise returns expr2.
Returns first non-NULL value.
@note Dies if argument count != 2
*/
void func_ifnull(Expression_value args[], int count, Expression_value *result)
{
if (count != 2)
die("ifnull() expects 2 arguments, got %d", count);
/**
@brief NULL-if-equal function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
NULLIF(expr1, expr2) returns NULL if expr1 = expr2, otherwise returns expr1.
This is the same as `CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END`.
@note Dies if argument count != 2
*/
void func_nullif(Expression_value args[], int count, Expression_value *result)
{
if (count != 2)
die("nullif() expects 2 arguments, got %d", count);
/* If first is NULL, result is NULL (returns expr1) */
if (args[0].type == EXPR_NULL)
{
result->reset();
return;
}
if (equal)
result->reset();
else
*result= args[0];
}
/**
@brief First non-NULL value function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
COALESCE(value1, value2, ...) returns first non-NULL value in list.
Returns NULL if all values are NULL.
@note Dies if no arguments provided
*/
void func_coalesce(Expression_value args[], int count, Expression_value *result)
{
if (count < 1)
die("coalesce() expects at least 1 argument");
for (int i= 0; i < count; ++i)
{
if (args[i].type != EXPR_NULL)
{
*result= args[i];
return;
}
}
result->reset();
}
/**
@brief Least value function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
LEAST(value1, value2, ...) returns the minimum value among arguments.
For numeric values, performs numeric comparison. For strings, performs
string comparison.
@note
If mixed numeric and string values are provided, the string values are
converted to numbers and the numeric comparison is performed. Be careful
with this, as a string could contain a number that is larger than 2^64-1.
@note Dies if less than 2 arguments provided.
*/
void func_least(Expression_value args[], int count, Expression_value *result)
{
if (count < 2)
die("least() expects at least 2 arguments");
// Numeric mode if any arg is numeric
bool any_numeric= false;
for (int i= 0; i < count; ++i)
if (args[i].is_numeric) { any_numeric= true; break; }
if (any_numeric)
{
int best_idx= 0;
for (int i= 1; i < count; ++i)
{
if (cmp_decimal(args[i].to_string(), args[best_idx].to_string()) < 0)
best_idx= i;
}
if(args[best_idx].is_unsigned)
result->set_uint(args[best_idx].to_uint());
else
result->set_int(args[best_idx].to_int());
return;
}
// String mode: collation-aware
int best_idx= 0;
for (int i= 1; i < count; ++i)
{
My_string cur= args[i].to_string();
My_string best= args[best_idx].to_string();
if (sortcmp(&best, &cur, charset_info) > 0)
best_idx= i;
}
*result= args[best_idx];
}
/**
@brief Greatest value function
@param[in] args Array of function arguments
@param[in] count Number of arguments
@param[out] result Expression_value to store result
@details
GREATEST(value1, value2, ...) returns the maximum value among arguments.
For numeric values, performs numeric comparison. For strings, performs
string comparison.
@note
If mixed numeric and string values are provided, the string values are
converted to numbers and the numeric comparison is performed. Be careful
with this, as a string could contain a number that is larger than 2^64-1.
@note Dies if less than 2 arguments provided.
*/
void func_greatest(Expression_value args[], int count, Expression_value *result)
{
if (count < 2)
die("greatest() expects at least 2 arguments");
bool any_numeric= false;
for (int i= 0; i < count; ++i)
if (args[i].is_numeric) { any_numeric= true; break; }
if (any_numeric)
{
int best_idx= 0;
for (int i= 1; i < count; ++i)
{
if (cmp_decimal(args[i].to_string(), args[best_idx].to_string()) > 0)
best_idx= i;
}
if(args[best_idx].is_unsigned)
result->set_uint(args[best_idx].to_uint());
else
result->set_int(args[best_idx].to_int());
return;
}
// String mode: collation-aware
int best_idx= 0;
for (int i= 1; i < count; ++i)
{
My_string best= args[best_idx].to_string();
My_string cur= args[i].to_string();
if (sortcmp(&best, &cur, charset_info) < 0)
best_idx= i;
}
*result= args[best_idx];
}
enum func_type get_expr_function_type(const char *name, size_t len)
{
for (int i= 0; function_table[i].name; ++i)
{
if (function_table[i].length == len &&
!strncasecmp(function_table[i].name, name, len))
return function_table[i].type;
}
return FUNC_UNKNOWN;
}
/**
@brief Execute built-in function call during expression parsing
@param[in] func_type Type of function to execute (from func_type enum)
@param[out] result Expression_value to store function result
@param[in] s Pointer to string pointer containing expression
@details
Parses function arguments and executes the specified built-in function.
Each argument is recursively evaluated as a full expression.
Then delegates to the appropriate func_* implementation based on func_type.
@note Dies on syntax errors or argument count mismatches
*/
while (!match(s, ")"))
{
if (arg_count >= MAX_FUNC_ARGS)
die("Too many function arguments (max %d)", MAX_FUNC_ARGS);
expr(&args[arg_count], s);
arg_count++;
/* if the next character is a comma, skip it */
if (match(s, ","))
continue;
if (match(s, ")"))
break;
die("Syntax error: Expected ',' or ')' in function call");
}
/* generic NULL propagation, except for IFNULL/NULLIF/COALESCE/CONCAT_WS */
if (func_type != FUNC_IFNULL && func_type != FUNC_NULLIF &&
func_type != FUNC_COALESCE && func_type != FUNC_CONCAT_WS)
{
for (int i= 0; i < arg_count; i++)
{
if (args[i].type == EXPR_NULL)
{
result->reset();
return;
}
}
}
switch (func_type) {
case FUNC_ABS:
func_abs(args, arg_count, result);
break;
case FUNC_CONV:
func_conv(args, arg_count, result);
break;
case FUNC_BIN:
func_bin(args, arg_count, result);
break;
case FUNC_OCT:
func_oct(args, arg_count, result);
break;
case FUNC_HEX:
func_hex(args, arg_count, result);
break;
case FUNC_INSTR:
func_instr(args, arg_count, result);
break;
case FUNC_LOCATE:
func_locate(args, arg_count, result);
break;
case FUNC_REPLACE:
func_replace(args, arg_count, result);
break;
case FUNC_SUBSTR:
func_substr(args, arg_count, result);
break;
case FUNC_CONCAT:
func_concat(args, arg_count, result);
break;
case FUNC_CONCAT_WS:
func_concat_ws(args, arg_count, result);
break;
case FUNC_LOWER:
func_lower(args, arg_count, result);
break;
case FUNC_UPPER:
func_upper(args, arg_count, result);
break;
case FUNC_REVERSE:
func_reverse(args, arg_count, result);
break;
case FUNC_TRIM:
func_trim(args, arg_count, result);
break;
case FUNC_LTRIM:
func_ltrim(args, arg_count, result);
break;
case FUNC_RTRIM:
func_rtrim(args, arg_count, result);
break;
case FUNC_LPAD:
func_lpad(args, arg_count, result);
break;
case FUNC_RPAD:
func_rpad(args, arg_count, result);
break;
case FUNC_LENGTH:
func_length(args, arg_count, result);
break;
case FUNC_SUBSTR_IDX:
func_substring_index(args, arg_count, result);
break;
case FUNC_REPEAT:
func_repeat(args, arg_count, result);
break;
case FUNC_INSERT:
func_insert(args, arg_count, result);
break;
case FUNC_LEAST:
func_least(args, arg_count, result);
break;
case FUNC_GREATEST:
func_greatest(args, arg_count, result);
break;
case FUNC_REGEXP_INSTR:
func_regexp_instr(args, arg_count, result);
break;
case FUNC_REGEXP_SUBSTR:
func_regexp_substr(args, arg_count, result);
break;
case FUNC_REGEXP_REPLACE:
func_regexp_replace(args, arg_count, result);
break;
case FUNC_IFNULL:
func_ifnull(args, arg_count, result);
break;
case FUNC_NULLIF:
func_nullif(args, arg_count, result);
break;
case FUNC_COALESCE:
func_coalesce(args, arg_count, result);
break;
case FUNC_UNKNOWN:
default:
die("Unknown function");
break;
}
}
/*
Assign the variable <var_name> with <var_val>
SYNOPSIS
do_let()
query called command
DESCRIPTION
let $<var_name>=<var_val><delimiter>
<var_name> - is the string string found between the $ and =
<var_val> - is the content between the = and <delimiter>, it may span
multiple line and contain any characters except <delimiter>
<delimiter> - is a string containing of one or more chars, default is ;
RETURN VALUES
Program will die if error detected
*/
SYNOPSIS
do_sleep() q called command
real_sleep use the value from opt_sleep as number of seconds to sleep
if real_sleep is false
DESCRIPTION
sleep <seconds>
real_sleep <seconds>
The difference between the sleep and real_sleep commands is that sleep
uses the delay from the --sleep command-line option if there is one.
(If the --sleep option is not given, the sleep command uses the delay
specified by its argument.) The real_sleep command always uses the
delay specified by its argument. The logic is that sometimes delays are
cpu-dependent, and --sleep can be used to set this delay. real_sleep is
used for cpu-independent delays.
*/
p= ds_sleep.str;
sleep_end= ds_sleep.str + ds_sleep.length;
while (my_isspace(charset_info, *p)) p++;
if (!*p)
die("Missing argument to %.*sB", command->first_word_len, command->query);
sleep_start= p;
/* Check that arg starts with a digit, not handled by my_strtod */
if (!my_isdigit(charset_info, *sleep_start))
die("Invalid argument to %.*sB \"%s\"", command->first_word_len, command->query, sleep_start);
sleep_val= my_strtod(sleep_start, &sleep_end, &error);
check_eol_junk_line(sleep_end);
if (error)
die("Invalid argument to %.*sB \"%s\"", command->first_word_len, command->query, command->first_argument);
dynstr_free(&ds_sleep);
/* Fixed sleep time selected by --sleep option */
if (opt_sleep >= 0 && !real_sleep)
sleep_val= opt_sleep;
#ifdef _WIN32
#define SIGKILL 9
#include <my_minidump.h>
static int my_kill(int pid, int sig)
{
HANDLE proc;
if (sig == SIGABRT)
{
/*
Create a minidump. If process is being debugged, debug break
Otherwise, terminate.
*/
verbose_msg("Aborting %d",pid);
my_create_minidump(pid,TRUE);
proc= OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(!proc)
return -1;
BOOL debugger_present;
if (CheckRemoteDebuggerPresent(proc,&debugger_present) && debugger_present)
{
if (DebugBreakProcess(proc))
{
CloseHandle(proc);
return 0;
}
}
}
else if ((proc= OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid)) == NULL)
return -1;
if (sig == 0)
{
DWORD wait_result= WaitForSingleObject(proc, 0);
CloseHandle(proc);
return wait_result == WAIT_OBJECT_0?-1:0;
}
(void)TerminateProcess(proc, 201);
CloseHandle(proc);
return 1;
}
/* Wait until process is gone, with timeout */
static int wait_until_dead(int pid, int timeout)
{
HANDLE proc= OpenProcess(SYNCHRONIZE, FALSE, pid);
if (!proc)
return 0; /* already dead */
DBUG_ASSERT(timeout >= 0);
DBUG_ASSERT(timeout <= UINT_MAX/1000);
DWORD wait_result= WaitForSingleObject(proc, (DWORD)timeout*1000);
CloseHandle(proc);
return (int)wait_result;
}
#else /* !_WIN32 */
static int my_kill(int pid, int sig)
{
DBUG_PRINT("info", ("Killing server, pid: %d", pid));
return kill(pid, sig);
}
/*
Shutdown the server of current connection and
make sure it goes away within <timeout> seconds
NOTE! Currently only works with local server
SYNOPSIS
do_shutdown_server() command called command
DESCRIPTION
shutdown_server [<timeout>]
*/
static int wait_until_dead(int pid, int timeout)
{
DBUG_ENTER("wait_until_dead");
/* Check that server dies */
while (timeout--)
{
if (my_kill(pid, 0) < 0)
{
DBUG_PRINT("info", ("Process %d does not exist anymore", pid));
DBUG_RETURN(0);
}
DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout));
/* Sleep one second */
my_sleep(1000000L);
}
DBUG_RETURN(1); // Did not die
}
#endif /* _WIN32 */
/* the wait-for-pos' default based value of 'timeout' must fit to MDEV-23511 */
compile_time_assert(default_wait_for_pos_timeout / 5 >= 60);
check_command_args(command, command->first_argument, shutdown_args,
sizeof(shutdown_args)/sizeof(struct command_arg), ' ');
if (ds_timeout.length)
{
char* endptr;
timeout= strtol(ds_timeout.str, &endptr, 10);
if (*endptr != '\0')
die("Illegal argument for timeout: '%s'", ds_timeout.str);
}
dynstr_free(&ds_timeout);
/* Get the servers pid_file name and use it to read pid */
if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1,
&ds_pidfile_name))
die("Failed to get pid_file from server");
/* Read the pid from the file */
{
int fd;
char buff[32];
if ((fd= my_open(ds_pidfile_name.str, O_RDONLY, MYF(0))) < 0)
die("Failed to open file '%s'", ds_pidfile_name.str);
dynstr_free(&ds_pidfile_name);
if (my_read(fd, (uchar*)&buff, sizeof(buff), MYF(0)) <= 0){
my_close(fd, MYF(0));
die("pid file was empty");
}
my_close(fd, MYF(0));
pid= atoi(buff);
if (pid == 0)
die("Pidfile didn't contain a valid number");
}
DBUG_PRINT("info", ("Got pid %d", pid));
/*
If timeout == 0, it means we should kill the server hard, without
any shutdown or core (SIGKILL)
If timeout is given, then we do things in the following order:
- mysql_shutdown()
- If server is not dead within timeout
- kill SIGABRT (to get a core)
- If server is not dead within new timeout
- kill SIGKILL
*/
if (!timeout || wait_until_dead(pid, timeout))
{
if (timeout)
(void) my_kill(pid, SIGABRT);
/* Give server a few seconds to die in all cases */
if (!timeout || wait_until_dead(pid, timeout < 5 ? 5 : timeout))
{
(void) my_kill(pid, SIGKILL);
wait_until_dead(pid, 5);
}
}
DBUG_VOID_RETURN;
}
/* List of error names to error codes */
typedef struct
{
const char *name;
uint code;
const char *text;
} st_error;
/* Loop through the array of known error names */
for (; e->name; e++)
{
/*
If we get a match, we need to check the length of the name we
matched against in case it was longer than what we are checking
(as in ER_WRONG_VALUE vs. ER_WRONG_VALUE_COUNT).
*/
if (!strncmp(error_name, e->name, (int) (error_end - error_name)) &&
(uint) strlen(e->name) == (uint) (error_end - error_name))
{
DBUG_RETURN(e->code);
}
}
DBUG_RETURN(0);
}
if (! error_code)
{
DBUG_RETURN("");
}
for (; e->name; e++)
{
if (e->code == error_code)
{
DBUG_RETURN(e->name);
}
}
/* Apparently, errors without known names may occur */
DBUG_RETURN(unknown_error);
}
if (!*command->first_argument)
die("Missing argument(s) to 'error'");
/* TODO: Potentially, there is a possibility of variables
being expanded twice, e.g.
let $errcodes = 1,\$a;
let $a = 1051;
error $errcodes;
DROP TABLE unknown_table;
...
Got one of the listed errors
But since it requires manual escaping, it does not seem
particularly dangerous or error-prone.
*/
DYNAMIC_STRING ds;
init_dynamic_string(&ds, 0, command->query_len + 64, 256);
do_eval(&ds, command->first_argument, command->end, !is_windows);
char *p= ds.str;
uint count= 0;
char *next;
do
{
char *end;
/* Skip leading spaces */
while (*p && *p == ' ') p++;
/* Find end */
end= p;
while (*end && *end != ',' && *end != ' ')
end++;
next=end;
/* code to handle variables passed to mysqltest */
if( *p == '$')
{
const char* fin; VAR *var = var_get(p,&fin,0,0); p=var->str_val;
end=p+var->str_val_len;
}
if (*p == 'S')
{
char *to_ptr= to->code.sqlstate;
/*
SQLSTATE string
- Must be SQLSTATE_LENGTH long
- May contain only digits[0-9] and _uppercase_ letters
*/ p++; /* Step past the S */
if ((end - p) != SQLSTATE_LENGTH)
die("The sqlstate must be exactly %d chars long", SQLSTATE_LENGTH);
/* Check sqlstate string validity */
while (*p && p < end)
{
if (my_isdigit(charset_info, *p) || my_isupper(charset_info, *p))
*to_ptr++= *p++;
else
die("The sqlstate may only consist of digits[0-9] " \ "and _uppercase_ letters");
}
*to_ptr= 0;
to->type= ERR_SQLSTATE;
DBUG_PRINT("info", ("ERR_SQLSTATE: %s", to->code.sqlstate));
}
else if (*p == 's')
{
die("The sqlstate definition must start with an uppercase S");
}
else if (*p == 'E' || *p == 'W' || *p == 'H')
{
/* Error name string */
DBUG_PRINT("info", ("Error name: %s", p));
to->code.errnum= get_errcode_from_name(p, end);
to->type= ERR_ERRNO;
DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum));
}
else if (*p == 'e' || *p == 'w' || *p == 'h')
{
die("The error name definition must start with an uppercase E or W or H");
}
else
{
uint val;
char *start= p;
/* Check that the string passed to str2int only contain digits */
while (*p && p != end)
{
if (!my_isdigit(charset_info, *p))
die("Invalid argument to error: '%s' - " \ "the errno may only consist of digits[0-9]", command->first_argument); p++;
}
/* Convert the string to int */
if (!str2int(start, p - start, 10, &val))
die("Invalid argument to error: '%s'", command->first_argument);
/* Find separator */
if (*from == '"' || *from == '\'')
sep= *from++;
else
sep=' '; /* Separated with space */
for ( ; (c=*from) ; from++)
{
if (c == '\\' && from[1])
{ /* Escaped character */
/* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */
switch (*++from) {
case 'n':
*to++= '\n';
break;
case 't':
*to++= '\t';
break;
case 'r':
*to++ = '\r';
break;
case 'b':
*to++ = '\b';
break;
case 'Z': /* ^Z must be escaped on Win32 */
*to++='\032';
break;
default:
*to++ = *from;
break;
}
}
else if (c == sep)
{
if (c == ' ' || c != *++from)
break; /* Found end of string */
*to++=c; /* Copy duplicated separator */
}
else
*to++=c;
}
if (*from != ' ' && *from)
die("Wrong string argument in %s", command->query);
while (my_isspace(charset_info,*from)) /* Point to next string */
from++;
*to =0; /* End of string marker */
*to_ptr= to+1; /* Store pointer to end */
*from_ptr= from;
/* Check if this was a variable */
if (*start == '$')
{
const char *end= to; VAR *var=var_get(start, &end, 0, 1);
if (var && to == (char*) end+1)
{
DBUG_PRINT("info",("var: '%s' -> '%s'", start, var->str_val));
DBUG_RETURN(var->str_val); /* return found variable value */
}
}
DBUG_RETURN(start);
}
/**
Change the current connection to the given st_connection, and update
$mysql_get_server_version and $CURRENT_CONNECTION accordingly.
*/
void set_current_connection(struct st_connection *con)
{
cur_con= con;
/* Update $mysql_get_server_version to that of current connection */
var_set_int("$mysql_get_server_version",
mysql_get_server_version(con->mysql));
/* Update $CURRENT_CONNECTION to the name of the current connection */
var_set_string("$CURRENT_CONNECTION", con->name);
}
if (!(con= find_connection_by_name(ds_connection.str)))
die("connection '%s' not found in connection pool", ds_connection.str);
DBUG_PRINT("info", ("Closing connection %s", con->name));
#ifndef EMBEDDED_LIBRARY
if (command->type == Q_DIRTY_CLOSE)
mariadb_cancel(con->mysql);
else
{
simple_command(con->mysql,COM_QUIT,0,0,0);
if (con->util_mysql)
simple_command(con->util_mysql,COM_QUIT,0,0,0);
}
#endif /*!EMBEDDED_LIBRARY*/
if (con->stmt)
do_stmt_close(con);
#ifdef EMBEDDED_LIBRARY
/*
As query could be still executed in a separate thread
we need to check if the query's thread was finished and probably wait
(embedded-server specific)
*/
emb_close_connection(con);
#endif /*EMBEDDED_LIBRARY*/
mysql_close(con->mysql);
con->mysql= 0;
if (con->util_mysql)
mysql_close(con->util_mysql);
con->util_mysql= 0;
con->pending= FALSE;
my_free(con->name);
/*
When the connection is closed set name to CLOSED_CONNECTION
to make it possible to reuse the connection name.
*/
if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, CLOSED_CONNECTION,
MYF(MY_WME))))
die("Out of memory");
con->name_len= sizeof(CLOSED_CONNECTION)-1;
if (con == cur_con)
{
/* Current connection was closed */
var_set_int("$mysql_get_server_version", 0xFFFFFFFF);
var_set_string("$CURRENT_CONNECTION", con->name);
}
/* Connection logging if enabled */
if (!disable_connect_log && !disable_query_log)
{
DYNAMIC_STRING *ds= &ds_res;
/*
Connect to a server doing several retries if needed.
SYNOPSIS
safe_connect()
con - connection structure to be used
host, user, pass, - connection parameters
db, port, sock
NOTE
Sometimes in a test the client starts before
the server - to solve the problem, we try again
after some sleep if connection fails the first
time
This function will try to connect to the given server "opt_max_connect_retries" times and sleep "connection_retry_sleep"
seconds between attempts before finally giving up.
This helps in situation when the client starts
before the server (which happens sometimes).
It will only ignore connection errors during these retries.
Only allow retry if this was an error indicating the server
could not be contacted. Error code differs depending
on protocol/connection type
*/
if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR ||
mysql_errno(mysql) == CR_CONNECTION_ERROR) &&
failed_attempts < opt_max_connect_retries)
{
verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts,
opt_max_connect_retries, mysql_errno(mysql),
mysql_error(mysql));
my_sleep(connection_retry_sleep);
}
else
{
if (failed_attempts > 0)
die("Could not open connection '%s' after %d attempts: %d %s", name,
failed_attempts, mysql_errno(mysql), mysql_error(mysql));
else
die("Could not open connection '%s': %d %s", name,
mysql_errno(mysql), mysql_error(mysql));
}
failed_attempts++;
}
verbose_msg("... Connected.");
DBUG_VOID_RETURN;
}
/*
Connect to a server and handle connection errors in case they occur.
SYNOPSIS
connect_n_handle_errors()
q - context of connect "query" (command)
con - connection structure to be used
host, user, pass, - connection parameters
db, port, sock
default_db - 0 if db was explicitly passed
DESCRIPTION
This function will try to establish a connection to server and handle
possible errors in the same manner as if "connect" was usual SQL-statement
(If error is expected it will ignore it once it occurs and log the "statement" to the query log).
Unlike safe_connect() it won't do several attempts.
/* Only log if an error is expected */
if (command->expected_errors.count > 0 &&
!disable_query_log)
{
/*
Log the connect to result log
*/
dynstr_append_mem(ds, "connect(", 8);
replace_dynstr_append(ds, host);
dynstr_append_mem(ds, ",", 1);
replace_dynstr_append(ds, user);
dynstr_append_mem(ds, ",", 1);
replace_dynstr_append(ds, pass);
dynstr_append_mem(ds, ",", 1);
if (db)
replace_dynstr_append(ds, db);
dynstr_append_mem(ds, ",", 1);
replace_dynstr_append_uint(ds, port);
dynstr_append_mem(ds, ",", 1);
if (sock)
replace_dynstr_append(ds, sock);
dynstr_append_mem(ds, ")", 1);
dynstr_append_mem(ds, delimiter, delimiter_length);
dynstr_append_mem(ds, "\n", 1);
}
/* Simplified logging if enabled */
if (!disable_connect_log && !disable_query_log)
{
replace_dynstr_append(ds, command->query);
dynstr_append_mem(ds, ";\n", 2);
}
mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest");
while (!mysql_real_connect(con, host, user, pass,
(default_db ? "" : db),
port, (sock ? sock : 0),
CLIENT_MULTI_STATEMENTS))
{
/*
If we have used up all our connections check whether this
is expected (by --error). If so, handle the error right away.
Otherwise, give it some extra time to rule out race-conditions.
If extra-time doesn't help, we have an unexpected error and
must abort -- just proceeding to handle_error() when second
and third chances are used up will handle that for us.
There are various user-limits of which only max_user_connections
and max_connections_per_hour apply at connect time. For the
the second to create a race in our logic, we'd need a limits
test that runs without a FLUSH for longer than an hour, so we'll
stay clear of trying to work out which exact user-limit was
exceeded.
*/
auto my_err= mysql_errno(con);
if(my_err == 0)
{
/* Workaround client library bug, not indicating connection error. */
my_err= CR_SERVER_LOST;
}
if (((my_err == ER_TOO_MANY_USER_CONNECTIONS) ||
(my_err == ER_USER_LIMIT_REACHED)) &&
(failed_attempts++ < opt_max_connect_retries))
{
int i;
<name> - name of the new connection
<host> - hostname of server
<user> - user to connect as
<pass> - password used when connecting
<db> - initial db when connected
<port> - server port
<sock> - server socket
<opts> - options to use for the connection
* SSL - use SSL if available
* COMPRESS - use compression if available
* PIPE - use named pipe if available
/* Port */
if (ds_port.length)
{
con_port= atoi(ds_port.str);
if (con_port == 0)
die("Illegal argument for port: '%s'", ds_port.str);
}
/* Sock */
if (ds_sock.length)
{
/*
If the socket is specified just as a name without path
or an abstract socket indicator ('@'), then
append tmpdir in front
*/
if (*ds_sock.str != FN_LIBCHAR && *ds_sock.str != '@')
{
char buff[FN_REFLEN];
fn_format(buff, ds_sock.str, TMPDIR, "", 0);
dynstr_set(&ds_sock, buff);
}
}
else
{
/* No socket specified, use default */
dynstr_set(&ds_sock, unix_sock);
}
DBUG_PRINT("info", ("socket: %s", ds_sock.str));
/* Options */
con_options= ds_options.str;
while (*con_options)
{
size_t length;
char *end;
/* Step past any spaces in beginning of option*/
while (*con_options && my_isspace(charset_info, *con_options))
con_options++;
/* Find end of this option */
end= con_options;
while (*end && !my_isspace(charset_info, *end))
end++;
length= (size_t) (end - con_options);
if (length == 3 && !strncmp(con_options, STRING_WITH_LEN("SSL")))
con_ssl= USE_SSL_REQUIRED;
else if (length == 5 && !strncmp(con_options, STRING_WITH_LEN("NOSSL")))
con_ssl= USE_SSL_FORBIDDEN;
else if (!strncmp(con_options, "SSL-CIPHER=", 11))
{
con_ssl= USE_SSL_REQUIRED;
ssl_cipher=con_options + 11;
}
else if (length == 8 && !strncmp(con_options, STRING_WITH_LEN("COMPRESS")))
con_compress= 1;
else if (length == 3 && !strncmp(con_options, STRING_WITH_LEN("TCP")))
protocol= MYSQL_PROTOCOL_TCP;
else if (length == 7 && !strncmp(con_options, STRING_WITH_LEN("DEFAULT")))
protocol= MYSQL_PROTOCOL_DEFAULT;
else if (length == 4 && !strncmp(con_options, STRING_WITH_LEN("PIPE")))
{
#ifdef _WIN32
protocol= MYSQL_PROTOCOL_PIPE;
#endif
}
else if (length == 6 && !strncmp(con_options, STRING_WITH_LEN("SOCKET")))
{
#ifndef _WIN32
protocol= MYSQL_PROTOCOL_SOCKET;
#endif
}
else if (length == 6 && !strncmp(con_options, STRING_WITH_LEN("MEMORY")))
{
#ifdef _WIN32
protocol= MYSQL_PROTOCOL_MEMORY;
#endif
}
else if (strncasecmp(con_options, STRING_WITH_LEN("read_timeout=")) == 0)
{
read_timeout= atoi(con_options + sizeof("read_timeout=")-1);
}
else if (strncasecmp(con_options, STRING_WITH_LEN("write_timeout=")) == 0)
{
write_timeout= atoi(con_options + sizeof("write_timeout=")-1);
}
else if (strncasecmp(con_options, STRING_WITH_LEN("connect_timeout=")) == 0)
{
connect_timeout= atoi(con_options + sizeof("connect_timeout=")-1);
}
else if (strncasecmp(con_options, STRING_WITH_LEN("CHARSET=")) == 0)
{
csname= strdup(con_options + sizeof("CHARSET=") - 1);
}
else if (strncasecmp(con_options, STRING_WITH_LEN("auth=")) == 0)
{
rauth= strdup(con_options + sizeof("auth=") - 1);
}
else
die("Illegal option to connect: %.*sB",
(int) (end - con_options), con_options);
/* Process next option */
con_options= end;
}
if (find_connection_by_name(ds_connection_name.str))
die("Connection %s already exists", ds_connection_name.str);
if (next_con != connections_end)
con_slot= next_con;
else
{
if (!(con_slot= find_connection_by_name(CLOSED_CONNECTION)))
die("Connection limit exhausted, you can have max %d connections",
opt_max_connections);
my_free(con_slot->name);
con_slot->name= 0;
}
init_connection_thd(con_slot);
if (!(con_slot->mysql= mysql_init(0)))
die("Failed on mysql_init()");
if (opt_connect_timeout)
mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
(void *) &opt_connect_timeout);
if (mysql_options(con_slot->mysql, MYSQL_OPT_NONBLOCK, 0))
die("Failed to initialise non-blocking API");
if (opt_compress || con_compress)
mysql_options(con_slot->mysql, MYSQL_OPT_COMPRESS, NullS);
mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_NAME,
csname ? csname : charset_info->cs_name.str);
if (opt_charsets_dir)
mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_DIR,
opt_charsets_dir);
#ifndef EMBEDDED_LIBRARY
if (rauth)
mysql_options(con_slot->mysql, MARIADB_OPT_RESTRICTED_AUTH, rauth);
if (protocol)
mysql_options(con_slot->mysql, MYSQL_OPT_PROTOCOL, (char*) &protocol);
if (read_timeout)
{
mysql_options(con_slot->mysql, MYSQL_OPT_READ_TIMEOUT,
(char*)&read_timeout);
}
if (write_timeout)
{
mysql_options(con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT,
(char*)&write_timeout);
}
if (connect_timeout)
{
mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
(char*)&connect_timeout);
}
/* Use default db name */
if (ds_database.length == 0)
{
dynstr_set(&ds_database, opt_db);
default_db= 1;
}
else
default_db= 0;
if (opt_plugin_dir && *opt_plugin_dir)
mysql_options(con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);
if (ds_default_auth.length)
mysql_options(con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str);
/* Special database to allow one to connect without a database name */
if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*"))
dynstr_set(&ds_database, "");
if (connect_n_handle_errors(command, con_slot->mysql,
ds_host.str,ds_user.str,
ds_password.str, ds_database.str,
con_port, ds_sock.str, default_db))
{
DBUG_PRINT("info", ("Inserting connection %s in connection pool",
ds_connection_name.str));
if (!(con_slot->name= my_strdup(PSI_NOT_INSTRUMENTED, ds_connection_name.str, MYF(MY_WME))))
die("Out of memory");
con_slot->name_len= strlen(con_slot->name);
set_current_connection(con_slot);
if (con_slot == next_con)
next_con++; /* if we used the next_con slot, advance the pointer */
}
else // Failed to connect. Free the memory.
{
mysql_close(con_slot->mysql);
con_slot->mysql= NULL;
}
int do_done(struct st_command *command)
{
/* Check if empty block stack */
if (cur_block == block_stack)
{
if (*command->query != '}')
die("Stray 'end' command - end of block before beginning");
die("Stray '}' - end of block before beginning");
}
/* Test if inner block has been executed */
if (cur_block->ok && cur_block->cmd == cmd_while)
{
/* Pop block from stack, re-execute outer block */
cur_block--;
parser.current_line = cur_block->line;
}
else
{
if (*cur_block->delim)
{
/* Restore "old" delimiter after false if block */
if (safe_strcpy_truncated(delimiter, sizeof delimiter, cur_block->delim))
die("Delimiter too long, truncated");
delimiter_length= strlen(delimiter);
}
/* Pop block from stack, goto next line */
cur_block--;
parser.current_line++;
}
return 0;
}
/* Operands available in if or while conditions */
if (first == '=' && next == '=')
return EQ_OP;
if (first == '!' && next == '=')
return NE_OP;
if (first == '>' && next == '=')
return GE_OP;
if (first == '>')
return GT_OP;
if (first == '<' && next == '=')
return LE_OP;
if (first == '<')
return LT_OP;
return ILLEG_OP;
}
/*
do_break
DESCRIPTION
Instruction to stop execution of the current loop
*/
void do_break(struct st_command* command)
{
int depth= 0;
cur_block->ok= false;
/* Disable every outer block until while found or block stack ends */
while (cur_block->cmd != cmd_while && cur_block > block_stack)
{
cur_block--;
cur_block->ok= false;
depth++;
}
/* Check if the top block is not 'while' */
if (cur_block->cmd != cmd_while)
{
die("Stray break was found");
}
/* Set current block back */
cur_block+= depth;
}
/*
Process start of a "if" or "while" statement
SYNOPSIS
do_block()
cmd Type of block
q called command
DESCRIPTION
if ([!]<expr>)
{
<block statements>
}
while ([!]<expr>)
{
<block statements>
}
Evaluates the <expr> and if it evaluates to
greater than zero executes the following code block.
A '!' can be used before the <expr> to indicate it should
be executed if it evaluates to zero.
<expr> can also be a simple comparison condition:
<variable> <op> <expr>
The left hand side must be a variable, the right hand side can be a
variable, number, string or `query`. Operands are ==, !=, <, <=, >, >=.
== and != can be used for strings, all can be used for numerical values.
*/
/* Check stack overflow */
if (cur_block == block_stack_end)
die("Nesting too deeply");
/* Set way to find outer block again, increase line counter */
cur_block->line= parser.current_line++;
/* If this block is ignored */
if (!cur_block->ok)
{
/* Inner block should be ignored too */
cur_block++;
cur_block->cmd= cmd;
cur_block->ok= FALSE;
cur_block->delim[0]= '\0';
DBUG_VOID_RETURN;
}
/* Parse and evaluate test expression */
expr_start= strchr(p, '(');
if (!expr_start)
die("missing '(' in %s", cmd_name);
expr_start++;
while (my_isspace(charset_info, *expr_start))
expr_start++;
/* Check for !<expr> */
if (*expr_start == '!')
{
not_expr= TRUE;
expr_start++; /* Step past the '!', then any whitespace */
while (*expr_start && my_isspace(charset_info, *expr_start))
expr_start++;
}
/* Find ending ')' */
expr_end= strrchr(expr_start, ')');
if (!expr_end)
die("missing ')' in %s", cmd_name);
p= (char*)expr_end+1;
while (*p && my_isspace(charset_info, *p))
p++;
if (*p && *p != '{')
die("Missing '{' after %s. Found \"%s\"", cmd_name, p);
if (*expr_start == '$' && *(expr_start + 1) == '(')
{
const char *scanner= expr_start + 2;
int paren_level= 1;
char in_quote= 0; // Track quote state
bool escaped= false; // Track if the current character is escaped
while (*scanner && paren_level > 0)
{
if (!in_quote)
{
// Not inside quotes - handle parentheses normally
if (*scanner == '(') paren_level++;
else if (*scanner == ')') paren_level--;
else if (*scanner == '\'' || *scanner == '"')
in_quote= *scanner; // Start of quoted string
}
else
{
// Inside quotes - only look for closing quote
if (!escaped && *scanner == in_quote)
in_quote= 0; // End of quoted string
}
escaped= (!escaped && *scanner == '\\');
scanner++;
}
// if the $(...) is the entire condition, evaluate it
if (scanner == end_ptr)
{
DYNAMIC_STRING evaluated_expr;
init_dynamic_string(&evaluated_expr, "", 64, 256);
char* result_str= evaluated_expr.str;
while (*result_str && my_isspace(charset_info, *result_str))
result_str++;
/*
Setup the next block on the stack
This logic is borrowed from the end of this function
Any non-empty string which does not begin with 0 is TRUE
*/
cur_block++;
cur_block->cmd= cmd;
cur_block->ok= (*result_str && *result_str != '0');
if (not_expr)
cur_block->ok= !cur_block->ok;
if (cur_block->ok)
{
cur_block->delim[0]= '\0';
}
else
{
/* Remember "old" delimiter if entering a false if block */
if (safe_strcpy_truncated(cur_block->delim, sizeof cur_block->delim,
delimiter))
die("Delimiter too long, truncated");
}
DBUG_PRINT("info", ("OK: %d", cur_block->ok));
dynstr_free(&evaluated_expr);
DBUG_VOID_RETURN;
}
else
{
die("Expression evaluator $(...) must be the entire condition");
}
}
var_init(&v,0,0,0,0);
/* If expression starts with a variable, it may be a compare condition */
if (*expr_start == '$')
{
const char *curr_ptr= expr_end;
eval_expr(&v, expr_start, &curr_ptr, true);
while (my_isspace(charset_info, *++curr_ptr))
{}
/* If there was nothing past the variable, skip condition part */
if (curr_ptr == expr_end)
goto NO_COMPARE;
enum block_op operand= find_operand(curr_ptr);
if (operand == ILLEG_OP)
die("Found junk '%.*sB' after $variable in condition",
(int)(expr_end - curr_ptr), curr_ptr);
/* We could silently allow this, but may be confusing */
if (not_expr)
die("Negation and comparison should not be combined, please rewrite");
/* Skip the 1 or 2 chars of the operand, then white space */
if (operand == LT_OP || operand == GT_OP)
{
curr_ptr++;
}
else
{
curr_ptr+= 2;
}
while (my_isspace(charset_info, *curr_ptr))
curr_ptr++;
if (curr_ptr == expr_end)
die("Missing right operand in comparison");
/* Strip off trailing white space */
while (my_isspace(charset_info, expr_end[-1]))
expr_end--;
/* strip off ' or " around the string */
if (*curr_ptr == '\'' || *curr_ptr == '"')
{
if (expr_end[-1] != *curr_ptr)
die("Unterminated string value");
curr_ptr++;
expr_end--;
}
VAR v2;
var_init(&v2,0,0,0,0);
eval_expr(&v2, curr_ptr, &expr_end);
if ((operand!=EQ_OP && operand!=NE_OP) && ! (v.is_int && v2.is_int))
die("Only == and != are supported for string values");
/* Now we overwrite the first variable with 0 or 1 (for false or true) */
switch (operand)
{
case EQ_OP:
if (v.is_int)
v.int_val= (v2.is_int && v2.int_val == v.int_val);
else
v.int_val= !strcmp (v.str_val, v2.str_val);
break;
case NE_OP:
if (v.is_int)
v.int_val= ! (v2.is_int && v2.int_val == v.int_val);
else
v.int_val= (strcmp (v.str_val, v2.str_val) != 0);
break;
case LT_OP:
v.int_val= (v.int_val < v2.int_val);
break;
case LE_OP:
v.int_val= (v.int_val <= v2.int_val);
break;
case GT_OP:
v.int_val= (v.int_val > v2.int_val);
break;
case GE_OP:
v.int_val= (v.int_val >= v2.int_val);
break;
case ILLEG_OP:
die("Impossible operator, this cannot happen");
}
v.is_int= TRUE;
var_free(&v2);
} else
{
if (*expr_start != '`' && ! my_isdigit(charset_info, *expr_start))
die("Expression in if/while must begin with $, ` or a number");
eval_expr(&v, expr_start, &expr_end);
}
NO_COMPARE:
/* Define inner block */
cur_block++;
cur_block->cmd= cmd;
if (v.is_int)
{
cur_block->ok= (v.int_val != 0);
} else
/* Any non-empty string which does not begin with 0 is also TRUE */
{
p= v.str_val;
/* First skip any leading white space or unary -+ */
while (*p && ((my_isspace(charset_info, *p) || *p == '-' || *p == '+')))
p++;
if (cur_block->ok)
{
cur_block->delim[0]= '\0';
}
else
{
/* Remember "old" delimiter if entering a false if block */
if (safe_strcpy_truncated(cur_block->delim, sizeof cur_block->delim,
delimiter))
die("Delimiter too long, truncated");
}
DESCRIPTION
This function actually reads several lines and adds them to the
buffer buf. It continues to read until it finds what it believes
is a complete query.
Normally that means it will read lines until it reaches the "delimiter" that marks end of query. Default delimiter is ';'
The function should be smart enough not to detect delimiter's
found inside strings surrounded with '"' and '\'' escaped strings.
If the first line in a query starts with '#' or '-' this line is treated
as a comment. A comment is always terminated when end of line '\n' is
reached.
*p= 0;
start_lineno= cur_file->lineno;
DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno));
while (1)
{
if (p >= buf_end)
{
my_ptrdiff_t off= p - read_command_buf;
read_command_buf= (char*)my_realloc(PSI_NOT_INSTRUMENTED, read_command_buf,
read_command_buflen*2, MYF(MY_FAE));
p= read_command_buf + off;
read_command_buflen*= 2;
buf_end= read_command_buf + read_command_buflen - max_multibyte_length;
}
skip_char= 0;
c= my_getc(cur_file->file);
if (feof(cur_file->file))
{
found_eof:
if (cur_file->file != stdin)
{
fclose(cur_file->file);
cur_file->file= 0;
}
my_free(cur_file->file_name);
cur_file->file_name= 0;
if (cur_file == file_stack)
{
/* We're back at the first file, check if
all { have matching }
*/
if (cur_block != block_stack)
die("Missing end of block");
*p= 0;
DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno));
DBUG_RETURN(1);
}
cur_file--;
start_lineno= cur_file->lineno;
continue;
}
if (c == '\n')
{
/* Line counting is independent of state */
cur_file->lineno++;
/* Convert cr/lf to lf */
if (p != read_command_buf && *(p-1) == '\r')
p--;
}
bool drop_last_char= false;
switch(state) {
case R_NORMAL:
case R_HINT:
if (end_of_query(c))
{
*p= 0;
DBUG_PRINT("exit", ("Found delimiter '%s' at line %d",
delimiter, cur_file->lineno));
goto ret;
}
else if ((c == '{' &&
(!my_strnncoll_simple(charset_info, (const uchar*) "while", 5,
(uchar*) read_command_buf, MY_MIN(5, p - read_command_buf), 0) ||
!my_strnncoll_simple(charset_info, (const uchar*) "if", 2,
(uchar*) read_command_buf, MY_MIN(2, p - read_command_buf), 0))))
{
/* Only if and while commands can be terminated by { */
*p++= c;
*p= 0;
DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d",
cur_file->lineno));
goto ret;
}
else if (c == '\'' || c == '"' || c == '`')
{
if (! have_slash)
{
last_quote= c;
state= R_Q;
}
}
else if (c == '*' && last_char == '/')
{
state= R_CSTYLE_COMMENT;
break;
}
else if (c == '/' && last_char == '*') // Closing sequence `*/`
{
state= R_NORMAL;
// The hint is finished, and we don't want to interpret the current slash
// as an opener for a next hint or a C-style comment like it can happen
// for a statement like `SELECT /*+ BNL(t1) */* FROM t1` where there is
//no space between `*/` and `*`. So discard the current slash
drop_last_char= true;
}
have_slash= is_escape_char(c, last_quote);
break;
case R_CSTYLE_COMMENT:
if (c == '!' || c == '+')
{
// Got hint introducer '/*!' or '/*+'
state= R_HINT;
}
else if (c == '/' && last_char == '*')
{
state= R_NORMAL;
drop_last_char= true; // See comment for `drop_last_char` above
}
break;
case R_COMMENT:
if (c == '\n')
{
/* Comments are terminated by newline */
*p= 0;
DBUG_PRINT("exit", ("Found newline in comment at line: %d",
cur_file->lineno));
goto ret;
}
break;
case R_LINE_START:
if (c == '#' || c == '-')
{
/* A # or - in the first position of the line - this is a comment */
state = R_COMMENT;
} elseif (my_isspace(charset_info, c))
{ if (c == '\n')
{ if (last_char == '\n')
{ /* Two new lines in a row, return empty line */
DBUG_PRINT("info", ("Found two new lines in a row"));
*p++= c;
*p= 0; goto ret;
}
/* Query hasn't started yet */
start_lineno= cur_file->lineno;
DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d",
start_lineno));
}
/* Skip all space at beginning of line */
skip_char= 1;
} elseif (end_of_query(c))
{
*p= 0;
DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d",
delimiter, cur_file->lineno)); goto ret;
} elseif (c == '}')
{ /* A "}" need to be by itself in the beginning of a line to terminate */
*p++= c;
*p= 0;
DBUG_PRINT("exit", ("Found '}' in beginning of a line at line: %d",
cur_file->lineno)); goto ret;
} elseif (c == '\'' || c == '"' || c == '`')
{
last_quote= c;
state= R_Q;
} else
state= R_NORMAL; break;
case R_Q: if (c == last_quote)
state= R_NORMAL; elseif (is_escape_char(c, last_quote))
state= R_SLASH_IN_Q; break;
if (!skip_char)
{
*p++= c; if (charset_info->use_mb())
{ constchar *mb_start= p - 1; /* Could be a multibyte character */ /* See a similar code in "sql_load.cc" */ for ( ; p < buf_end; )
{ int charlen= charset_info->charlen(mb_start, p); if (charlen > 0) break; /* Full character */ if (MY_CS_IS_TOOSMALL(charlen))
{ /* We give up if multibyte character is started but not */ /* completed before we pass buf_end */
c= my_getc(cur_file->file); if (feof(cur_file->file)) goto found_eof;
*p++ = c; continue;
}
DBUG_ASSERT(charlen == MY_CS_ILSEQ); /* It was not a multiline char, push back the characters */ /* We leave first 'c', i.e. pretend it was a normal char */ while (p - 1 > mb_start)
my_ungetc(*--p); break;
}
}
}
}
ret:
read_command_buf_end= p;
DBUG_RETURN(0);
}
/* Skip past all spacing chars and comments */ while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n'))
{ /* Skip past comments started with # and ended with newline */ if (*p && *p == '#')
{
p++; while (*p && *p != '\n')
p++;
}
/* Check this line */ if (*p && *p == '\n')
check_eol_junk_line(p);
/* Skip spaces between command and first argument */ while (p < end && my_isspace(charset_info, *p))
p++;
command->first_argument= p;
parser.read_lines++;
DBUG_RETURN(0);
}
static struct my_option my_long_options[] =
{
{"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"basedir", 'b', "Basedir for tests.", &opt_basedir,
&opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"character-sets-dir", 0, "Directory for character set files.", &opt_charsets_dir,
&opt_charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"compress", 'C', "Use the compressed server/client protocol.",
&opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"continue-on-error", 0, "Continue test even if we got an error. " "This is mostly useful when testing a storage engine to see what from a test file it can execute, " "or to find all syntax errors in a newly created big test file",
&opt_continue_on_error, &opt_continue_on_error, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"cursor-protocol", 0, "Use cursors for prepared statements.",
&cursor_protocol, &cursor_protocol, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"database", 'D', "Database to use.", &opt_db, &opt_db, 0,
GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
#ifdef DBUG_OFF
{"debug", '#', "This is a non-debug version. Catch this and exit", 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
#else
{"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
#endif
{"debug-check", 0, "Check memory and open file usage at exit.",
&debug_check_flag, &debug_check_flag, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"debug-info", 0, "Print some debug info at exit.",
&debug_info_flag, &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"host", 'h', "Connect to host.", &opt_host, &opt_host, 0,
GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"prologue", 0, "Include SQL before each test case.", &opt_prologue,
&opt_prologue, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"logdir", OPT_LOG_DIR, "Directory for log files", &opt_logdir,
&opt_logdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"mark-progress", 0, "Write line number and elapsed time to <testname>.progress.",
&opt_mark_progress, &opt_mark_progress, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"max-connect-retries", 0, "Maximum number of attempts to connect to server.",
&opt_max_connect_retries, &opt_max_connect_retries, 0,
GET_INT, REQUIRED_ARG, 500, 1, 10000, 0, 0, 0},
{"max-connections", 0, "Max number of open connections to server",
&opt_max_connections, &opt_max_connections, 0,
GET_INT, REQUIRED_ARG, DEFAULT_MAX_CONN, 8, 5120, 0, 0, 0},
{"password", 'p', "Password to use when connecting to server.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
{"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"port", 'P', "Port number to use for connection or 0 for default to, in " "order of preference, my.cnf, $MYSQL_TCP_PORT, "
#if MYSQL_PORT_DEFAULT == 0 "/etc/services, "
#endif "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
&opt_port, &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"ps-protocol", 0, "Use prepared-statement protocol for communication.",
&ps_protocol, &ps_protocol, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"non-blocking-api", 0, "Use the non-blocking client API for communication.",
&non_blocking_api_enabled, &non_blocking_api_enabled, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"quiet", 's', "Suppress all normal output.", &silent,
&silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"record", 'r', "Record output of test_file into result file.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"result-file", 'R', "Read/store result from/in this file.",
&result_file_name, &result_file_name, 0,
GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"result-format-version", OPT_RESULT_FORMAT_VERSION, "Version of the result file format to use",
&opt_result_format_version,
&opt_result_format_version, 0,
GET_INT, REQUIRED_ARG, 1, 1, 2, 0, 0, 0},
{"server-arg", 'A', "Send option value to embedded server as a parameter.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"server-file", 'F', "Read embedded server arguments from file.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"silent", 's', "Suppress all normal output. Synonym for --quiet.",
&silent, &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"sleep", 'T', "Always sleep this many seconds on sleep commands.",
&opt_sleep, &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, -1, 0, 0, 0, 0},
{"socket", 'S', "The socket file to use for connection.",
&unix_sock, &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"sp-protocol", 0, "Use stored procedures for select.",
&sp_protocol, &sp_protocol, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
#include "sslopt-longopts.h"
{"tail-lines", 0, "Number of lines of the result to include in a failure report.",
&opt_tail_lines, &opt_tail_lines, 0,
GET_INT, REQUIRED_ARG, 0, 0, 10000, 0, 0, 0},
{"test-file", 'x', "Read test from/in this file (default stdin).", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"timer-file", 'm', "File where the timing in microseconds is stored.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"tmpdir", 't', "Temporary directory where sockets are put.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"user", 'u', "User for login.", &opt_user, &opt_user, 0,
GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"verbose", 'v', "Write more.", &verbose, &verbose, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"view-protocol", 0, "Use views for select.",
&view_protocol, &view_protocol, 0,
GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
{"connect_timeout", 0, "Number of seconds before connection timeout.",
&opt_connect_timeout, &opt_connect_timeout, 0, GET_UINT, REQUIRED_ARG, 120, 0, 3600 * 12, 0, 0, 0},
{"wait_for_pos_timeout", 0, "Number of seconds to wait for master_pos_wait",
&opt_wait_for_pos_timeout, &opt_wait_for_pos_timeout, 0, GET_UINT,
REQUIRED_ARG, default_wait_for_pos_timeout, 0, 3600 * 12, 0, 0, 0},
{"plugin_dir", 0, "Directory for client-side plugins.",
&opt_plugin_dir, &opt_plugin_dir, 0,
GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"overlay-dir", 0, "Overlay directory.", &opt_overlay_dir,
&opt_overlay_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"suite-dir", 0, "Suite directory.", &opt_suite_dir,
&opt_suite_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};
void usage()
{
print_version();
puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
printf("Runs a test against the MariaDB server and compares output with a results file.\n\n");
printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname);
print_defaults("my",load_default_groups);
puts("");
my_print_help(my_long_options);
my_print_variables(my_long_options);
}
if (!record)
{
/* Check that the result file exists */
if (result_file_name && access(result_file_name, F_OK) != 0)
die("The specified result file '%s' does not exist",
result_file_name);
}
return 0;
}
/*
Write the content of str into file
SYNOPSIS
str_to_file2
fname - name of file to truncate/create and write to
str - content to write to file
size - size of content witten to file
append - append to file instead of overwriting old file
*/
void check_regerr(regex_t* r, int err)
{
char err_buf[1024];
if (err)
{
regerror(err,r,err_buf,sizeof(err_buf));
die("Regex error: %s\n", err_buf);
}
}
#ifdef _WIN32
DYNAMIC_ARRAY patterns;
/*
init_win_path_patterns
DESCRIPTION
Setup string patterns that will be used to detect filenames that
needs to be converted from Win to Unix format
*/
void init_win_path_patterns()
{
/* List of string patterns to match in order to find paths */
const char* paths[] = { "$MYSQL_TEST_DIR",
"$MYSQL_TMP_DIR",
"$MYSQLTEST_VARDIR",
"$MASTER_MYSOCK",
"$MYSQL_SHAREDIR",
"$MYSQL_LIBDIR",
"./test/" };
int num_paths= sizeof(paths)/sizeof(char*);
int i;
char* p;
/* Loop through all paths in the array */
for (i= 0; i < num_paths; i++)
{
VAR* v;
if (*(paths[i]) == '$')
{
v= var_get(paths[i], 0, 0, 0);
p= my_strdup(PSI_NOT_INSTRUMENTED, v->str_val, MYF(MY_FAE));
}
else
p= my_strdup(PSI_NOT_INSTRUMENTED, paths[i], MYF(MY_FAE));
/* Don't insert zero length strings in patterns array */
if (strlen(p) == 0)
{
my_free(p);
continue;
}
if (insert_dynamic(&patterns, &p))
die("Out of memory");
DBUG_PRINT("info", ("p: %s", p));
while (*p)
{
if (*p == '/')
*p='\\';
p++;
}
}
DBUG_VOID_RETURN;
}
void free_win_path_patterns()
{
uint i= 0;
for (i=0 ; i < patterns.elements ; i++)
{
const char** pattern= dynamic_element(&patterns, i, const char**);
my_free((void *) *pattern);
}
delete_dynamic(&patterns);
}
#endif
/*
fix_win_paths
DESCRIPTION
Search the string 'val' for the patterns that are known to be
strings that contain filenames. Convert all \ to / in the
filenames that are found.
Ex:
val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist'
=> $MYSQL_TEST_DIR is found by strstr
=> all \ from c:\mysql\m... until next space is converted into /
*/
if (display_result_sorted)
{
init_dynamic_string(&rs_unsorted, "", 1024, 1024);
rs= &rs_unsorted;
}
while ((row = mysql_fetch_row(res)))
{
uint i;
lengths = mysql_fetch_lengths(res);
for (i = 0; i < num_fields; i++)
append_field(rs, i, &fields[i],
row[i], lengths[i], !row[i]);
if (!display_result_vertically)
dynstr_append_mem(rs, "\n", 1);
}
if (display_result_sorted)
{
dynstr_append_sorted(ds, &rs_unsorted);
dynstr_free(&rs_unsorted);
}
}
/*
Append all results from ps execution to the dynamic string separated
with '\t'. Values may be converted with 'replace_column'
*/
/* Allocate data for the result of each field */
for (i= 0; i < num_fields; i++)
{
uint max_length= fields[i].max_length + 1;
my_bind[i].buffer_type= MYSQL_TYPE_STRING;
my_bind[i].buffer= my_malloc(PSI_NOT_INSTRUMENTED, max_length,
MYF(MY_WME|MY_FAE));
my_bind[i].buffer_length= max_length;
my_bind[i].is_null= &is_null[i];
my_bind[i].length= &length[i];
DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu",
i, my_bind[i].buffer_type, my_bind[i].buffer_length));
}
if (mysql_stmt_bind_result(stmt, my_bind))
die("mysql_stmt_bind_result failed: %d: %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
while ((error=mysql_stmt_fetch(stmt)) == 0)
{
for (i= 0; i < num_fields; i++)
append_field(rs, i, &fields[i], (char*)my_bind[i].buffer,
*my_bind[i].length, *my_bind[i].is_null);
if (!display_result_vertically)
dynstr_append_mem(rs, "\n", 1);
}
if (error != MYSQL_NO_DATA)
die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: "
"error: %d", error);
if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA)
die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
for (i= 0; i < num_fields; i++)
{
/* Free data for output */
my_free(my_bind[i].buffer);
}
/* Free array with bind structs, lengths and NULL flags */
my_free(my_bind);
my_free(length);
my_free(is_null);
if (display_result_sorted)
{
dynstr_append_sorted(ds, &rs_unsorted);
dynstr_free(&rs_unsorted);
}
}
/**
@brief Append state change information (received through Ok packet) to the output.
@details The appended string is lines prefixed with "-- ". Only
tracking types with info sent from the server are displayed. For
each tracking type, the first line is the type name e.g.
"-- Tracker : SESSION_TRACK_SYSTEM_VARIABLES".
The subsequent lines are the actual tracking info. When type is
SESSION_TRACK_SYSTEM_VARIABLES, the actual tracking info is a list
of name-value pairs of lines, sorted by name, e.g. if the info
received from the server is "autocommit=ON;time_zone=SYSTEM", the
corresponding string is
-- autocommit: ON
-- time_zone: SYSTEM
@param [in,out] ds Dynamic string to hold the content to be printed.
@param [in] mysql Connection handle.
*/
static void append_session_track_info(DYNAMIC_STRING *ds, MYSQL *mysql)
{
if (!(mysql->server_status & SERVER_SESSION_STATE_CHANGED))
return;
#ifndef EMBEDDED_LIBRARY
DYNAMIC_STRING ds_sort, *ds_type= NULL;
for (unsigned int type= SESSION_TRACK_BEGIN; type <= SESSION_TRACK_END; type++)
{
const char *data;
size_t data_length;
/*
Append the tracking type line, if any corresponding tracking
info is received.
*/
if (!mysql_session_track_get_first(mysql,
(enum_session_state_type) type,
&data, &data_length))
{
dynstr_append_mem(ds, STRING_WITH_LEN("-- "));
if (type <= SESSION_TRACK_END)
{
dynstr_append_mem(ds, trking_info_desc[type],
strlen(trking_info_desc[type]));
}
else
{
DBUG_ASSERT(0);
dynstr_append_mem(ds, STRING_WITH_LEN("Tracker???\n"));
}
}
else
continue;
/*
The remaining of this function: format and append the actual
tracking info.
*/
if (type == SESSION_TRACK_SYSTEM_VARIABLES)
{
/* Prepare a string to be sorted before being appended. */
if (init_dynamic_string(&ds_sort, "", 1024, 1024))
die("Out of memory");
ds_type= &ds_sort;
}
else
ds_type= ds;
/* Append the first piece of info */
dynstr_append_mem(ds_type, STRING_WITH_LEN("-- "));
dynstr_append_mem(ds_type, data, data_length);
/* Whether we are appending the value of a variable */
bool appending_value= type == SESSION_TRACK_SYSTEM_VARIABLES;
/* Append remaining pieces */
while (!mysql_session_track_get_next(mysql,
(enum_session_state_type) type,
&data, &data_length))
{
if (appending_value)
dynstr_append_mem(ds_type, STRING_WITH_LEN(": "));
else
dynstr_append_mem(ds_type, STRING_WITH_LEN("\n-- "));
appending_value= !appending_value && type == SESSION_TRACK_SYSTEM_VARIABLES;
if (data == NULL)
{
DBUG_ASSERT(data_length == 0);
dynstr_append_mem(ds_type, STRING_WITH_LEN("<NULL>"));
}
else
dynstr_append_mem(ds_type, data, data_length);
}
DBUG_ASSERT(!appending_value);
if (type == SESSION_TRACK_SYSTEM_VARIABLES)
{
dynstr_append_mem(ds_type, STRING_WITH_LEN("\n"));
dynstr_append_sorted(ds, ds_type);
dynstr_append_mem(ds, STRING_WITH_LEN("\n"));
dynstr_free(&ds_sort);
}
else
dynstr_append_mem(ds, STRING_WITH_LEN("\n\n"));
}
#endif /* EMBEDDED_LIBRARY */
}
/* Display the table headings with the names tab separated
*/
if (!(count= mysql_warning_count(mysql)))
DBUG_RETURN(0);
DBUG_PRINT("info", ("Warnings: %ud", count));
/*
If one day we will support execution of multi-statements
through PS API we should not issue SHOW WARNINGS until
we have not read all results...
*/
DBUG_ASSERT(!mysql_more_results(mysql));
SYNOPSIS
run_query_normal()
mysql mysql handle
command current command pointer
flags flags indicating if we should SEND and/or REAP
query query string to execute
query_len length query string to execute
ds output buffer where to store result form query
*/
if (flags & QUERY_SEND_FLAG)
{
/*
Send the query
*/
if (do_send_query(cn, query, query_len))
{
handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), ds);
goto end;
}
}
if (!(flags & QUERY_REAP_FLAG))
{
cn->pending= TRUE;
DBUG_VOID_RETURN;
}
do
{
/*
When on first result set, call mysql_read_query_result to retrieve
answer to the query sent earlier
*/
if ((counter==0) && do_read_query_result(cn))
{
/* we've failed to collect the result set */
cn->pending= TRUE;
handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), ds);
goto end;
}
/*
Store the result of the query if it will return any fields
*/
if (mysql_field_count(mysql) && ((res= mysql_store_result(mysql)) == 0))
{
handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), ds);
goto end;
}
if (!disable_result_log)
{
if (res)
{
MYSQL_FIELD *fields= mysql_fetch_fields(res);
uint num_fields= mysql_num_fields(res);
if (display_metadata)
append_metadata(ds, fields, num_fields);
if (!display_result_vertically)
append_table_headings(ds, fields, num_fields);
append_result(ds, res);
}
/*
Need to call mysql_affected_rows() before the "new"
query to find the warnings.
*/
if (!disable_info)
append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql));
if (display_session_track_info)
append_session_track_info(ds, mysql);
/*
Add all warnings to the result. We can't do this if we are in
the middle of processing results from multi-statement, because
this will break protocol.
*/
if (!disable_warnings && !mysql_more_results(mysql))
{
if (append_warnings(ds_warnings, mysql) || ds_warnings->length)
{
dynstr_append_mem(ds, "Warnings:\n", 10);
dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length);
}
}
}
if (res)
{
mysql_free_result(res);
res= 0;
}
counter++;
} while (!(err= mysql_next_result(mysql)));
if (err > 0)
{
/* We got an error from mysql_next_result, maybe expected */
handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), ds);
goto end;
}
DBUG_ASSERT(err == -1); /* Successful and there are no more results */
/* If we come here the query is both executed and read successfully */
handle_no_error(command);
display_optimizer_trace(cn, ds);
revert_properties();
end:
cn->pending= FALSE;
/*
We save the return code (mysql_errno(mysql)) from the last call sent
to the server into the mysqltest builtin variable $mysql_errno. This
variable then can be used from the test case itself.
*/
var_set_errno(mysql_errno(mysql));
DBUG_VOID_RETURN;
}
/*
Check whether given error is in list of expected errors
SYNOPSIS
match_expected_error()
PARAMETERS
command the current command (and its expect-list)
err_errno error number of the error that actually occurred
err_sqlstate SQL-state that was thrown, or NULL for impossible
(file-ops, diff, etc.)
RETURNS
-1 for not in list, index in list of expected errors otherwise
NOTE
If caller needs to know whether the list was empty, they should
check command->expected_errors.count.
*/
static int match_expected_error(struct st_command *command,
unsigned int err_errno,
const char *err_sqlstate)
{
uint i;
for (i= 0 ; (uint) i < command->expected_errors.count ; i++)
{
if ((command->expected_errors.err[i].type == ERR_ERRNO) &&
(command->expected_errors.err[i].code.errnum == err_errno))
return i;
if (command->expected_errors.err[i].type == ERR_SQLSTATE)
{
/*
NULL is quite likely, but not in conjunction with a SQL-state expect!
*/
if (unlikely(err_sqlstate == NULL))
die("expecting a SQL-state (%s) from query '%s' which cannot " "produce one...",
command->expected_errors.err[i].code.sqlstate, command->query);
SYNOPSIS
handle_error()
command - command
err_errno - error number
err_error - error message
err_sqlstate - sql state
ds - dynamic string which is used for output buffer
NOTE
If there is an unexpected error this function will abort mysqltest
immediately.
*/
void handle_error(struct st_command *command,
unsigned int err_errno, const char *err_error,
const char *err_sqlstate, DYNAMIC_STRING *ds)
{
int i;
DBUG_ENTER("handle_error");
var_set_int("$errno", err_errno);
command->used_replace= 1;
if (command->require_file)
{
/*
The query after a "--require" failed. This is fine as long the server
returned a valid response. Don't allow 2013 or 2006 to trigger an
abort_not_supported_test
*/
if (err_errno == CR_SERVER_LOST ||
err_errno == CR_SERVER_GONE_ERROR)
die("require query '%s' failed: %s (%d): %s", command->query,
get_errname_from_code(err_errno),
err_errno, err_error);
/* Abort the run of this test, pass the failed query as reason */
abort_not_supported_test("Query '%s' failed, required functionality " \ "not supported", command->query);
}
if (i >= 0)
{
if (!disable_result_log)
{
if (command->expected_errors.count == 1)
{
/* Only log error if there is one possible error */
dynstr_append_mem(ds, "ERROR ", 6);
replace_dynstr_append(ds, err_sqlstate);
dynstr_append_mem(ds, ": ", 2);
replace_dynstr_append(ds, err_error);
dynstr_append_mem(ds,"\n",1);
}
/* Don't log error if we may not get an error */
else if (command->expected_errors.err[0].type == ERR_SQLSTATE ||
(command->expected_errors.err[0].type == ERR_ERRNO &&
command->expected_errors.err[0].code.errnum != 0))
dynstr_append_mem(ds, STRING_WITH_LEN("Got one of the listed " "errors\n"));
}
/* OK */
revert_properties();
DBUG_VOID_RETURN;
}
DBUG_PRINT("info",("i: %d expected_errors: %d", i,
command->expected_errors.count));
if (command->expected_errors.err[0].type == ERR_ERRNO &&
command->expected_errors.err[0].code.errnum != 0)
{
/* Error code we wanted was != 0, i.e. not an expected success */
report_or_die("query '%s' succeeded - should have failed with error " "%s (%d)...",
command->query,
get_errname_from_code(command->expected_errors.err[0].code.errnum),
command->expected_errors.err[0].code.errnum);
}
else if (command->expected_errors.err[0].type == ERR_SQLSTATE &&
strcmp(command->expected_errors.err[0].code.sqlstate,"00000") != 0)
{
/* SQLSTATE we wanted was != "00000", i.e. not an expected success */
report_or_die("query '%s' succeeded - should have failed with " "sqlstate %s...",
command->query,
command->expected_errors.err[0].code.sqlstate);
}
DBUG_VOID_RETURN;
}
/*
Read result set after prepare statement execution
SYNOPSIS
read_stmt_results
stmt - prepare statement
mysql - mysql handle
command - current command pointer
ds - output buffer where to store result form query
RETURN VALUE 1 - if there is an error in result set
*/
/*
We instruct that we want to update the "max_length" field in
mysql_stmt_store_result(), this is our only way to know how much
buffer to allocate for result data
*/
{
my_bool one= 1;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one))
die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
/*
If we got here the statement succeeded and was expected to do so,
get data. Note that this can still give errors found during execution!
Store the result of the query if if will return any fields
*/
if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
return 1;
}
if (!disable_result_log)
{
/*
Not all statements creates a result set. If there is one we can
now create another normal result set that contains the meta
data. This set can be handled almost like any other non prepared
statement result set.
*/
if ((res= mysql_stmt_result_metadata(stmt)) != NULL)
{
/* Take the column count from meta info */
MYSQL_FIELD *fields= mysql_fetch_fields(res);
uint num_fields= mysql_num_fields(res);
if (display_metadata)
append_metadata(ds, fields, num_fields);
if (!display_result_vertically)
append_table_headings(ds, fields, num_fields);
append_stmt_result(ds, stmt, fields, num_fields);
mysql_free_result(res); /* Free normal result set with meta data */
}
else
{
/*
This is a query without resultset
*/
}
}
return 0;
}
/*
Run query using prepared statement C API
SYNOPSIS
run_query_stmt
mysql - mysql handle
command - current command pointer
query - query string to execute
query_len - length query string to execute
ds - output buffer where to store result form query
if (!mysql)
{
handle_no_active_connection(command, cn, ds);
DBUG_VOID_RETURN;
}
/*
Init a new stmt if it's not already one created for this connection
*/
if (!(stmt= cn->stmt))
{
if (!(stmt= mysql_stmt_init(mysql)))
die("unable to init stmt structure");
cn->stmt= stmt;
}
/* Init dynamic strings for warnings */
if (!disable_warnings)
{
init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256);
init_dynamic_string(&ds_execute_warnings, NULL, 0, 256);
}
/* Check and remove potential trash */
if (strlen(ds->str) != 0)
{
dynstr_trunc(ds, 0);
}
/*
Prepare the query
*/
if (do_stmt_prepare(cn, query, query_len))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
goto end;
}
/*
Get the warnings from mysql_stmt_prepare and keep them in a
separate string
*/
if (!disable_warnings && prepare_warnings_enabled)
append_warnings(&ds_prepare_warnings, mysql);
/*
No need to call mysql_stmt_bind_param() because we have no
parameter markers.
*/
if (cursor_protocol_enabled)
{
ps2_protocol_enabled = 0;
/*
Use cursor for queries matching the filter,
else reset cursor type
*/
if (query_match_cursor_re)
{
/*
Use cursor when retrieving result
*/
ulong type= CURSOR_TYPE_READ_ONLY;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type))
die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
}
/*
Execute the query first time if second execution enable
*/
if (query_match_ps2_re)
{
if (do_stmt_execute(cn))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
goto end;
}
/*
We cannot run query twice if we get prepare warnings as these will otherwise be
disabled
*/
ignore_second_execution= (prepare_warnings_enabled &&
mysql_warning_count(mysql) != 0);
if (ignore_second_execution)
compare_2nd_execution = 0;
else
{
init_dynamic_string(&ds_res_1st_execution, "",
RESULT_STRING_INIT_MEM, RESULT_STRING_INCREMENT_MEM);
ds_res_1st_execution_init = TRUE;
if (read_stmt_results(stmt, &ds_res_1st_execution, command))
{
/*
There was an error during execution
and there is no result set to compare
*/
compare_2nd_execution = 0;
}
else
handle_no_error(command);
}
}
/*
Execute the query
*/
if (!ignore_second_execution && do_stmt_execute(cn))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
goto end;
}
DBUG_ASSERT(ds->length == 0);
int err;
do
{
/*
When running in cursor_protocol get the warnings from execute here
and keep them in a separate string for later. Cursor_protocol is used
only for queries matching the filter "cursor_re".
*/
if (query_match_cursor_re && !disable_warnings)
append_warnings(&ds_execute_warnings, mysql);
if (read_stmt_results(stmt, ds, command))
goto end;
if (!disable_result_log)
{
/*
The results of the first and second execution are compared
only if result logging is enabled
*/
if (compare_2nd_execution && query_match_ps2_re)
{
if (ds->length != ds_res_1st_execution.length ||
!(memcmp(ds_res_1st_execution.str, ds->str, ds->length) == 0))
{
die("The result of the 1st execution does not match with \n" "the result of the 2nd execution of ps-protocol:\n 1st:\n" "%s\n 2nd:\n %s", ds_res_1st_execution.str, ds->str);
}
}
/*
Normally, if there is a result set, we do not show warnings from the
prepare phase. This is because some warnings are generated both during
prepare and execute; this would generate different warning output
between normal and ps-protocol test runs.
The --enable_prepare_warnings command can be used to change this so
that warnings from both the prepare and execute phase are shown.
*/
if ((res= mysql_stmt_result_metadata(stmt)))
{
if (!disable_warnings &&
!prepare_warnings_enabled)
dynstr_set(&ds_prepare_warnings, NULL);
mysql_free_result(res);
}
/*
Fetch info before fetching warnings, since it will be reset
otherwise.
*/
if (!disable_info)
append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql));
if (display_session_track_info)
append_session_track_info(ds, mysql);
if (!disable_warnings && !mysql_more_results(stmt->mysql))
{
/* Get the warnings from execute */
/* Append warnings to ds - if there are any */
if (append_warnings(&ds_execute_warnings, mysql) ||
ds_execute_warnings.length ||
ds_prepare_warnings.length ||
ds_warnings->length)
{
dynstr_append_mem(ds, "Warnings:\n", 10);
if (ds_warnings->length)
dynstr_append_mem(ds, ds_warnings->str,
ds_warnings->length);
if (ds_prepare_warnings.length)
dynstr_append_mem(ds, ds_prepare_warnings.str,
ds_prepare_warnings.length);
if (ds_execute_warnings.length)
dynstr_append_mem(ds, ds_execute_warnings.str,
ds_execute_warnings.length);
}
}
}
} while ( !(err= mysql_stmt_next_result(stmt)));
if (err > 0)
/* We got an error from mysql_next_result, maybe expected */
handle_error(command, mysql_errno(mysql), mysql_error(mysql),
mysql_sqlstate(mysql), ds);
else
handle_no_error(command);
end:
if (ds_res_1st_execution_init)
{
dynstr_free(&ds_res_1st_execution);
ds_res_1st_execution_init= FALSE;
}
if (!disable_warnings)
{
dynstr_free(&ds_prepare_warnings);
dynstr_free(&ds_execute_warnings);
}
/*
We save the return code (mysql_stmt_errno(stmt)) from the last call sent
to the server into the mysqltest builtin variable $mysql_errno. This
variable then can be used from the test case itself.
*/
var_set_errno(mysql_stmt_errno(stmt));
display_optimizer_trace(cn, ds);
if (cursor_protocol_enabled)
{
ulong type= CURSOR_TYPE_NO_CURSOR;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type))
die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
revert_properties();
/* Close the statement if reconnect, need new prepare */
{
#ifndef EMBEDDED_LIBRARY
my_bool reconnect;
mysql_get_option(mysql, MYSQL_OPT_RECONNECT, &reconnect);
if (reconnect)
#else
if (mysql->reconnect)
#endif
{
mysql_stmt_close(stmt);
cn->stmt= NULL;
}
}
DBUG_VOID_RETURN;
}
/*
prepare query using prepared statement C API
SYNPOSIS
run_prepare_stmt
mysql - mysql handle
command - current command pointer
query - query string to execute
query_len - length query string to execute
ds - output buffer where to store result form query
/*
Init a new stmt if it's not already one created for this connection
*/
if(!(stmt= cn->stmt))
{
if (!(stmt= mysql_stmt_init(mysql)))
die("unable to init stmt structure");
cn->stmt= stmt;
}
/* Init dynamic strings for warnings */
if (!disable_warnings)
{
init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256);
}
/*
Get the warnings from mysql_stmt_prepare and keep them in a
separate string
*/
if (!disable_warnings)
{
append_warnings(&ds_prepare_warnings, mysql);
dynstr_free(&ds_prepare_warnings);
}
end:
DBUG_VOID_RETURN;
}
/*
bind parameters for a prepared statement C API
SYNPOSIS
run_bind_stmt
mysql - mysql handle
command - current command pointer
query - query string to execute
query_len - length query string to execute
ds - output buffer where to store result form query
int rc= mysql_stmt_bind_param(stmt, ps_params);
if (rc)
{
die("mysql_stmt_bind_param() failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
DBUG_VOID_RETURN;
}
/*
execute query using prepared statement C API
SYNPOSIS
run_axecute_stmt
mysql - mysql handle
command - current command pointer
query - query string to execute
query_len - length query string to execute
ds - output buffer where to store result form query
RETURN VALUE
error - function will not return
*/
void run_execute_stmt(struct st_connection *cn, struct st_command *command,
const char *query, size_t query_len, DYNAMIC_STRING *ds,
DYNAMIC_STRING *ds_warnings)
{
MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */
MYSQL *mysql= cn->mysql;
MYSQL_STMT *stmt= cn->stmt;
DYNAMIC_STRING ds_execute_warnings;
DBUG_ENTER("run_execute_stmt");
DBUG_PRINT("query", ("'%-.60s'", query));
/* Init dynamic strings for warnings */
if (!disable_warnings)
{
init_dynamic_string(&ds_execute_warnings, NULL, 0, 256);
}
if (cursor_protocol_enabled)
{
/*
Use cursor when retrieving result
*/
ulong type= CURSOR_TYPE_READ_ONLY;
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type))
die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
}
/*
Execute the query
*/ if (do_stmt_execute(cn))
{
handle_error(command, mysql_stmt_errno(stmt),
mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); goto end;
}
/* Whenrunningincursor_protocolgetthewarningsfromexecutehere andkeeptheminaseparatestringforlater.
*/ if (cursor_protocol_enabled && !disable_warnings)
append_warnings(&ds_execute_warnings, mysql);
/* If we got here the statement was both executed and read successfully */
handle_no_error(command); if (!disable_result_log)
{ /* Notallstatementscreatesaresultset.Ifthereisonewecan nowcreateanothernormalresultsetthatcontainsthemeta data.Thissetcanbehandledalmostlikeanyothernonprepared statementresultset.
*/ if ((res= mysql_stmt_result_metadata(stmt)) != NULL)
{ /* Take the column count from meta info */
MYSQL_FIELD *fields= mysql_fetch_fields(res);
uint num_fields= mysql_num_fields(res);
if (display_metadata)
append_metadata(ds, fields, num_fields);
if (!display_result_vertically)
append_table_headings(ds, fields, num_fields);
append_stmt_result(ds, stmt, fields, num_fields);
mysql_free_result(res); /* Free normal result set with meta data */
/* Fetchinfobeforefetchingwarnings,sinceitwillbereset otherwise.
*/ if (!disable_info)
append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql));
if (display_session_track_info)
append_session_track_info(ds, mysql);
if (!disable_warnings)
{ /* Get the warnings from execute */
/* Append warnings to ds - if there are any */ if (append_warnings(&ds_execute_warnings, mysql) ||
ds_execute_warnings.length ||
ds_warnings->length)
{
dynstr_append_mem(ds, "Warnings:\n", 10); if (ds_warnings->length)
dynstr_append_mem(ds, ds_warnings->str,
ds_warnings->length); if (ds_execute_warnings.length)
dynstr_append_mem(ds, ds_execute_warnings.str,
ds_execute_warnings.length);
}
}
}
end: if (!disable_warnings)
{
dynstr_free(&ds_execute_warnings);
}
/* Close the statement if reconnect, need new prepare */
{ #ifndef EMBEDDED_LIBRARY
my_bool reconnect;
java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 15 if (reconnect) #else if (mysql->reconnect) #endif
{ if (cn->ps_params)
{ for (size_t i=0; i<stmt->param_count; i++)
{
my_free(cn->ps_params[i].buffer);
cn->ps_params[i].buffer= NULL;
}
my_free(cn->ps_params);
}
mysql_stmt_close(stmt);
cn->stmt= NULL;
cn->ps_params= NULL;
}
}
DBUG_VOID_RETURN;
}
int util_query(MYSQL* org_mysql, constchar* query){
MYSQL* mysql;
DBUG_ENTER("util_query");
if (service_connection_enabled)
{ if(!(mysql= cur_con->util_mysql))
{
DBUG_PRINT("info", ("Creating util_mysql")); if (!(mysql= mysql_init(mysql)))
die("Failed in mysql_init()");
if (opt_connect_timeout)
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT,
(void *) &opt_connect_timeout);
/* enable local infile, in non-binary builds often disabled by default */
mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0);
mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0);
mysql_options(mysql,MYSQL_OPT_PROTOCOL,(char*)&(org_mysql->options.protocol));
SET_SSL_OPTS(mysql);
safe_connect(mysql, "util", org_mysql->host, org_mysql->user,
org_mysql->passwd, org_mysql->db, org_mysql->port,
org_mysql->unix_socket);
if (sp_created)
{ if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp "))
report_or_die("Failed to drop sp: %d: %s", mysql_errno(mysql),
mysql_error(mysql));
}
if (view_created)
{ if (util_query(mysql, "DROP VIEW mysqltest_tmp_v "))
report_or_die("Failed to drop view: %d: %s",
mysql_errno(mysql), mysql_error(mysql));
}
if (command->require_file)
{ /* A result file was specified for _this_ query andtheoutputshouldbecheckedagainstanalready existingfilewhichhasbeenspecifiedusing--requireor--result
*/
check_require(rs_output, command->require_file);
}
if (rs_output == &rs_cmp_result)
dynstr_free(&rs_cmp_result);
DBUG_VOID_RETURN;
}
/* Lookforcasewhere"query"wasexplicitlyspecifiedto forcecommandbeingsenttoserver
*/ if (type == Q_QUERY)
{ /* Skip the "query" part */
command->query_len-= (int)(command->first_argument - command->query);
command->query= command->first_argument;
}
} else
{ /* No mysqltest command matched */
if (command->type != Q_COMMENT_WITH_COMMAND)
{ /* A query that will sent to mysqld */
command->type= Q_QUERY;
} else
{ /* -- "comment" that didn't contain a mysqltest command */
report_or_die("Found line beginning with -- that didn't contain " \ "a valid mysqltest command, check your syntax or " \ "use # if you intended to write a comment");
command->type= Q_COMMENT;
}
}
/* Set expected error on command */
memcpy(&command->expected_errors, &saved_expected_errors, sizeof(saved_expected_errors));
DBUG_PRINT("info", ("There are %d expected errors",
command->expected_errors.count));
DBUG_VOID_RETURN;
}
fprintf(stderr, "Writing a core file...\n");
fflush(stderr);
my_write_core(sig); #ifndef _WIN32
sf_leaking_memory= 1; exit(1); // Shouldn't get here but just in case #endif
}
#ifdef _WIN32
LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp)
{
__try
{
my_set_exception_pointers(exp);
signal_handler(exp->ExceptionRecord->ExceptionCode);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
fputs("Got exception in exception handler!\n", stderr);
}
/* Set output destination of messages to the standard error stream. */
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
/* Do not not display the a error message box. */
mode= SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX;
SetErrorMode(mode);
/* Enable second execution of SELECT for ps-protocol
if ps-protocol is used */
ps2_protocol_enabled= ps_protocol;
ps_protocol_enabled= ps_protocol;
sp_protocol_enabled= sp_protocol;
view_protocol_enabled= view_protocol;
cursor_protocol_enabled= cursor_protocol;
st_connection *con= connections;
init_connection_thd(con);
if (! (con->mysql= mysql_init(0)))
die("Failed in mysql_init()");
if (opt_connect_timeout)
mysql_options(con->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
(void *) &opt_connect_timeout);
if (opt_compress)
mysql_options(con->mysql,MYSQL_OPT_COMPRESS,NullS);
mysql_options(con->mysql, MYSQL_SET_CHARSET_NAME,
charset_info->cs_name.str);
if (opt_charsets_dir)
mysql_options(con->mysql, MYSQL_SET_CHARSET_DIR,
opt_charsets_dir);
if (opt_protocol)
mysql_options(con->mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol);
if (opt_plugin_dir && *opt_plugin_dir)
mysql_options(con->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);
SET_SSL_OPTS(con->mysql);
if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "default", MYF(MY_WME))))
die("Out of memory");
mysql_options(con->mysql, MYSQL_OPT_NONBLOCK, 0);
/* Use all time until exit if no explicit 'start_timer' */
timer_start= timer_now();
/*
Initialize $mysql_errno with -1, so we can
- distinguish it from valid values ( >= 0 ) and
- detect if there was never a command sent to the server
*/
var_set_errno(-1);
set_current_connection(con);
if (opt_prologue)
{
open_file(opt_prologue);
}
verbose_msg("Start processing test commands from '%s' ...", cur_file->file_name);
while (!abort_flag && !read_command(&command))
{
my_bool ok_to_do;
int current_line_inc = 1, processed = 0;
if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND)
get_command_type(command);
if (parsing_disabled &&
command->type != Q_ENABLE_PARSING &&
command->type != Q_DISABLE_PARSING)
{
/* Parsing is disabled, silently convert this line to a comment */
command->type= Q_COMMENT;
}
/* (Re-)set abort_on_error for this command */
command->abort_on_error= (command->expected_errors.count == 0 &&
abort_on_error);
/*
some commands need to be executed or at least parsed unconditionally,
because they change the grammar.
*/
ok_to_do= cur_block->ok || command->type == Q_DELIMITER
|| command->type == Q_PERL;
/*
Some commands need to be "done" the first time if they may get
re-iterated over in a true context. This can only happen if there's
a while loop at some level above the current block.
*/
if (!ok_to_do)
{
if (command->type == Q_SOURCE ||
command->type == Q_ERROR ||
command->type == Q_WRITE_FILE ||
command->type == Q_APPEND_FILE)
{
for (struct st_block *stb= cur_block-1; stb >= block_stack; stb--)
{
if (stb->cmd == cmd_while)
{
ok_to_do= 1;
break;
}
}
}
}
if (ok_to_do)
{
command->last_argument= command->first_argument;
processed = 1;
/* Need to remember this for handle_error() */
curr_command= command;
switch (command->type) {
case Q_CONNECT:
do_connect(command);
break;
case Q_CONNECTION: select_connection(command); break;
case Q_DISCONNECT:
case Q_DIRTY_CLOSE:
do_close_connection(command); break;
case Q_ENABLE_PREPARE_WARNINGS: prepare_warnings_enabled=1; break;
case Q_DISABLE_PREPARE_WARNINGS: prepare_warnings_enabled=0; break;
case Q_ENABLE_QUERY_LOG:
set_property(command, P_QUERY, 0);
break;
case Q_DISABLE_QUERY_LOG:
set_property(command, P_QUERY, 1);
break;
case Q_ENABLE_ABORT_ON_ERROR:
set_property(command, P_ABORT, 1);
break;
case Q_DISABLE_ABORT_ON_ERROR:
set_property(command, P_ABORT, 0);
break;
case Q_ENABLE_RESULT_LOG:
set_property(command, P_RESULT, 0);
break;
case Q_DISABLE_RESULT_LOG:
set_property(command, P_RESULT, 1);
break;
case Q_ENABLE_CONNECT_LOG:
set_property(command, P_CONNECT, 0);
break;
case Q_DISABLE_CONNECT_LOG:
set_property(command, P_CONNECT, 1);
break;
case Q_ENABLE_WARNINGS:
set_property(command, P_WARN, 0);
break;
case Q_DISABLE_WARNINGS:
set_property(command, P_WARN, 1);
break;
case Q_ENABLE_INFO:
set_property(command, P_INFO, 0);
break;
case Q_DISABLE_INFO:
set_property(command, P_INFO, 1);
break;
case Q_ENABLE_SESSION_TRACK_INFO:
set_property(command, P_SESSION_TRACK, 1);
break;
case Q_DISABLE_SESSION_TRACK_INFO:
set_property(command, P_SESSION_TRACK, 0);
break;
case Q_ENABLE_METADATA:
set_property(command, P_META, 1);
break;
case Q_DISABLE_METADATA:
set_property(command, P_META, 0);
break;
case Q_ENABLE_COLUMN_NAMES:
disable_column_names= 0;
var_set_int("$ENABLED_COLUMN_NAMES", 0);
break;
case Q_DISABLE_COLUMN_NAMES:
disable_column_names= 1;
var_set_int("$ENABLED_COLUMN_NAMES", 1);
break;
case Q_SOURCE: do_source(command); break;
case Q_SLEEP: do_sleep(command, 0); break;
case Q_REAL_SLEEP: do_sleep(command, 1); break;
case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(command); break;
case Q_INC: do_modify_var(command, DO_INC); break;
case Q_DEC: do_modify_var(command, DO_DEC); break;
case Q_ECHO: do_echo(command); command_executed++; break;
case Q_BREAK: do_break(command); break;
case Q_SYSTEM: do_system(command); break;
case Q_REMOVE_FILE: do_remove_file(command); break;
case Q_REMOVE_FILES_WILDCARD: do_remove_files_wildcard(command); break;
case Q_MKDIR: do_mkdir(command); break;
case Q_RMDIR: do_rmdir(command); break;
case Q_LIST_FILES: do_list_files(command); break;
case Q_LIST_FILES_WRITE_FILE:
do_list_files_write_file_command(command, FALSE);
break;
case Q_LIST_FILES_APPEND_FILE:
do_list_files_write_file_command(command, TRUE);
break;
case Q_FILE_EXIST: do_file_exist(command); break;
case Q_WRITE_FILE: do_write_file(command); break;
case Q_WRITE_LINE: do_write_line(command); break;
case Q_APPEND_FILE: do_append_file(command); break;
case Q_DIFF_FILES: do_diff_files(command); break;
case Q_SEND_QUIT: do_send_quit(command); break;
case Q_CHANGE_USER: do_change_user(command); break;
case Q_CAT_FILE: do_cat_file(command); command_executed++; break;
case Q_COPY_FILE: do_copy_file(command); break;
case Q_MOVE_FILE: do_move_file(command); break;
case Q_CHMOD_FILE: do_chmod_file(command); break;
case Q_PERL: do_perl(command); break;
case Q_RESULT_FORMAT_VERSION: do_result_format_version(command); break;
case Q_DELIMITER:
do_delimiter(command);
break;
case Q_DISPLAY_VERTICAL_RESULTS:
display_result_vertically= TRUE;
break;
case Q_DISPLAY_HORIZONTAL_RESULTS:
display_result_vertically= FALSE;
break;
case Q_SORTED_RESULT:
/*
Turn on sorting of result set, will be reset after next
command
*/
display_result_sorted= TRUE;
break;
case Q_LOWERCASE:
/*
Turn on lowercasing of result, will be reset after next
command
*/
display_result_lower= TRUE;
break;
case Q_LET: do_let(command); break;
case Q_EVAL_RESULT:
die("'eval_result' command is deprecated");
break; // never called but keep compiler calm
case Q_EVAL:
case Q_EVALP:
case Q_QUERY_VERTICAL:
case Q_QUERY_HORIZONTAL:
if (command->query == command->query_buf)
{
/* Skip the first part of command, i.e query_xxx */
command->query_len-= (int)(command->first_argument - command->query);
command->query= command->first_argument;
command->first_word_len= 0;
}
/* fall through */
case Q_QUERY:
case Q_REAP:
case Q_PS_PREPARE:
case Q_PS_BIND:
case Q_PS_EXECUTE:
case Q_PS_CLOSE:
{
my_bool old_display_result_vertically= display_result_vertically;
/* Default is full query, both reap and send */
int flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG;
if (q_send_flag)
{
/* Last command was an empty 'send' */
flags= QUERY_SEND_FLAG;
q_send_flag= 0;
}
else if (command->type == Q_REAP)
{
flags= QUERY_REAP_FLAG;
}
if (command->type == Q_EVALP)
flags |= QUERY_PRINT_ORIGINAL_FLAG;
/* Check for special property for this query */
display_result_vertically|= (command->type == Q_QUERY_VERTICAL);
if (save_file[0])
{
if (!(command->require_file= strdup_root(&require_file_root,
save_file)))
die("out of memory for require_file");
save_file[0]= 0;
}
run_query(cur_con, command, flags);
command_executed++;
command->last_argument= command->end;
break;
}
case Q_SEND:
case Q_SEND_EVAL:
if (!*command->first_argument)
{
/*
This is a send without arguments, it indicates that _next_ query
should be send only
*/
q_send_flag= 1;
break;
}
/* Remove "send" if this is first iteration */
if (command->query == command->query_buf)
{
command->query_len-= (int)(command->first_argument - command->query);
command->query= command->first_argument;
}
/*
run_query() can execute a query partially, depending on the flags.
QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send
the query and read the result some time later when reap instruction
is given on this connection.
*/
run_query(cur_con, command, QUERY_SEND_FLAG);
command_executed++;
command->last_argument= command->end;
break;
case Q_REQUIRE:
do_get_file_name(command, save_file, sizeof(save_file));
break;
case Q_ERROR:
do_get_errcodes(command);
break;
case Q_REPLACE:
do_get_replace(command);
break;
case Q_REPLACE_REGEX:
do_get_replace_regex(command);
break;
case Q_REPLACE_COLUMN:
do_get_replace_column(command);
break;
case Q_SAVE_MASTER_POS: do_save_master_pos(); break;
case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break;
case Q_SYNC_SLAVE_WITH_MASTER:
{
do_save_master_pos();
if (*command->first_argument)
select_connection(command);
else
select_connection_name("slave");
do_sync_with_master2(command, 0, "");
break;
}
case Q_COMMENT:
{
command->last_argument= command->end;
/* Don't output comments in v1 */
if (opt_result_format_version == 1)
break;
/* Don't output comments if query logging is off */
if (disable_query_log)
break;
/* Write comment's with two starting #'s to result file */
const char* p= command->query;
if (p && *p == '#' && *(p+1) == '#')
{
dynstr_append_mem(&ds_res, command->query, command->query_len);
dynstr_append_mem(&ds_res, STRING_WITH_LEN("\n"));
}
break;
}
case Q_EMPTY_LINE:
/* Don't output newline in v1 */
if (opt_result_format_version == 1)
break;
/* Don't output newline if query logging is off */
if (disable_query_log)
break;
dynstr_append_mem(&ds_res, STRING_WITH_LEN("\n"));
break;
case Q_PING:
handle_command_error(command, mysql_ping(cur_con->mysql), -1);
break;
case Q_RESET_CONNECTION:
do_reset_connection();
break;
case Q_OPTIMIZER_TRACE:
enable_optimizer_trace(cur_con);
break;
case Q_DISABLE_REPLAY:
do_disable_replay(command);
break;
case Q_SEND_SHUTDOWN:
handle_command_error(command,
mysql_shutdown(cur_con->mysql,
SHUTDOWN_DEFAULT), -1);
break;
case Q_SHUTDOWN_SERVER:
do_shutdown_server(command);
break;
case Q_EXEC:
do_exec(command);
command_executed++;
break;
case Q_START_TIMER:
/* Overwrite possible earlier start of timer */
timer_start= timer_now();
break;
case Q_END_TIMER:
/* End timer before ending mysqltest */
timer_output();
break;
case Q_CHARACTER_SET:
do_set_charset(command);
break;
case Q_DISABLE_PS_PROTOCOL:
set_property(command, P_PS, 0);
/* Close any open statements */
close_statements();
break;
case Q_ENABLE_PS_PROTOCOL:
set_property(command, P_PS, ps_protocol);
break;
case Q_DISABLE_PS2_PROTOCOL:
set_property(command, P_PS2, 0);
break;
case Q_ENABLE_PS2_PROTOCOL:
set_property(command, P_PS2, ps_protocol);
break;
case Q_DISABLE_VIEW_PROTOCOL:
set_property(command, P_VIEW, 0);
/* Close only util connections */
close_util_connections();
break;
case Q_ENABLE_VIEW_PROTOCOL:
set_property(command, P_VIEW, view_protocol);
break;
case Q_DISABLE_CURSOR_PROTOCOL:
set_property(command, P_CURSOR, 0);
if (cursor_protocol)
set_property(command, P_PS, 0);
/* Close any open statements */
close_statements();
break;
case Q_ENABLE_CURSOR_PROTOCOL:
set_property(command, P_CURSOR, cursor_protocol);
set_property(command, P_PS, ps_protocol);
break;
case Q_DISABLE_SERVICE_CONNECTION:
set_property(command, P_CONN, 0);
/* Close only util connections */
close_util_connections();
break;
case Q_ENABLE_SERVICE_CONNECTION:
set_property(command, P_CONN, view_protocol);
break;
case Q_DISABLE_NON_BLOCKING_API:
non_blocking_api_enabled= 0;
break;
case Q_ENABLE_NON_BLOCKING_API:
non_blocking_api_enabled= 1;
break;
case Q_DISABLE_RECONNECT:
mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_false);
break;
case Q_ENABLE_RECONNECT:
mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_true);
/* Close any open statements - no reconnect, need new prepare */
close_statements();
break;
case Q_DISABLE_PARSING:
if (parsing_disabled == 0)
parsing_disabled= 1;
else
report_or_die("Parsing is already disabled");
break;
case Q_ENABLE_PARSING:
/*
Ensure we don't get parsing_disabled < 0 as this would accidentally
disable code we don't want to have disabled
*/
if (parsing_disabled == 1)
parsing_disabled= 0;
else
report_or_die("Parsing is already enabled");
break;
case Q_DIE:
{
char message[160];
const char *msg;
DYNAMIC_STRING ds_echo;
/* Abort test with error code and error message */
die("%s", msg);
break;
}
case Q_EXIT:
/* Stop processing any more commands */
abort_flag= 1;
break;
case Q_SKIP:
/* Eval the query, thus replacing all environment variables */
dynstr_set(&ds_res, 0);
do_eval(&ds_res, command->first_argument, command->end, FALSE);
abort_not_supported_test("%s",ds_res.str);
break;
case Q_RESULT:
die("result, deprecated command");
break;
default:
processed= 0;
break;
}
}
if (!processed)
{
current_line_inc= 0;
switch (command->type) {
case Q_WHILE: do_block(cmd_while, command); break;
case Q_IF: do_block(cmd_if, command); break;
case Q_END_BLOCK: do_done(command); break;
default: current_line_inc = 1; break;
}
}
else
check_eol_junk(command->last_argument);
if (command->type != Q_ERROR &&
command->type != Q_COMMENT)
{
/*
As soon as any non "error" command or comment has been executed,
the array with expected errors should be cleared
*/
memset(&saved_expected_errors, 0, sizeof(saved_expected_errors));
}
if (command_executed != last_command_executed || command->used_replace)
{
/*
As soon as any command has been executed,
the replace structures should be cleared
*/
free_all_replace();
/* Also reset "sorted_result" and "lowercase"*/
display_result_sorted= FALSE;
display_result_lower= FALSE;
}
last_command_executed= command_executed;
parser.current_line += current_line_inc;
if ( opt_mark_progress )
mark_progress(command, parser.current_line);
/* Write result from command to log file immediately */
log_file.write(&ds_res);
log_file.flush();
dynstr_set(&ds_res, 0);
}
log_file.close();
start_lineno= 0;
verbose_msg("... Done processing test commands.");
if (parsing_disabled)
die("Test ended with parsing disabled");
/*
The whole test has been executed successfully.
Time to compare result or save it to record file.
The entire output from test is in the log file
*/
if (log_file.bytes_written())
{
if (result_file_name)
{
/* A result file has been specified */
if (record)
{
/* Recording */
/* save a copy of the log to result file */
if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0)
die("Failed to copy '%s' to '%s', errno: %d",
log_file.file_name(), result_file_name, errno);
}
else
{
/* Check that the output from test is equal to result file */
error= check_result();
}
}
}
else
{
/* Empty output is an error *unless* we also have an empty result file */
if (! result_file_name || record ||
compare_files (log_file.file_name(), result_file_name))
{
fprintf(stderr, "mysqltest: The test didn't produce any output\n");
error= 1;
}
else
{
empty_result= TRUE; /* Meaning empty was expected */
}
}
if (!command_executed && result_file_name && !empty_result)
{
fprintf(stderr, "mysqltest: No queries executed but non-empty result file found!\n");
error= 1;
}
if (!error)
verbose_msg("Test has succeeded!");
timer_output();
cleanup_and_exit(error, 0);
return 0; /* Keep compiler happy too */
}
/*
A primitive timer that give results in milliseconds if the
--timer-file=<filename> is given. The timer result is written
to that file when the result is available. To not confuse
mysql-test-run with an old obsolete result, we remove the file
before executing any commands. The time we measure is
- If no explicit 'start_timer' or 'end_timer' is given in the
test case, the timer measure how long we execute in mysqltest.
- If only 'start_timer' is given we measure how long we execute
from that point until we terminate mysqltest.
- If only 'end_timer' is given we measure how long we execute
from that we enter mysqltest to the 'end_timer' is command is
executed.
- If both 'start_timer' and 'end_timer' are given we measure
the time between executing the two commands.
*/
void timer_output(void)
{
if (timer_file)
{
char buf[32], *end;
ulonglong timer= timer_now() - timer_start;
end= longlong10_to_str(timer, buf, 10);
str_to_file(timer_file,buf, (int) (end-buf));
/* Timer has been written to the file, don't use it anymore */
timer_file= 0;
}
}
/*
Get arguments for replace_columns. The syntax is:
replace-column column_number to_string [column_number to_string ...]
Where each argument may be quoted with ' or "
A argument may also be a variable, in which case the value of the
variable is replaced.
*/
free_replace_column();
if (!*from)
die("Missing argument in %s", command->query);
/* Allocate a buffer for results */
start= buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, strlen(from)+1,
MYF(MY_WME|MY_FAE));
while (*from)
{
char *to;
uint column_number;
to= get_string(&buff, &from, command);
if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS)
die("Wrong column number to replace_column in '%s'", command->query);
if (!*from)
die("Wrong number of arguments to replace_column in '%s'",
command->query);
to= get_string(&buff, &from, command);
my_free(replace_column[column_number-1]);
replace_column[column_number-1]= my_strdup(PSI_NOT_INSTRUMENTED, to,
MYF(MY_WME|MY_FAE));
set_if_bigger(max_replace_column, column_number);
}
my_free(start);
command->last_argument= command->end;
DBUG_VOID_RETURN;
}
void free_replace_column()
{
uint i;
for (i=0 ; i < max_replace_column ; i++)
{
if (replace_column[i])
{
my_free(replace_column[i]);
replace_column[i]= 0;
}
}
max_replace_column= 0;
}
typedef struct st_pointer_array { /* when using array-strings */
TYPELIB typelib; /* Pointer to strings */
uchar *str; /* Strings is here */
uint8 *flag; /* Flag about each var. */
uint array_allocs,max_count,length,max_length;
} POINTER_ARRAY;
/*
Get arguments for replace. The syntax is:
replace from to [from to ...]
Where each argument may be quoted with ' or "
A argument may also be a variable, in which case the value of the
variable is replaced.
*/
start= from= str;
rep_pos=rep+1;
for (;;)
{
/* Loop through states */
DBUG_PRINT("info", ("Looping through states"));
while (!rep_pos->found)
rep_pos= rep_pos->next[(uchar) *from++];
/* Does this state contain a string to be replaced */
if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string)
{
/* No match found */
dynstr_append_mem(ds, start, from - start - 1);
DBUG_PRINT("exit", ("Found no more string to replace, appended: %s", start));
DBUG_VOID_RETURN;
}
/* Found a string that needs to be replaced */
DBUG_PRINT("info", ("found: %d, to_offset: %u, from_offset: %d, string: %s",
rep_str->found, rep_str->to_offset,
rep_str->from_offset, rep_str->replace_string));
/* Append part of original string before replace string */
dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start);
if (!*(from-=rep_str->from_offset) && rep_pos->found != 2)
{
/* End of from string */
DBUG_PRINT("exit", ("Found end of from string"));
DBUG_VOID_RETURN;
}
start= from;
rep_pos=rep;
}
}
/*
Regex replace functions
*/
/* Stores regex substitutions */
struct st_regex
{
char* pattern; /* Pattern to be replaced */
char* replace; /* String or expression to replace the pattern with */
int icase; /* true if the match is case insensitive */
};
int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace,
char *string, int icase);
bool parse_re_part(char *start_re, char *end_re,
char **p, char *end, char **buf)
{
if (*start_re != *end_re)
{
switch ((*start_re= *(*p)++)) {
case '(': *end_re= ')'; break;
case '[': *end_re= ']'; break;
case '{': *end_re= '}'; break;
case '<': *end_re= '>'; break;
default: *end_re= *start_re;
}
}
while (*p < end && **p != *end_re)
{
if ((*p)[0] == '\\' && *p + 1 < end && (*p)[1] == *end_re)
(*p)++;
*(*buf)++= *(*p)++;
}
*(*buf)++= 0;
(*p)++;
return *p > end;
}
/*
Initializes the regular substitution expression to be used in the
result output of test.
Returns: st_replace_regex struct with pairs of substitutions
*/
void append_replace_regex(char*, char*, struct st_replace_regex*, char**);
/* my_malloc() will die on fail with MY_FAE */
res=(struct st_replace_regex*)my_malloc(PSI_NOT_INSTRUMENTED,
sizeof(*res)+8192 ,MYF(MY_FAE+MY_WME));
my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &res->regex_arr, sizeof(struct st_regex), 128, 128, MYF(0));
/* for each regexp substitution statement */
while (p < expr_end)
{
bzero(®,sizeof(reg));
/* find the start of the statement */
while (my_isspace(charset_info, *p) && p < expr_end)
p++;
if (p >= expr_end)
{
if (res->regex_arr.elements)
break;
else
goto err;
}
start_re= 0;
reg.pattern= *buf_p;
/* Allow variable for the *entire* list of replacements */
if (*p == '$')
{
const char *v_end= 0;
VAR *val= var_get(p, &v_end, 0, 1);
Returns: true if substitution was made, false otherwise
Side-effect: Sets r->buf to be the buffer with all substitutions done.
IN:
struct st_replace_regex* r
char* val
Out:
struct st_replace_regex* r
r->buf points at the resulting buffer
r->even_buf and r->odd_buf might have been reallocated
r->even_buf_len and r->odd_buf_len might have been changed
TODO: at some point figure out if there is a way to do everything
in one pass
*/
/* For each substitution, do the replace */
for (i= 0; i < r->regex_arr.elements; i++)
{
struct st_regex re;
char* save_out_buf= out_buf;
get_dynamic(&r->regex_arr, &re, i);
if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace,
in_buf, re.icase))
{
/* if the buffer has been reallocated, make adjustments */
if (save_out_buf != out_buf)
{
if (save_out_buf == r->even_buf)
r->even_buf= out_buf;
else
r->odd_buf= out_buf;
}
r->buf= out_buf;
if (in_buf == val)
in_buf= r->odd_buf;
/*
auxiliary macro used by reg_replace
makes sure the result buffer has sufficient length
*/
#define SECURE_REG_BUF if (buf_len < need_buf_len) \
{ \
ssize_t off= res_p - buf; \
buf= (char*)my_realloc(PSI_NOT_INSTRUMENTED, buf,need_buf_len,MYF(MY_WME+MY_FAE)); \
res_p= buf + off; \
buf_len= need_buf_len; \
} \
\
/*
Performs a regex substitution
IN:
buf_p - result buffer pointer. Will change if reallocated
buf_len_p - result buffer length. Will change if the buffer is reallocated
pattern - regexp pattern to match
replace - replacement expression
string - the string to perform substitutions in
icase - flag, if set to 1 the match is case insensitive
*/
int reg_replace(char** buf_p, int* buf_len_p, char *pattern,
char *replace, char *string, int icase)
{
regex_t r;
regmatch_t *subs;
char *replace_end;
char *buf= *buf_p;
size_t len;
size_t buf_len, need_buf_len;
int cflags= REG_EXTENDED | REG_DOTALL;
int err_code;
char *res_p,*str_p,*str_end;
/* for each pattern match instance perform a replacement */
while (!err_code)
{
/* find the match */
err_code= regexec(&r,str_p, r.re_nsub+1, subs,
(str_p == string) ? 0 : REG_NOTBOL);
/* if regular expression error (eg. bad syntax, or out of memory) */
if (err_code && err_code != REG_NOMATCH)
{
check_regerr(&r,err_code);
regfree(&r);
return 1;
}
/* if match found */
if (!err_code)
{
char* expr_p= replace;
int c;
/*
we need at least what we have so far in the buffer + the part
before this match
*/
need_buf_len= (res_p - buf) + (int) subs[0].rm_so;
/* on this pass, calculate the memory for the result buffer */
while (expr_p < replace_end)
{
int back_ref_num= -1;
c= *expr_p;
/* found a valid back_ref (eg. \1)*/
if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub)
{
regoff_t start_off, end_off;
if ((start_off=subs[back_ref_num].rm_so) > -1 &&
(end_off=subs[back_ref_num].rm_eo) > -1)
{
need_buf_len += (int) (end_off - start_off);
}
expr_p += 2;
}
else
{
expr_p++;
need_buf_len++;
}
}
need_buf_len++;
/*
now that we know the size of the buffer,
make sure it is big enough
*/
SECURE_REG_BUF
/* copy the pre-match part */
if (subs[0].rm_so)
{
memcpy(res_p, str_p, (size_t) subs[0].rm_so);
res_p+= subs[0].rm_so;
}
expr_p= replace;
/* copy the match and expand back_refs */
while (expr_p < replace_end)
{
int back_ref_num= -1;
c= *expr_p;
typedef struct st_rep_set {
uint *bits; /* Pointer to used sets */
short next[LAST_CHAR_CODE]; /* Pointer to next sets */
uint found_len; /* Best match to date */
int found_offset;
uint table_offset;
uint size_of_bits; /* For convenience */
} REP_SET;
typedef struct st_rep_sets {
uint count; /* Number of sets */
uint extra; /* Extra sets in buffer */
uint invisible; /* Sets not chown */
uint size_of_bits;
REP_SET *set,*set_buffer;
uint *bit_buffer;
} REP_SETS;
typedef struct st_found_set {
uint table_offset;
int found_offset;
} FOUND_SET;
/* Count number of states */
for (i=result_len=max_length=0 , states=2 ; i < count ; i++)
{
len=replace_len(from[i]);
if (!len)
{
errno=EINVAL;
DBUG_RETURN(0);
}
states+=len+1;
result_len+=(uint) strlen(to[i])+1;
if (len > max_length)
max_length=len;
}
bzero(is_word_end, sizeof(is_word_end));
for (i=0 ; word_end_chars[i] ; i++)
is_word_end[(uchar) word_end_chars[i]]=1;
if (init_sets(&sets,states))
DBUG_RETURN(0);
found_sets=0;
if (!(found_set= (FOUND_SET*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(FOUND_SET)*max_length*count,
MYF(MY_WME|MY_FAE))))
{
free_sets(&sets);
DBUG_RETURN(0);
}
(void) make_new_set(&sets); /* Set starting set */
make_sets_invisible(&sets); /* Hide previous sets */
used_sets=-1;
word_states=make_new_set(&sets); /* Start of new word */
start_states=make_new_set(&sets); /* This is first state */
if (!(follow=(FOLLOWS*) my_malloc(PSI_NOT_INSTRUMENTED, (states+2)*sizeof(FOLLOWS),MYF(MY_WME|MY_FAE))))
{
free_sets(&sets);
my_free(found_set);
DBUG_RETURN(0);
}
for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++)
{
set=sets.set+set_nr;
default_state= 0; /* Start from beginning */
/* If end of found-string not found or start-set with current set */
for (i= (uint) ~0; (i=get_next_bit(set,i)) ;)
{
if (!follow[i].chr)
{
if (! default_state)
default_state= find_found(found_set,set->table_offset,
set->found_offset+1);
}
}
copy_bits(sets.set+used_sets,set); /* Save set for changes */
if (!default_state)
or_bits(sets.set+used_sets,sets.set); /* Can restart from start */
/* Find all chars that follows current sets */
bzero(used_chars, sizeof(used_chars));
for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;)
{
used_chars[follow[i].chr]=1;
if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr &&
follow[i].len > 1) || follow[i].chr == END_OF_LINE)
used_chars[0]=1;
}
/* Mark word_chars used if \b is in state */
if (used_chars[SPACE_CHAR])
for (pos= word_end_chars ; *pos ; pos++)
used_chars[(int) (uchar) *pos] = 1;
/* Handle other used characters */
for (chr= 0 ; chr < 256 ; chr++)
{
if (! used_chars[chr])
set->next[chr]= chr ? default_state : -1;
else
{
new_set=make_new_set(&sets);
set=sets.set+set_nr; /* if realloc */
new_set->table_offset=set->table_offset;
new_set->found_len=set->found_len;
new_set->found_offset=set->found_offset+1;
found_end=0;
for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; )
{
if (!follow[i].chr || follow[i].chr == chr ||
(follow[i].chr == SPACE_CHAR &&
(is_word_end[chr] ||
(!chr && follow[i].len > 1 && ! follow[i+1].chr))) ||
(follow[i].chr == END_OF_LINE && ! chr))
{
if ((! chr || (follow[i].chr && !follow[i+1].chr)) &&
follow[i].len > found_end)
found_end=follow[i].len;
if (chr && follow[i].chr)
internal_set_bit(new_set,i+1); /* To next set */
else
internal_set_bit(new_set,i);
}
}
if (found_end)
{
new_set->found_len=0; /* Set for testing if first */
bits_set=0;
for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;)
{
if ((follow[i].chr == SPACE_CHAR ||
follow[i].chr == END_OF_LINE) && ! chr)
bit_nr=i+1;
else
bit_nr=i;
if (follow[bit_nr-1].len < found_end ||
(new_set->found_len &&
(chr == 0 || !follow[bit_nr].chr)))
internal_clear_bit(new_set,i);
else
{
if (chr == 0 || !follow[bit_nr].chr)
{ /* best match */
new_set->table_offset=follow[bit_nr].table_offset;
if (chr || (follow[i].chr == SPACE_CHAR ||
follow[i].chr == END_OF_LINE))
new_set->found_offset=found_end; /* New match */
new_set->found_len=found_end;
}
bits_set++;
}
}
if (bits_set == 1)
{
set->next[chr] = find_found(found_set,
new_set->table_offset,
new_set->found_offset);
free_last_set(&sets);
}
else
set->next[chr] = find_set(&sets,new_set);
}
else
set->next[chr] = find_set(&sets,new_set);
}
}
}
/* Alloc replace structure for the replace-state-machine */
if ((replace=(REPLACE*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(REPLACE)*(sets.count)+
sizeof(REPLACE_STRING)*(found_sets+1)+
sizeof(char *)*count+result_len,
MYF(MY_WME|MY_FAE|MY_ZEROFILL))))
{
rep_str=(REPLACE_STRING*) (replace+sets.count);
to_array= (char **) (rep_str+found_sets+1);
to_pos=(char *) (to_array+count);
for (i=0 ; i < count ; i++)
{
to_array[i]=to_pos;
to_pos=strmov(to_pos,to[i])+1;
}
rep_str[0].found=1;
rep_str[0].replace_string=0;
for (i=1 ; i <= found_sets ; i++)
{
pos=from[found_set[i-1].table_offset];
rep_str[i].found= !strncmp(pos, "\\^", 3) ? 2 : 1;
rep_str[i].replace_string=to_array[found_set[i-1].table_offset];
rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos);
rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+
end_of_word(pos);
}
for (i=0 ; i < sets.count ; i++)
{
for (j=0 ; j < 256 ; j++)
if (sets.set[i].next[j] >= 0)
replace[i].next[j]=replace+sets.set[i].next[j];
else
replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1));
}
}
my_free(follow);
free_sets(&sets);
my_free(found_set);
DBUG_PRINT("exit",("Replace table has %d states",sets.count));
DBUG_RETURN(replace);
}
int init_sets(REP_SETS *sets,uint states)
{
bzero(sets, sizeof(*sets));
sets->size_of_bits=((states+7)/8);
if (!(sets->set_buffer=(REP_SET*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(REP_SET)*SET_MALLOC_HUNC,
MYF(MY_WME|MY_FAE))))
return 1;
if (!(sets->bit_buffer=(uint*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(uint)*sets->size_of_bits*
SET_MALLOC_HUNC,MYF(MY_WME|MY_FAE))))
{
my_free(sets->set);
return 1;
}
return 0;
}
while (! bits && ++start < end)
bits=start[0];
if (!bits)
return 0;
pos=(uint) (start-set->bits)*WORD_BIT;
while (! (bits & 1))
{
bits>>=1;
pos++;
}
return pos;
}
/* find if there is a same set in sets. If there is, use it and
free given set, else put in given set in sets and return its
position */
int find_set(REP_SETS *sets,REP_SET *find)
{
uint i;
for (i=0 ; i < sets->count-1 ; i++)
{
if (!cmp_bits(sets->set+i,find))
{
free_last_set(sets);
return i;
}
}
return i; /* return new position */
}
/* find if there is a found_set with same table_offset & found_offset
If there is return offset to it, else add new offset and return pos.
Pos returned is -offset-2 in found_set_structure because it is
saved in set->next and set->next[] >= 0 points to next set and
set->next[] == -1 is reserved for end without replaces.
*/
int find_found(FOUND_SET *found_set,uint table_offset, int found_offset)
{
int i;
for (i=0 ; (uint) i < found_sets ; i++)
if (found_set[i].table_offset == table_offset &&
found_set[i].found_offset == found_offset)
return -i-2;
found_set[i].table_offset=table_offset;
found_set[i].found_offset=found_offset;
found_sets++;
return -i-2; /* return new position */
}
/* Return 1 if regexp starts with \b or ends with \b*/
/* Append uint to ds, with optional replace */
void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val)
{
char buff[22]; /* This should be enough for any int */
char *end= longlong10_to_str(val, buff, 10);
replace_dynstr_append_mem(ds, buff, end - buff);
}
/*
Build a list of pointer to each line in ds_input, sort
the list and use the sorted list to append the strings
sorted to the output ds. The string ds_input needs to
end with a newline.
SYNOPSIS
dynstr_append_sorted()
ds string where the sorted output will be appended
ds_input string to be sorted
*/
static int comp_lines(const void *a_, const void *b_)
{
auto a= static_cast<const char *const *>(a_);
auto b= static_cast<const char *const *>(b_);
return (strcmp(*a,*b));
}
/*
for the purpose of testing (see dialog.test)
we replace default mysql_authentication_dialog_ask function with the one,
that always reads from stdin with explicit echo.
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.