Commit 29d9aa65 authored by monty@mishka.local's avatar monty@mishka.local

Merge bk-internal.mysql.com:/home/bk/mysql-4.1

into  mishka.local:/home/my/mysql-4.1
parents c9abae2c 7e83d09c
......@@ -972,7 +972,8 @@ do { doubleget_union _tmp; \
#define float4get(V,M) do { *((long *) &(V)) = *((long*) (M)); } while(0)
#define float8get(V,M) doubleget((V),(M))
#define float4store(V,M) memcpy((byte*) V,(byte*) (&M),sizeof(float))
#define floatstore(T,V) memcpy((byte*)(T), (byte*)(&V), sizeof(float))
#define floatstore(T,V) memcpy((byte*)(T), (byte*)(&V),sizeof(float))
#define floatget(V,M) memcpy((byte*) &V,(byte*) (M),sizeof(float))
#define float8store(V,M) doublestore((V),(M))
#endif /* __i386__ */
......@@ -1143,7 +1144,8 @@ do { doubleget_union _tmp; \
*(((char*)T)+1)=(((A) >> 16));\
*(((char*)T)+0)=(((A) >> 24)); } while(0)
#define floatstore(T,V) memcpy_fixed((byte*)(T), (byte*)(&V), sizeof(float))
#define floatget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(float))
#define floatstore(T,V) memcpy_fixed((byte*) (T),(byte*)(&V),sizeof(float))
#define doubleget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(double))
#define doublestore(T,V) memcpy_fixed((byte*) (T),(byte*) &V,sizeof(double))
#define longlongget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(ulonglong))
......@@ -1158,7 +1160,8 @@ do { doubleget_union _tmp; \
#define shortstore(T,V) int2store(T,V)
#define longstore(T,V) int4store(T,V)
#ifndef floatstore
#define floatstore(T,V) memcpy_fixed((byte*)(T), (byte*)(&V), sizeof(float))
#define floatstore(T,V) memcpy_fixed((byte*) (T),(byte*) (&V),sizeof(float))
#define floatget(V,M) memcpy_fixed((byte*) &V, (byte*) (M), sizeof(float))
#endif
#ifndef doubleget
#define doubleget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(double))
......
......@@ -2594,7 +2594,26 @@ INSERT INTO t1
SELECT 50, 3, 3 FROM DUAL
WHERE NOT EXISTS
(SELECT * FROM t1 WHERE a = 50 AND b = 3);
select found_rows();
found_rows()
0
SELECT * FROM t1;
a b c
50 3 3
select count(*) from t1;
count(*)
1
select found_rows();
found_rows()
1
select count(*) from t1 limit 2,3;
count(*)
select found_rows();
found_rows()
0
select SQL_CALC_FOUND_ROWS count(*) from t1 limit 2,3;
count(*)
select found_rows();
found_rows()
1
DROP TABLE t1;
......@@ -2153,6 +2153,13 @@ INSERT INTO t1
SELECT 50, 3, 3 FROM DUAL
WHERE NOT EXISTS
(SELECT * FROM t1 WHERE a = 50 AND b = 3);
select found_rows();
SELECT * FROM t1;
select count(*) from t1;
select found_rows();
select count(*) from t1 limit 2,3;
select found_rows();
select SQL_CALC_FOUND_ROWS count(*) from t1 limit 2,3;
select found_rows();
DROP TABLE t1;
......@@ -21,18 +21,6 @@
struct st_des_keyschedule des_keyschedule[10];
uint des_default_key;
pthread_mutex_t LOCK_des_key_file;
static int initialized= 0;
void
init_des_key_file()
{
if (!initialized)
{
initialized=1;
pthread_mutex_init(&LOCK_des_key_file,MY_MUTEX_INIT_FAST);
}
}
/*
Function which loads DES keys from plaintext file into memory on MySQL
......@@ -55,8 +43,6 @@ load_des_key_file(const char *file_name)
DBUG_ENTER("load_des_key_file");
DBUG_PRINT("enter",("name: %s",file_name));
init_des_key_file();
VOID(pthread_mutex_lock(&LOCK_des_key_file));
if ((file=my_open(file_name,O_RDONLY | O_BINARY ,MYF(MY_WME))) < 0 ||
init_io_cache(&io, file, IO_SIZE*2, READ_CACHE, 0, 0, MYF(MY_WME)))
......@@ -113,15 +99,4 @@ load_des_key_file(const char *file_name)
VOID(pthread_mutex_unlock(&LOCK_des_key_file));
DBUG_RETURN(result);
}
void free_des_key_file()
{
if (initialized)
{
initialized= 01;
pthread_mutex_destroy(&LOCK_des_key_file);
}
}
#endif /* HAVE_OPENSSL */
......@@ -324,7 +324,34 @@ static void do_field_real(Copy_field *copy)
}
/*
string copy for single byte characters set when to string is shorter than
from string
*/
static void do_cut_string(Copy_field *copy)
{
CHARSET_INFO *cs= copy->from_field->charset();
memcpy(copy->to_ptr,copy->from_ptr,copy->to_length);
/* Check if we loosed any important characters */
if (cs->cset->scan(cs,
copy->from_ptr + copy->to_length,
copy->from_ptr + copy->from_length,
MY_SEQ_SPACES) < copy->from_length - copy->to_length)
{
copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARN_DATA_TRUNCATED, 1);
}
}
/*
string copy for multi byte characters set when to string is shorter than
from string
*/
static void do_cut_string_complex(Copy_field *copy)
{ // Shorter string field
int well_formed_error;
CHARSET_INFO *cs= copy->from_field->charset();
......@@ -351,6 +378,8 @@ static void do_cut_string(Copy_field *copy)
}
static void do_expand_string(Copy_field *copy)
{
CHARSET_INFO *cs= copy->from_field->charset();
......@@ -517,7 +546,8 @@ void (*Copy_field::get_copy_func(Field *to,Field *from))(Copy_field*)
from_length)
return do_varstring;
else if (to_length < from_length)
return do_cut_string;
return (from->charset()->mbmaxlen == 1 ?
do_cut_string : do_cut_string_complex);
else if (to_length > from_length)
return do_expand_string;
}
......
......@@ -3045,14 +3045,15 @@ Item_func_get_system_var(sys_var *var_arg, enum_var_type var_type_arg,
bool
Item_func_get_system_var::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref)
{
Item *item= var->item(thd, var_type, &component);
Item *item;
DBUG_ENTER("Item_func_get_system_var::fix_fields");
/*
Evaluate the system variable and substitute the result (a basic constant)
instead of this item. If the variable can not be evaluated,
the error is reported in sys_var::item().
*/
if (item == 0)
if (!(item= var->item(thd, var_type, &component)))
DBUG_RETURN(1); // Impossible
item->set_name(name, 0, system_charset_info); // don't allocate a new name
thd->change_item_tree(ref, item);
......
......@@ -345,19 +345,20 @@ String *Item_func_concat::val_str(String *str)
void Item_func_concat::fix_length_and_dec()
{
max_length=0;
ulonglong max_result_length= 0;
if (agg_arg_charsets(collation, args, arg_count, MY_COLL_ALLOW_CONV))
return;
for (uint i=0 ; i < arg_count ; i++)
max_length+=args[i]->max_length;
max_result_length+= args[i]->max_length;
if (max_length > MAX_BLOB_WIDTH)
if (max_result_length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_result_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) max_result_length;
}
/*
......@@ -388,9 +389,6 @@ String *Item_func_des_encrypt::val_str(String *str)
if (arg_count == 1)
{
/* Make sure LOCK_des_key_file was initialized. */
init_des_key_file();
/* Protect against someone doing FLUSH DES_KEY_FILE */
VOID(pthread_mutex_lock(&LOCK_des_key_file));
keyschedule= des_keyschedule[key_number=des_default_key];
......@@ -401,10 +399,6 @@ String *Item_func_des_encrypt::val_str(String *str)
key_number= (uint) args[1]->val_int();
if (key_number > 9)
goto error;
/* Make sure LOCK_des_key_file was initialized. */
init_des_key_file();
VOID(pthread_mutex_lock(&LOCK_des_key_file));
keyschedule= des_keyschedule[key_number];
VOID(pthread_mutex_unlock(&LOCK_des_key_file));
......@@ -493,9 +487,6 @@ String *Item_func_des_decrypt::val_str(String *str)
if (!(current_thd->master_access & SUPER_ACL) || key_number > 9)
goto error;
/* Make sure LOCK_des_key_file was initialized. */
init_des_key_file();
VOID(pthread_mutex_lock(&LOCK_des_key_file));
keyschedule= des_keyschedule[key_number];
VOID(pthread_mutex_unlock(&LOCK_des_key_file));
......@@ -669,7 +660,7 @@ String *Item_func_concat_ws::val_str(String *str)
void Item_func_concat_ws::fix_length_and_dec()
{
max_length=0;
ulonglong max_result_length;
if (agg_arg_charsets(collation, args, arg_count, MY_COLL_ALLOW_CONV))
return;
......@@ -679,15 +670,16 @@ void Item_func_concat_ws::fix_length_and_dec()
it is done on parser level in sql_yacc.yy
so, (arg_count - 2) is safe here.
*/
max_length= args[0]->max_length * (arg_count - 2);
max_result_length= (ulonglong) args[0]->max_length * (arg_count - 2);
for (uint i=1 ; i < arg_count ; i++)
max_length+=args[i]->max_length;
max_result_length+=args[i]->max_length;
if (max_length > MAX_BLOB_WIDTH)
if (max_result_length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_result_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) max_result_length;
}
......@@ -866,18 +858,19 @@ String *Item_func_replace::val_str(String *str)
void Item_func_replace::fix_length_and_dec()
{
max_length=args[0]->max_length;
ulonglong max_result_length= args[0]->max_length;
int diff=(int) (args[2]->max_length - args[1]->max_length);
if (diff > 0 && args[1]->max_length)
{ // Calculate of maxreplaces
uint max_substrs= max_length/args[1]->max_length;
max_length+= max_substrs * (uint) diff;
ulonglong max_substrs= max_result_length/args[1]->max_length;
max_result_length+= max_substrs * (uint) diff;
}
if (max_length > MAX_BLOB_WIDTH)
if (max_result_length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_result_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) max_result_length;
if (agg_arg_charsets(collation, args, 3, MY_COLL_CMP_CONV))
return;
......@@ -925,18 +918,22 @@ String *Item_func_insert::val_str(String *str)
void Item_func_insert::fix_length_and_dec()
{
Item *cargs[2];
ulonglong max_result_length;
cargs[0]= args[0];
cargs[1]= args[3];
if (agg_arg_charsets(collation, cargs, 2, MY_COLL_ALLOW_CONV))
return;
args[0]= cargs[0];
args[3]= cargs[1];
max_length=args[0]->max_length+args[3]->max_length;
if (max_length > MAX_BLOB_WIDTH)
max_result_length= ((ulonglong) args[0]->max_length+
(ulonglong) args[3]->max_length);
if (max_result_length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_result_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) max_result_length;
}
......@@ -1967,17 +1964,19 @@ void Item_func_repeat::fix_length_and_dec()
collation.set(args[0]->collation);
if (args[1]->const_item())
{
max_length=(long) (args[0]->max_length * args[1]->val_int());
if (max_length >= MAX_BLOB_WIDTH)
ulonglong max_result_length= ((ulonglong) args[0]->max_length *
args[1]->val_int());
if (max_result_length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_result_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) max_result_length;
}
else
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
}
......@@ -2032,6 +2031,7 @@ String *Item_func_repeat::val_str(String *str)
void Item_func_rpad::fix_length_and_dec()
{
Item *cargs[2];
cargs[0]= args[0];
cargs[1]= args[2];
if (agg_arg_charsets(collation, cargs, 2, MY_COLL_ALLOW_CONV))
......@@ -2040,18 +2040,20 @@ void Item_func_rpad::fix_length_and_dec()
args[2]= cargs[1];
if (args[1]->const_item())
{
uint32 length= (uint32) args[1]->val_int() * collation.collation->mbmaxlen;
max_length=max(args[0]->max_length,length);
if (max_length >= MAX_BLOB_WIDTH)
ulonglong length= ((ulonglong) args[1]->val_int() *
collation.collation->mbmaxlen);
length= max((ulonglong) args[0]->max_length, length);
if (length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) length;
}
else
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
}
......@@ -2126,18 +2128,20 @@ void Item_func_lpad::fix_length_and_dec()
if (args[1]->const_item())
{
uint32 length= (uint32) args[1]->val_int() * collation.collation->mbmaxlen;
max_length=max(args[0]->max_length,length);
if (max_length >= MAX_BLOB_WIDTH)
ulonglong length= ((ulonglong) args[1]->val_int() *
collation.collation->mbmaxlen);
length= max((ulonglong) args[0]->max_length, length);
if (length >= MAX_BLOB_WIDTH)
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
max_length= (ulong) length;
}
else
{
max_length=MAX_BLOB_WIDTH;
maybe_null=1;
max_length= MAX_BLOB_WIDTH;
maybe_null= 1;
}
}
......
......@@ -1538,7 +1538,7 @@ void Item_func_date_format::fix_length_and_dec()
{
fixed_length=0;
/* The result is a binary string (no reason to use collation->mbmaxlen */
max_length=args[1]->max_length*10;
max_length=min(args[1]->max_length,MAX_BLOB_WIDTH) * 10;
set_if_smaller(max_length,MAX_BLOB_WIDTH);
}
maybe_null=1; // If wrong date
......
......@@ -1810,24 +1810,13 @@ int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli,
thd->query= load_data_query;
}
/*
We need to set thd->lex->sql_command and thd->lex->duplicates
since InnoDB tests these variables to decide if this is a LOAD
DATA ... REPLACE INTO ... statement even though mysql_parse()
is not called. This is not needed in 5.0 since there the LOAD
DATA ... statement is replicated using mysql_parse(), which
sets the thd->lex fields correctly.
*/
thd->lex->sql_command= SQLCOM_LOAD;
if (sql_ex.opt_flags & REPLACE_FLAG)
{
thd->lex->duplicates= DUP_REPLACE;
handle_dup= DUP_REPLACE;
}
else if (sql_ex.opt_flags & IGNORE_FLAG)
{
ignore= 1;
thd->lex->duplicates= DUP_ERROR;
handle_dup= DUP_ERROR;
}
else
......@@ -1845,9 +1834,18 @@ int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli,
If reading from net (a 3.23 master), mysql_load() will change this
to IGNORE.
*/
thd->lex->duplicates= DUP_ERROR;
handle_dup= DUP_ERROR;
}
/*
We need to set thd->lex->sql_command and thd->lex->duplicates
since InnoDB tests these variables to decide if this is a LOAD
DATA ... REPLACE INTO ... statement even though mysql_parse()
is not called. This is not needed in 5.0 since there the LOAD
DATA ... statement is replicated using mysql_parse(), which
sets the thd->lex fields correctly.
*/
thd->lex->sql_command= SQLCOM_LOAD;
thd->lex->duplicates= handle_dup;
sql_exchange ex((char*)fname, sql_ex.opt_flags & DUMPFILE_FLAG);
String field_term(sql_ex.field_term,sql_ex.field_term_len,log_cs);
......
......@@ -631,9 +631,7 @@ extern char *des_key_file;
extern struct st_des_keyschedule des_keyschedule[10];
extern uint des_default_key;
extern pthread_mutex_t LOCK_des_key_file;
void init_des_key_file();
bool load_des_key_file(const char *file_name);
void free_des_key_file();
#endif /* HAVE_OPENSSL */
/* sql_do.cc */
......@@ -943,6 +941,9 @@ extern pthread_mutex_t LOCK_mysql_create_db,LOCK_Acl,LOCK_open,
LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
LOCK_slave_list, LOCK_active_mi, LOCK_manager,
LOCK_global_system_variables, LOCK_user_conn;
#ifdef HAVE_OPENSSL
extern pthread_mutex_t LOCK_des_key_file;
#endif
extern rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
extern pthread_cond_t COND_refresh, COND_thread_count, COND_manager;
extern pthread_attr_t connection_attrib;
......
......@@ -417,6 +417,9 @@ pthread_mutex_t LOCK_mysql_create_db, LOCK_Acl, LOCK_open, LOCK_thread_count,
LOCK_crypt, LOCK_bytes_sent, LOCK_bytes_received,
LOCK_global_system_variables,
LOCK_user_conn, LOCK_slave_list, LOCK_active_mi;
#ifdef HAVE_OPENSSL
pthread_mutex_t LOCK_des_key_file;
#endif
rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
pthread_cond_t COND_refresh,COND_thread_count, COND_slave_stopped,
COND_slave_start;
......@@ -647,7 +650,11 @@ static void close_connections(void)
end_thr_alarm(0); // Abort old alarms.
end_slave();
/* First signal all threads that it's time to die */
/*
First signal all threads that it's time to die
This will give the threads some time to gracefully abort their
statements and inform their clients that the server is about to die.
*/
THD *tmp;
(void) pthread_mutex_lock(&LOCK_thread_count); // For unlink from list
......@@ -657,13 +664,7 @@ static void close_connections(void)
{
DBUG_PRINT("quit",("Informing thread %ld that it's time to die",
tmp->thread_id));
/*
Re: bug 7403 - close_connection will be called mulitple times
bug a wholesale clean up of our network code is a very large
project. This will wake up the socket on Windows and prevent the
printing of the error message that we are force closing a connection.
*/
close_connection(tmp, 0, 0);
tmp->killed= 1;
if (tmp->mysys_var)
{
tmp->mysys_var->abort=1;
......@@ -680,9 +681,13 @@ static void close_connections(void)
(void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list
if (thread_count)
sleep(1); // Give threads time to die
sleep(2); // Give threads time to die
/* Force remaining threads to die by closing the connection to the client */
/*
Force remaining threads to die by closing the connection to the client
This will ensure that threads that are waiting for a command from the
client on a blocking read call are aborted.
*/
for (;;)
{
......@@ -697,8 +702,9 @@ static void close_connections(void)
#ifndef __bsdi__ // Bug in BSDI kernel
if (tmp->vio_ok())
{
sql_print_error(ER(ER_FORCING_CLOSE),my_progname,
tmp->thread_id,tmp->user ? tmp->user : "");
if (global_system_variables.log_warnings)
sql_print_warning(ER(ER_FORCING_CLOSE),my_progname,
tmp->thread_id,tmp->user ? tmp->user : "");
close_connection(tmp,0,0);
}
#endif
......@@ -1012,7 +1018,6 @@ void clean_up(bool print_message)
#ifdef HAVE_OPENSSL
if (ssl_acceptor_fd)
my_free((gptr) ssl_acceptor_fd, MYF(MY_ALLOW_ZERO_PTR));
free_des_key_file();
#endif /* HAVE_OPENSSL */
#ifdef USE_REGEX
regex_end();
......@@ -1082,6 +1087,9 @@ static void clean_up_mutexes()
(void) pthread_mutex_destroy(&LOCK_bytes_sent);
(void) pthread_mutex_destroy(&LOCK_bytes_received);
(void) pthread_mutex_destroy(&LOCK_user_conn);
#ifdef HAVE_OPENSSL
(void) pthread_mutex_destroy(&LOCK_des_key_file);
#endif
#ifdef HAVE_REPLICATION
(void) pthread_mutex_destroy(&LOCK_rpl_status);
(void) pthread_cond_destroy(&COND_rpl_status);
......@@ -2585,6 +2593,9 @@ static int init_thread_environment()
(void) pthread_mutex_init(&LOCK_active_mi, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_global_system_variables, MY_MUTEX_INIT_FAST);
(void) pthread_mutex_init(&LOCK_uuid_generator, MY_MUTEX_INIT_FAST);
#ifdef HAVE_OPENSSL
(void) pthread_mutex_init(&LOCK_des_key_file,MY_MUTEX_INIT_FAST);
#endif
(void) my_rwlock_init(&LOCK_sys_init_connect, NULL);
(void) my_rwlock_init(&LOCK_sys_init_slave, NULL);
(void) my_rwlock_init(&LOCK_grant, NULL);
......
......@@ -312,7 +312,7 @@ static void set_param_float(Item_param *param, uchar **pos, ulong len)
return;
float4get(data,*pos);
#else
data= *(float*) *pos;
floatget(data, *pos);
#endif
param->set_double((double) data);
*pos+= 4;
......@@ -326,7 +326,7 @@ static void set_param_double(Item_param *param, uchar **pos, ulong len)
return;
float8get(data,*pos);
#else
data= *(double*) *pos;
doubleget(data, *pos);
#endif
param->set_double((double) data);
*pos+= 8;
......@@ -580,10 +580,8 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array,
Item_param **begin= stmt->param_array;
Item_param **end= begin + stmt->param_count;
uint32 length= 0;
String str;
const String *res;
DBUG_ENTER("insert_params_withlog");
if (query->copy(stmt->query, stmt->query_length, default_charset_info))
......
......@@ -1081,14 +1081,18 @@ JOIN::exec()
else
{
error= (int) result->send_eof();
send_records=1;
send_records= ((select_options & OPTION_FOUND_ROWS) ? 1 :
thd->sent_row_count);
}
}
else
{
error=(int) result->send_eof();
send_records= 0;
}
}
/* Single select (without union and limit) always returns 1 row */
thd->limit_found_rows= 1;
/* Single select (without union) always returns 0 or 1 row */
thd->limit_found_rows= send_records;
thd->examined_row_count= 0;
DBUG_VOID_RETURN;
}
......@@ -3552,6 +3556,7 @@ static void add_not_null_conds(JOIN *join)
if (tab->ref.null_rejecting & (1 << keypart))
{
Item *item= tab->ref.items[keypart];
Item *notnull;
DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
Item_field *not_null_item= (Item_field*)item;
JOIN_TAB *referred_tab= not_null_item->field->table->reginfo.join_tab;
......@@ -3562,7 +3567,6 @@ static void add_not_null_conds(JOIN *join)
*/
if (!referred_tab || referred_tab->join != join)
continue;
Item *notnull;
if (!(notnull= new Item_func_isnotnull(not_null_item)))
DBUG_VOID_RETURN;
/*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment