mirror of
https://github.com/MariaDB/server.git
synced 2025-01-31 11:01:52 +01:00
Merge spachev@bk-internal.mysql.com:/home/bk/mysql-5.1-new
into asksasha.com:/reiser-data/mysql-dev/mysql-5.1-new
This commit is contained in:
commit
729f819d42
4 changed files with 519 additions and 1 deletions
|
@ -1611,3 +1611,4 @@ vio/viotest-sslconnect.cpp
|
|||
vio/viotest.cpp
|
||||
zlib/*.ds?
|
||||
zlib/*.vcproj
|
||||
BitKeeper/etc/RESYNC_TREE
|
||||
|
|
|
@ -187,6 +187,35 @@ static test_file* cur_file;
|
|||
static test_file* file_stack_end;
|
||||
uint start_lineno; /* Start line of query */
|
||||
|
||||
/* Stores regex substitutions */
|
||||
|
||||
struct st_regex
|
||||
{
|
||||
char* pattern; /* Pattern to be replaced */
|
||||
char* replace; /* String or expression to replace the pattern with */
|
||||
int icase; /* true if the match is case insensitive */
|
||||
};
|
||||
|
||||
struct st_replace_regex
|
||||
{
|
||||
DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex subsitutions */
|
||||
|
||||
/*
|
||||
Temporary storage areas for substitutions. To reduce unnessary copying
|
||||
and memory freeing/allocation, we pre-allocate two buffers, and alternate
|
||||
their use, one for input/one for output, the roles changing on the next
|
||||
st_regex substition. At the end of substitutions buf points to the
|
||||
one containing the final result.
|
||||
*/
|
||||
char* buf;
|
||||
char* even_buf;
|
||||
uint even_buf_len;
|
||||
char* odd_buf;
|
||||
uint odd_buf_len;
|
||||
};
|
||||
|
||||
struct st_replace_regex *glob_replace_regex= 0;
|
||||
|
||||
static char TMPDIR[FN_REFLEN];
|
||||
static char delimiter[MAX_DELIMITER]= DEFAULT_DELIMITER;
|
||||
static uint delimiter_length= 1;
|
||||
|
@ -226,6 +255,9 @@ static void init_re(void);
|
|||
static int match_re(my_regex_t *, char *);
|
||||
static void free_re(void);
|
||||
|
||||
static int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace,
|
||||
char *string, int icase);
|
||||
|
||||
static const char *embedded_server_groups[]=
|
||||
{
|
||||
"server",
|
||||
|
@ -336,6 +368,7 @@ Q_EXIT,
|
|||
Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT,
|
||||
Q_IF,
|
||||
Q_DISABLE_PARSING, Q_ENABLE_PARSING,
|
||||
Q_REPLACE_REGEX,
|
||||
|
||||
Q_UNKNOWN, /* Unknown command. */
|
||||
Q_COMMENT, /* Comments, ignored. */
|
||||
|
@ -429,6 +462,7 @@ const char *command_names[]=
|
|||
"if",
|
||||
"disable_parsing",
|
||||
"enable_parsing",
|
||||
"replace_regex",
|
||||
0
|
||||
};
|
||||
|
||||
|
@ -469,6 +503,7 @@ struct st_replace *init_replace(my_string *from, my_string *to, uint count,
|
|||
uint replace_strings(struct st_replace *rep, my_string *start,
|
||||
uint *max_length, const char *from);
|
||||
void free_replace();
|
||||
static void free_replace_regex();
|
||||
static int insert_pointer_name(reg1 POINTER_ARRAY *pa,my_string name);
|
||||
void free_pointer_array(POINTER_ARRAY *pa);
|
||||
static int initialize_replace_buffer(void);
|
||||
|
@ -1927,6 +1962,204 @@ static char *get_string(char **to_ptr, char **from_ptr,
|
|||
DBUG_RETURN(start);
|
||||
}
|
||||
|
||||
/*
|
||||
Finds the next (non-escaped) '/' in the expression.
|
||||
(If the character '/' is needed, it can be escaped using '\'.)
|
||||
*/
|
||||
|
||||
#define PARSE_REGEX_ARG \
|
||||
while (p < expr_end) \
|
||||
{\
|
||||
char c= *p;\
|
||||
if (c == '/')\
|
||||
{\
|
||||
if (last_c == '\\')\
|
||||
{\
|
||||
buf_p[-1]= '/';\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
*buf_p++ = 0;\
|
||||
break;\
|
||||
} \
|
||||
} \
|
||||
else\
|
||||
*buf_p++ = c;\
|
||||
\
|
||||
last_c= c;\
|
||||
p++;\
|
||||
} \
|
||||
|
||||
/*
|
||||
Initializes the regular substitution expression to be used in the
|
||||
result output of test.
|
||||
|
||||
Returns: st_replace_regex struct with pairs of substitutions
|
||||
*/
|
||||
|
||||
static struct st_replace_regex* init_replace_regex(char* expr)
|
||||
{
|
||||
struct st_replace_regex* res;
|
||||
char* buf,*expr_end;
|
||||
char* p;
|
||||
char* buf_p;
|
||||
uint expr_len= strlen(expr);
|
||||
char last_c = 0;
|
||||
struct st_regex reg;
|
||||
|
||||
/* my_malloc() will die on fail with MY_FAE */
|
||||
res=(struct st_replace_regex*)my_malloc(
|
||||
sizeof(*res)+expr_len ,MYF(MY_FAE+MY_WME));
|
||||
my_init_dynamic_array(&res->regex_arr,sizeof(struct st_regex),128,128);
|
||||
|
||||
buf= (char*)res + sizeof(*res);
|
||||
expr_end= expr + expr_len;
|
||||
p= expr;
|
||||
buf_p= buf;
|
||||
|
||||
/* for each regexp substitution statement */
|
||||
while (p < expr_end)
|
||||
{
|
||||
bzero(®,sizeof(reg));
|
||||
/* find the start of the statement */
|
||||
while (p < expr_end)
|
||||
{
|
||||
if (*p == '/')
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p == expr_end || ++p == expr_end)
|
||||
{
|
||||
if (res->regex_arr.elements)
|
||||
break;
|
||||
else
|
||||
goto err;
|
||||
}
|
||||
/* we found the start */
|
||||
reg.pattern= buf_p;
|
||||
|
||||
/* Find first argument -- pattern string to be removed */
|
||||
PARSE_REGEX_ARG
|
||||
|
||||
if (p == expr_end || ++p == expr_end)
|
||||
goto err;
|
||||
|
||||
/* buf_p now points to the replacement pattern terminated with \0 */
|
||||
reg.replace= buf_p;
|
||||
|
||||
/* Find second argument -- replace string to replace pattern */
|
||||
PARSE_REGEX_ARG
|
||||
|
||||
if (p == expr_end)
|
||||
goto err;
|
||||
|
||||
/* skip the ending '/' in the statement */
|
||||
p++;
|
||||
|
||||
/* Check if we should do matching case insensitive */
|
||||
if (p < expr_end && *p == 'i')
|
||||
reg.icase= 1;
|
||||
|
||||
/* done parsing the statement, now place it in regex_arr */
|
||||
if (insert_dynamic(&res->regex_arr,(gptr) ®))
|
||||
die("Out of memory");
|
||||
}
|
||||
res->odd_buf_len= res->even_buf_len= 8192;
|
||||
res->even_buf= (char*)my_malloc(res->even_buf_len,MYF(MY_WME+MY_FAE));
|
||||
res->odd_buf= (char*)my_malloc(res->odd_buf_len,MYF(MY_WME+MY_FAE));
|
||||
res->buf= res->even_buf;
|
||||
|
||||
return res;
|
||||
|
||||
err:
|
||||
my_free((gptr)res,0);
|
||||
die("Error parsing replace_regex \"%s\"", expr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Execute all substitutions on val.
|
||||
|
||||
Returns: true if substituition was made, false otherwise
|
||||
Side-effect: Sets r->buf to be the buffer with all substitutions done.
|
||||
|
||||
IN:
|
||||
struct st_replace_regex* r
|
||||
char* val
|
||||
Out:
|
||||
struct st_replace_regex* r
|
||||
r->buf points at the resulting buffer
|
||||
r->even_buf and r->odd_buf might have been reallocated
|
||||
r->even_buf_len and r->odd_buf_len might have been changed
|
||||
|
||||
TODO: at some point figure out if there is a way to do everything
|
||||
in one pass
|
||||
*/
|
||||
|
||||
static int multi_reg_replace(struct st_replace_regex* r,char* val)
|
||||
{
|
||||
uint i;
|
||||
char* in_buf, *out_buf;
|
||||
int* buf_len_p;
|
||||
|
||||
in_buf= val;
|
||||
out_buf= r->even_buf;
|
||||
buf_len_p= &r->even_buf_len;
|
||||
r->buf= 0;
|
||||
|
||||
/* For each substitution, do the replace */
|
||||
for (i= 0; i < r->regex_arr.elements; i++)
|
||||
{
|
||||
struct st_regex re;
|
||||
char* save_out_buf= out_buf;
|
||||
|
||||
get_dynamic(&r->regex_arr,(gptr)&re,i);
|
||||
|
||||
if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace,
|
||||
in_buf, re.icase))
|
||||
{
|
||||
/* if the buffer has been reallocated, make adjustements */
|
||||
if (save_out_buf != out_buf)
|
||||
{
|
||||
if (save_out_buf == r->even_buf)
|
||||
r->even_buf= out_buf;
|
||||
else
|
||||
r->odd_buf= out_buf;
|
||||
}
|
||||
|
||||
r->buf= out_buf;
|
||||
if (in_buf == val)
|
||||
in_buf= r->odd_buf;
|
||||
|
||||
swap_variables(char*,in_buf,out_buf);
|
||||
|
||||
buf_len_p= (out_buf == r->even_buf) ? &r->even_buf_len :
|
||||
&r->odd_buf_len;
|
||||
}
|
||||
}
|
||||
|
||||
return (r->buf == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
Parse the regular expression to be used in all result files
|
||||
from now on.
|
||||
|
||||
The syntax is --replace_regex /from/to/i /from/to/i ...
|
||||
i means case-insensitive match. If omitted, the match is
|
||||
case-sensitive
|
||||
|
||||
*/
|
||||
static void get_replace_regex(struct st_query *q)
|
||||
{
|
||||
char *expr= q->first_argument;
|
||||
free_replace_regex();
|
||||
if (!(glob_replace_regex=init_replace_regex(expr)))
|
||||
die("Could not init replace_regex");
|
||||
q->last_argument= q->end;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get arguments for replace. The syntax is:
|
||||
|
@ -1979,6 +2212,18 @@ static void get_replace(struct st_query *q)
|
|||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
static void free_replace_regex()
|
||||
{
|
||||
if (glob_replace_regex)
|
||||
{
|
||||
my_free(glob_replace_regex->even_buf,MYF(MY_ALLOW_ZERO_PTR));
|
||||
my_free(glob_replace_regex->odd_buf,MYF(MY_ALLOW_ZERO_PTR));
|
||||
my_free((char*) glob_replace_regex,MYF(0));
|
||||
glob_replace_regex=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void free_replace()
|
||||
{
|
||||
DBUG_ENTER("free_replace");
|
||||
|
@ -2277,6 +2522,7 @@ int connect_n_handle_errors(struct st_query *q, MYSQL* con, const char* host,
|
|||
|
||||
err:
|
||||
free_replace();
|
||||
free_replace_regex();
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -3126,6 +3372,214 @@ void dump_result_to_reject_file(const char *record_file, char *buf, int size)
|
|||
str_to_file(fn_format(reject_file, record_file,"",".reject",2), buf, size);
|
||||
}
|
||||
|
||||
static void check_regerr(my_regex_t* r, int err)
|
||||
{
|
||||
char err_buf[1024];
|
||||
|
||||
if (err)
|
||||
{
|
||||
my_regerror(err,r,err_buf,sizeof(err_buf));
|
||||
die("Regex error: %s\n", err_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
auxiluary macro used by reg_replace
|
||||
makes sure the result buffer has sufficient length
|
||||
*/
|
||||
#define SECURE_REG_BUF if (buf_len < need_buf_len)\
|
||||
{\
|
||||
int off= res_p - buf;\
|
||||
buf= (char*)my_realloc(buf,need_buf_len,MYF(MY_WME+MY_FAE));\
|
||||
res_p= buf + off;\
|
||||
buf_len= need_buf_len;\
|
||||
}\
|
||||
|
||||
/*
|
||||
Performs a regex substitution
|
||||
|
||||
IN:
|
||||
|
||||
buf_p - result buffer pointer. Will change if reallocated
|
||||
buf_len_p - result buffer length. Will change if the buffer is reallocated
|
||||
pattern - regexp pattern to match
|
||||
replace - replacement expression
|
||||
string - the string to perform substituions in
|
||||
icase - flag, if set to 1 the match is case insensitive
|
||||
*/
|
||||
static int reg_replace(char** buf_p, int* buf_len_p, char *pattern,
|
||||
char *replace, char *string, int icase)
|
||||
{
|
||||
my_regex_t r;
|
||||
my_regmatch_t* subs;
|
||||
char* buf_end, *replace_end;
|
||||
char* buf= *buf_p;
|
||||
int len;
|
||||
int buf_len,need_buf_len;
|
||||
int cflags= REG_EXTENDED;
|
||||
int err_code;
|
||||
char* res_p,*str_p,*str_end;
|
||||
|
||||
buf_len= *buf_len_p;
|
||||
len= strlen(string);
|
||||
str_end= string + len;
|
||||
|
||||
/* start with a buffer of a reasonable size that hopefully will not
|
||||
need to be reallocated
|
||||
*/
|
||||
need_buf_len= len * 2 + 1;
|
||||
res_p= buf;
|
||||
|
||||
SECURE_REG_BUF
|
||||
|
||||
buf_end = buf + buf_len;
|
||||
|
||||
if (icase)
|
||||
cflags |= REG_ICASE;
|
||||
|
||||
if ((err_code=my_regcomp(&r,pattern,cflags,&my_charset_latin1)))
|
||||
{
|
||||
check_regerr(&r,err_code);
|
||||
return 1;
|
||||
}
|
||||
|
||||
subs= (my_regmatch_t*)my_malloc(sizeof(my_regmatch_t) * (r.re_nsub+1),
|
||||
MYF(MY_WME+MY_FAE));
|
||||
|
||||
*res_p= 0;
|
||||
str_p= string;
|
||||
replace_end= replace + strlen(replace);
|
||||
|
||||
/* for each pattern match instance perform a replacement */
|
||||
while (!err_code)
|
||||
{
|
||||
/* find the match */
|
||||
err_code= my_regexec(&r,str_p, r.re_nsub+1, subs,
|
||||
(str_p == string) ? REG_NOTBOL : 0);
|
||||
|
||||
/* if regular expression error (eg. bad syntax, or out of memory) */
|
||||
if (err_code && err_code != REG_NOMATCH)
|
||||
{
|
||||
check_regerr(&r,err_code);
|
||||
my_regfree(&r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* if match found */
|
||||
if (!err_code)
|
||||
{
|
||||
char* expr_p= replace;
|
||||
int c;
|
||||
|
||||
/*
|
||||
we need at least what we have so far in the buffer + the part
|
||||
before this match
|
||||
*/
|
||||
need_buf_len= (res_p - buf) + subs[0].rm_so;
|
||||
|
||||
/* on this pass, calculate the memory for the result buffer */
|
||||
while (expr_p < replace_end)
|
||||
{
|
||||
int back_ref_num= -1;
|
||||
c= *expr_p;
|
||||
|
||||
if (c == '\\' && expr_p + 1 < replace_end)
|
||||
{
|
||||
back_ref_num= expr_p[1] - '0';
|
||||
}
|
||||
|
||||
/* found a valid back_ref (eg. \1)*/
|
||||
if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub)
|
||||
{
|
||||
int start_off,end_off;
|
||||
if ((start_off=subs[back_ref_num].rm_so) > -1 &&
|
||||
(end_off=subs[back_ref_num].rm_eo) > -1)
|
||||
{
|
||||
need_buf_len += (end_off - start_off);
|
||||
}
|
||||
expr_p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
expr_p++;
|
||||
need_buf_len++;
|
||||
}
|
||||
}
|
||||
need_buf_len++;
|
||||
/*
|
||||
now that we know the size of the buffer,
|
||||
make sure it is big enough
|
||||
*/
|
||||
SECURE_REG_BUF
|
||||
|
||||
/* copy the pre-match part */
|
||||
if (subs[0].rm_so)
|
||||
{
|
||||
memcpy(res_p,str_p,subs[0].rm_so);
|
||||
res_p += subs[0].rm_so;
|
||||
}
|
||||
|
||||
expr_p= replace;
|
||||
|
||||
/* copy the match and expand back_refs */
|
||||
while (expr_p < replace_end)
|
||||
{
|
||||
int back_ref_num= -1;
|
||||
c= *expr_p;
|
||||
|
||||
if (c == '\\' && expr_p + 1 < replace_end)
|
||||
{
|
||||
back_ref_num= expr_p[1] - '0';
|
||||
}
|
||||
|
||||
if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub)
|
||||
{
|
||||
int start_off,end_off;
|
||||
if ((start_off=subs[back_ref_num].rm_so) > -1 &&
|
||||
(end_off=subs[back_ref_num].rm_eo) > -1)
|
||||
{
|
||||
int block_len= end_off - start_off;
|
||||
memcpy(res_p,str_p + start_off, block_len);
|
||||
res_p += block_len;
|
||||
}
|
||||
expr_p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
*res_p++ = *expr_p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* handle the post-match part */
|
||||
if (subs[0].rm_so == subs[0].rm_eo)
|
||||
{
|
||||
if (str_p + subs[0].rm_so >= str_end)
|
||||
break;
|
||||
str_p += subs[0].rm_eo ;
|
||||
*res_p++ = *str_p++;
|
||||
}
|
||||
else
|
||||
{
|
||||
str_p += subs[0].rm_eo;
|
||||
}
|
||||
}
|
||||
else /* no match this time, just copy the string as is */
|
||||
{
|
||||
int left_in_str= str_end-str_p;
|
||||
need_buf_len= (res_p-buf) + left_in_str;
|
||||
SECURE_REG_BUF
|
||||
memcpy(res_p,str_p,left_in_str);
|
||||
res_p += left_in_str;
|
||||
str_p= str_end;
|
||||
}
|
||||
}
|
||||
my_regfree(&r);
|
||||
*res_p= 0;
|
||||
*buf_p= buf;
|
||||
*buf_len_p= buf_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Append the string to ds, with optional replace */
|
||||
|
||||
|
@ -3139,6 +3593,16 @@ static void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val,
|
|||
die("Out of memory in replace");
|
||||
val=out_buff;
|
||||
}
|
||||
|
||||
if (glob_replace_regex)
|
||||
{
|
||||
if (!multi_reg_replace(glob_replace_regex,(char*)val))
|
||||
{
|
||||
val= glob_replace_regex->buf;
|
||||
len= strlen(val);
|
||||
}
|
||||
}
|
||||
|
||||
dynstr_append_mem(ds, val, len);
|
||||
}
|
||||
|
||||
|
@ -3577,6 +4041,7 @@ static void run_query_normal(MYSQL *mysql, struct st_query *command,
|
|||
|
||||
end:
|
||||
free_replace();
|
||||
free_replace_regex();
|
||||
|
||||
/*
|
||||
We save the return code (mysql_errno(mysql)) from the last call sent
|
||||
|
@ -3887,7 +4352,8 @@ static void run_query_stmt(MYSQL *mysql, struct st_query *command,
|
|||
|
||||
end:
|
||||
free_replace();
|
||||
|
||||
free_replace_regex();
|
||||
|
||||
if (!disable_warnings)
|
||||
{
|
||||
dynstr_free(&ds_prepare_warnings);
|
||||
|
@ -3899,6 +4365,7 @@ end:
|
|||
to the server into the mysqltest builtin variable $mysql_errno. This
|
||||
variable then can be used from the test case itself.
|
||||
*/
|
||||
|
||||
var_set_errno(mysql_stmt_errno(stmt));
|
||||
#ifndef BUG15518_FIXED
|
||||
mysql_stmt_close(stmt);
|
||||
|
@ -4623,6 +5090,10 @@ int main(int argc, char **argv)
|
|||
case Q_REPLACE:
|
||||
get_replace(q);
|
||||
break;
|
||||
case Q_REPLACE_REGEX:
|
||||
get_replace_regex(q);
|
||||
break;
|
||||
|
||||
case Q_REPLACE_COLUMN:
|
||||
get_replace_column(q);
|
||||
break;
|
||||
|
|
|
@ -419,3 +419,22 @@ select-me
|
|||
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insertz error query' at line 1
|
||||
drop table t1;
|
||||
drop table t1;
|
||||
select "b" as col1, "c" as col2;
|
||||
col1 col2
|
||||
b c
|
||||
select "b" as col1, "b" as col2, "c" as col3;
|
||||
col1 col2 col3
|
||||
b b c
|
||||
seled "b" bs col1, "d" bs col2;
|
||||
col1 col2
|
||||
b d
|
||||
select "raspberry and strawberry","blackberry","tomato";
|
||||
raspberry and strawberry blackberry tomato
|
||||
raspberry and strawberry blackberry tomato
|
||||
mysqltest: At line 1: Error parsing replace_regex "a"
|
||||
mysqltest: At line 1: Error parsing replace_regex "a;"
|
||||
mysqltest: At line 1: Error parsing replace_regex "a"
|
||||
mysqltest: At line 1: Error parsing replace_regex "a "
|
||||
mysqltest: At line 1: Error parsing replace_regex "a b"
|
||||
mysqltest: At line 1: Error parsing replace_regex "/a b c"
|
||||
mysqltest: At line 1: Error parsing replace_regex "/a /b c "
|
||||
|
|
|
@ -1061,3 +1061,30 @@ drop table t1;
|
|||
drop table t1;
|
||||
|
||||
|
||||
# test for replace_regex
|
||||
--replace_regex /at/b/
|
||||
select "at" as col1, "c" as col2;
|
||||
|
||||
--replace_regex /at/b/i
|
||||
select "at" as col1, "AT" as col2, "c" as col3;
|
||||
|
||||
--replace_regex /a/b/ /ct/d/
|
||||
select "a" as col1, "ct" as col2;
|
||||
|
||||
--replace_regex /(strawberry)/raspberry and \1/ /blueberry/blackberry/ /potato/tomato/;
|
||||
select "strawberry","blueberry","potato";
|
||||
|
||||
--error 1
|
||||
--exec echo "--replace_regex a" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "--replace_regex a;" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "replace_regex a;" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "replace_regex a ;" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "replace_regex a b; echo OK;" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "--replace_regex /a b c" | $MYSQL_TEST 2>&1
|
||||
--error 1
|
||||
--exec echo "replace_regex /a /b c ;" | $MYSQL_TEST 2>&1
|
||||
|
|
Loading…
Add table
Reference in a new issue