Commit c38bb3d7 authored by pem@mysql.comhem.se's avatar pem@mysql.comhem.se

Various bug fixes:

  - Duplicate parameters/variables, conditions and cursors (not allowed).
  - ITERATE in labelled BEGIN-END (not allowed).
  - Missing SQLSTATE [VALUE] keywords in CONDITION/HANDLER declaration (added).
  - Empty BEGIN-END (now allowed).
  - End label (now optional).
parent d4b89f42
......@@ -323,4 +323,5 @@
#define ER_SP_UNDECLARED_VAR 1304
#define ER_SP_WRONG_NO_OF_FETCH_ARGS 1305
#define ER_SP_FETCH_NO_DATA 1306
#define ER_ERROR_MESSAGES 307
#define ER_SP_DUP_THING 1307
#define ER_ERROR_MESSAGES 308
......@@ -47,6 +47,11 @@ iterate bar;
end loop;
ERROR HY000: ITERATE with no matching label: bar
create procedure foo()
foo: begin
iterate foo;
end;
ERROR HY000: ITERATE with no matching label: foo
create procedure foo()
foo: loop
foo: loop
set @x=2;
......@@ -219,4 +224,30 @@ end;
call p();
ERROR HY000: Wrong number of FETCH variables
drop procedure p;
create procedure p(in x int, x char(10))
begin
end;
ERROR HY000: Duplicate parameter: x
create function p(x int, x char(10))
begin
end;
ERROR HY000: Duplicate parameter: x
create procedure p()
begin
declare x float;
declare x int;
end;
ERROR HY000: Duplicate parameter: x
create procedure p()
begin
declare c condition for 1064;
declare c condition for 1065;
end;
ERROR HY000: Duplicate condition: c
create procedure p()
begin
declare c cursor for select * from t1;
declare c cursor for select field from t1;
end;
ERROR HY000: Duplicate cursor: c
drop table t1;
......@@ -36,6 +36,20 @@ select * from t1;
id data
bar 666
delete from t1;
create procedure empty()
begin
end;
call empty();
drop procedure empty;
create procedure scope(a int, b float)
begin
declare b int;
declare c float;
begin
declare c int;
end;
end;
drop procedure scope;
create procedure two(x1 char(16), x2 char(16), y int)
begin
insert into test.t1 values (x1, y);
......@@ -256,7 +270,7 @@ insert into test.t1 values ("d", x);
set x = x-1;
leave hmm;
insert into test.t1 values ("x", x);
end while hmm;
end while;
call d(3);
select * from t1;
id data
......@@ -335,6 +349,21 @@ h1 1
h? 17
delete from t1;
drop procedure h;
create procedure i(x int)
foo:
begin
if x = 0 then
leave foo;
end if;
insert into test.t1 values ("i", x);
end foo;
call i(0);
call i(3);
select * from t1;
id data
i 3
delete from t1;
drop procedure i;
create procedure into_test(x char(16), y int)
begin
insert into test.t1 values (x, y);
......@@ -461,6 +490,8 @@ create procedure hndlr1(val int)
begin
declare x int default 0;
declare foo condition for 1146;
declare bar condition for sqlstate '42S98'; # Just for testing syntax
declare zip condition for sqlstate value '42S99'; # Just for testing syntax
declare continue handler for foo set x = 1;
insert into test.t666 values ("hndlr1", val); # Non-existing table
if (x) then
......@@ -477,7 +508,7 @@ create procedure hndlr2(val int)
begin
declare x int default 0;
begin
declare exit handler for '42S02' set x = 1;
declare exit handler for sqlstate '42S02' set x = 1;
insert into test.t666 values ("hndlr2", val); # Non-existing table
end;
insert into test.t1 values ("hndlr2", x);
......@@ -744,7 +775,7 @@ end if;
set s = s+1;
end;
end if;
end loop again;
end loop;
end;
create procedure ip(m int unsigned)
begin
......
......@@ -74,6 +74,11 @@ create procedure foo()
foo: loop
iterate bar;
end loop|
--error 1285
create procedure foo()
foo: begin
iterate foo;
end|
# Redefining label
--error 1286
......@@ -298,6 +303,33 @@ end|
call p()|
drop procedure p|
--error 1307
create procedure p(in x int, x char(10))
begin
end|
--error 1307
create function p(x int, x char(10))
begin
end|
--error 1307
create procedure p()
begin
declare x float;
declare x int;
end|
--error 1307
create procedure p()
begin
declare c condition for 1064;
declare c condition for 1065;
end|
--error 1307
create procedure p()
begin
declare c cursor for select * from t1;
declare c cursor for select field from t1;
end|
drop table t1|
delimiter ;|
......@@ -59,6 +59,28 @@ delete from t1;
# Now for multiple statements...
delimiter |;
# Empty statement
create procedure empty()
begin
end|
call empty()|
drop procedure empty|
# Scope test. This is legal (warnings might be possible in the future,
# but for the time being, we just accept it).
create procedure scope(a int, b float)
begin
declare b int;
declare c float;
begin
declare c int;
end;
end|
drop procedure scope|
# Two statements.
create procedure two(x1 char(16), x2 char(16), y int)
begin
......@@ -313,7 +335,7 @@ hmm: while x > 0 do
set x = x-1;
leave hmm;
insert into test.t1 values ("x", x);
end while hmm|
end while|
call d(3)|
select * from t1|
......@@ -393,6 +415,23 @@ delete from t1|
drop procedure h|
# It's actually possible to LEAVE a BEGIN-END block
create procedure i(x int)
foo:
begin
if x = 0 then
leave foo;
end if;
insert into test.t1 values ("i", x);
end foo|
call i(0)|
call i(3)|
select * from t1|
delete from t1|
drop procedure i|
# SELECT INTO local variables
create procedure into_test(x char(16), y int)
begin
......@@ -543,6 +582,8 @@ create procedure hndlr1(val int)
begin
declare x int default 0;
declare foo condition for 1146;
declare bar condition for sqlstate '42S98'; # Just for testing syntax
declare zip condition for sqlstate value '42S99'; # Just for testing syntax
declare continue handler for foo set x = 1;
insert into test.t666 values ("hndlr1", val); # Non-existing table
......@@ -561,7 +602,7 @@ begin
declare x int default 0;
begin
declare exit handler for '42S02' set x = 1;
declare exit handler for sqlstate '42S02' set x = 1;
insert into test.t666 values ("hndlr2", val); # Non-existing table
end;
......@@ -864,7 +905,7 @@ begin
set s = s+1;
end;
end if;
end loop again;
end loop;
end|
create procedure ip(m int unsigned)
......
......@@ -386,6 +386,7 @@ static SYMBOL symbols[] = {
{ "SPATIAL", SYM(SPATIAL_SYM),0,0},
{ "SPECIFIC", SYM(SPECIFIC_SYM),0,0},
{ "SQLEXCEPTION", SYM(SQLEXCEPTION_SYM),0,0},
{ "SQLSTATE", SYM(SQLSTATE_SYM),0,0},
{ "SQLWARNING", SYM(SQLWARNING_SYM),0,0},
{ "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT),0,0},
{ "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT),0,0},
......
......@@ -319,3 +319,4 @@ character-set=latin2
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -313,3 +313,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -321,3 +321,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -315,3 +315,4 @@ character-set=latin7
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -322,3 +322,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=greek
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=latin2
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=ujis
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=euckr
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -314,3 +314,4 @@ character-set=latin2
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -311,3 +311,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -314,3 +314,4 @@ character-set=latin2
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=koi8r
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -305,3 +305,4 @@ character-set=cp1250
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -318,3 +318,4 @@ character-set=latin2
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -312,3 +312,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -310,3 +310,4 @@ character-set=latin1
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -315,3 +315,4 @@ character-set=koi8u
"Undeclared variable: %s"
"Wrong number of FETCH variables"
"No data to FETCH"
"Duplicate %s: %s"
......@@ -32,6 +32,7 @@ sp_pcontext::sp_pcontext()
VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cursor, sizeof(LEX_STRING), 16, 8));
VOID(my_init_dynamic_array(&m_scopes, sizeof(sp_scope_t), 16, 8));
m_label.empty();
}
......@@ -41,23 +42,52 @@ sp_pcontext::destroy()
delete_dynamic(&m_pvar);
delete_dynamic(&m_cond);
delete_dynamic(&m_cursor);
delete_dynamic(&m_scopes);
m_label.empty();
}
void
sp_pcontext::push_scope()
{
sp_scope_t s;
s.vars= m_pvar.elements;
s.conds= m_cond.elements;
s.curs= m_cursor.elements;
insert_dynamic(&m_scopes, (gptr)&s);
}
void
sp_pcontext::pop_scope()
{
(void)pop_dynamic(&m_scopes);
}
/* This does a linear search (from newer to older variables, in case
** we have shadowed names).
** It's possible to have a more efficient allocation and search method,
** but it might not be worth it. The typical number of parameters and
** variables will in most cases be low (a handfull).
** And this is only called during parsing.
** ...and, this is only called during parsing.
*/
sp_pvar_t *
sp_pcontext::find_pvar(LEX_STRING *name)
sp_pcontext::find_pvar(LEX_STRING *name, my_bool scoped)
{
uint i = m_pvar.elements;
uint limit;
while (i-- > 0)
if (! scoped || m_scopes.elements == 0)
limit= 0;
else
{
sp_scope_t s;
get_dynamic(&m_scopes, (gptr)&s, m_scopes.elements-1);
limit= s.vars;
}
while (i-- > limit)
{
sp_pvar_t *p;
......@@ -101,6 +131,7 @@ sp_pcontext::push_label(char *name, uint ip)
{
lab->name= name;
lab->ip= ip;
lab->isbegin= FALSE;
m_label.push_front(lab);
}
return lab;
......@@ -137,11 +168,22 @@ sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val)
* See comment for find_pvar() above
*/
sp_cond_type_t *
sp_pcontext::find_cond(LEX_STRING *name)
sp_pcontext::find_cond(LEX_STRING *name, my_bool scoped)
{
uint i = m_cond.elements;
uint limit;
while (i-- > 0)
if (! scoped || m_scopes.elements == 0)
limit= 0;
else
{
sp_scope_t s;
get_dynamic(&m_scopes, (gptr)&s, m_scopes.elements-1);
limit= s.conds;
}
while (i-- > limit)
{
sp_cond_t *p;
......@@ -172,11 +214,22 @@ sp_pcontext::push_cursor(LEX_STRING *name)
* See comment for find_pvar() above
*/
my_bool
sp_pcontext::find_cursor(LEX_STRING *name, uint *poff)
sp_pcontext::find_cursor(LEX_STRING *name, uint *poff, my_bool scoped)
{
uint i = m_cursor.elements;
uint limit;
if (! scoped || m_scopes.elements == 0)
limit= 0;
else
{
sp_scope_t s;
while (i-- > 0)
get_dynamic(&m_scopes, (gptr)&s, m_scopes.elements-1);
limit= s.curs;
}
while (i-- > limit)
{
LEX_STRING n;
......
......@@ -42,6 +42,7 @@ typedef struct sp_label
{
char *name;
uint ip; // Instruction index
my_bool isbegin; // For ITERATE error checking
} sp_label_t;
typedef struct sp_cond_type
......@@ -57,6 +58,11 @@ typedef struct sp_cond
sp_cond_type_t *val;
} sp_cond_t;
typedef struct sp_scope
{
uint vars, conds, curs;
} sp_scope_t;
class sp_pcontext : public Sql_alloc
{
sp_pcontext(const sp_pcontext &); /* Prevent use of these */
......@@ -70,6 +76,13 @@ class sp_pcontext : public Sql_alloc
void
destroy();
// For error checking of duplicate things
void
push_scope();
void
pop_scope();
//
// Parameters and variables
//
......@@ -130,7 +143,7 @@ class sp_pcontext : public Sql_alloc
// Find by name
sp_pvar_t *
find_pvar(LEX_STRING *name);
find_pvar(LEX_STRING *name, my_bool scoped=0);
// Find by index
sp_pvar_t *
......@@ -182,7 +195,7 @@ class sp_pcontext : public Sql_alloc
}
sp_cond_type_t *
find_cond(LEX_STRING *name);
find_cond(LEX_STRING *name, my_bool scoped=0);
//
// Handlers
......@@ -208,7 +221,7 @@ class sp_pcontext : public Sql_alloc
push_cursor(LEX_STRING *name);
my_bool
find_cursor(LEX_STRING *name, uint *poff);
find_cursor(LEX_STRING *name, uint *poff, my_bool scoped=0);
inline void
pop_cursor(uint num)
......@@ -233,6 +246,7 @@ private:
DYNAMIC_ARRAY m_pvar; // Parameters/variables
DYNAMIC_ARRAY m_cond; // Conditions
DYNAMIC_ARRAY m_cursor; // Cursors
DYNAMIC_ARRAY m_scopes; // For error checking
List<sp_label_t> m_label; // The label list
......
......@@ -371,6 +371,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
%token SPATIAL_SYM
%token SPECIFIC_SYM
%token SQLEXCEPTION_SYM
%token SQLSTATE_SYM
%token SQLWARNING_SYM
%token SSL_SYM
%token STARTING
......@@ -618,7 +619,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
ULONGLONG_NUM field_ident select_alias ident ident_or_text
UNDERSCORE_CHARSET IDENT_sys TEXT_STRING_sys TEXT_STRING_literal
NCHAR_STRING opt_component
SP_FUNC ident_or_spfunc
SP_FUNC ident_or_spfunc sp_opt_label
%type <lex_str_ptr>
opt_table_alias
......@@ -1149,7 +1150,15 @@ sp_fdparams:
sp_fdparam:
ident type sp_opt_locator
{
Lex->spcont->push_pvar(&$1, (enum enum_field_types)$2, sp_param_in);
LEX *lex= Lex;
sp_pcontext *spc= lex->spcont;
if (spc->find_pvar(&$1, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "parameter", $1.str);
YYABORT;
}
spc->push_pvar(&$1, (enum enum_field_types)$2, sp_param_in);
}
;
......@@ -1167,9 +1176,16 @@ sp_pdparams:
sp_pdparam:
sp_opt_inout ident type sp_opt_locator
{
Lex->spcont->push_pvar(&$2,
(enum enum_field_types)$3,
(sp_param_mode_t)$1);
LEX *lex= Lex;
sp_pcontext *spc= lex->spcont;
if (spc->find_pvar(&$2, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "parameter", $2.str);
YYABORT;
}
spc->push_pvar(&$2, (enum enum_field_types)$3,
(sp_param_mode_t)$1);
}
;
......@@ -1186,7 +1202,7 @@ sp_opt_locator:
;
sp_proc_stmts:
sp_proc_stmt ';'
/* Empty */ {}
| sp_proc_stmts sp_proc_stmt ';'
;
......@@ -1231,6 +1247,14 @@ sp_decl:
}
| DECLARE_SYM ident CONDITION_SYM FOR_SYM sp_cond
{
LEX *lex= Lex;
sp_pcontext *spc= lex->spcont;
if (spc->find_cond(&$2, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "condition", $2.str);
YYABORT;
}
YYTHD->lex->spcont->push_cond(&$2, $5);
$$.vars= $$.hndlrs= $$.curs= 0;
$$.conds= 1;
......@@ -1272,8 +1296,16 @@ sp_decl:
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
sp_instr_cpush *i= new sp_instr_cpush(sp->instructions(), $5);
sp_pcontext *spc= lex->spcont;
uint offp;
sp_instr_cpush *i;
if (spc->find_cursor(&$2, &offp, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "cursor", $2.str);
YYABORT;
}
i= new sp_instr_cpush(sp->instructions(), $5);
sp->add_instr(i);
lex->spcont->push_cursor(&$2);
$$.vars= $$.conds= $$.hndlrs= 0;
......@@ -1344,18 +1376,23 @@ sp_cond:
$$->type= sp_cond_type_t::number;
$$->mysqlerr= $1;
}
| TEXT_STRING_literal
| SQLSTATE_SYM opt_value TEXT_STRING_literal
{ /* SQLSTATE */
uint len= ($1.length < sizeof($$->sqlstate)-1 ?
$1.length : sizeof($$->sqlstate)-1);
uint len= ($3.length < sizeof($$->sqlstate)-1 ?
$3.length : sizeof($$->sqlstate)-1);
$$= (sp_cond_type_t *)YYTHD->alloc(sizeof(sp_cond_type_t));
$$->type= sp_cond_type_t::state;
memcpy($$->sqlstate, $1.str, len);
memcpy($$->sqlstate, $3.str, len);
$$->sqlstate[len]= '\0';
}
;
opt_value:
/* Empty */ {}
| VALUE_SYM {}
;
sp_hcond:
sp_cond
{
......@@ -1390,12 +1427,28 @@ sp_hcond:
sp_decl_idents:
ident
{
Lex->spcont->push_pvar(&$1, (enum_field_types)0, sp_param_in);
LEX *lex= Lex;
sp_pcontext *spc= lex->spcont;
if (spc->find_pvar(&$1, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "parameter", $1.str);
YYABORT;
}
spc->push_pvar(&$1, (enum_field_types)0, sp_param_in);
$$= 1;
}
| sp_decl_idents ',' ident
{
Lex->spcont->push_pvar(&$3, (enum_field_types)0, sp_param_in);
LEX *lex= Lex;
sp_pcontext *spc= lex->spcont;
if (spc->find_pvar(&$3, TRUE))
{
net_printf(YYTHD, ER_SP_DUP_THING, "parameter", $3.str);
YYABORT;
}
spc->push_pvar(&$3, (enum_field_types)0, sp_param_in);
$$= $1 + 1;
}
;
......@@ -1532,7 +1585,7 @@ sp_proc_stmt:
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->find_label($2.str);
if (! lab)
if (! lab || lab->isbegin)
{
net_printf(YYTHD, ER_SP_LILABEL_MISMATCH, "ITERATE", $2.str);
YYABORT;
......@@ -1736,32 +1789,43 @@ sp_labeled_control:
lex->sphead->instructions());
}
}
sp_unlabeled_control IDENT
sp_unlabeled_control sp_opt_label
{
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->find_label($5.str);
if (!lab ||
my_strcasecmp(system_charset_info, $5.str, lab->name) != 0)
if ($5.str)
{
net_printf(YYTHD, ER_SP_LABEL_MISMATCH, $5.str);
YYABORT;
}
else
{
lex->spcont->pop_label();
lex->sphead->backpatch(lab);
sp_label_t *lab= lex->spcont->find_label($5.str);
if (!lab ||
my_strcasecmp(system_charset_info, $5.str, lab->name) != 0)
{
net_printf(YYTHD, ER_SP_LABEL_MISMATCH, $5.str);
YYABORT;
}
}
lex->sphead->backpatch(lex->spcont->pop_label());
}
;
sp_opt_label:
/* Empty */
{ $$.str= NULL; $$.length= 0; }
| IDENT
{ $$= $1; }
;
sp_unlabeled_control:
BEGIN_SYM
{ /* QQ This is just a dummy for grouping declarations and statements
together. No [[NOT] ATOMIC] yet, and we need to figure out how
make it coexist with the existing BEGIN COMMIT/ROLLBACK. */
LEX *lex= Lex;
sp_label_t *lab= lex->spcont->last_label();
Lex->spcont->push_label((char *)"", 0); /* For end of block */
lab->isbegin= TRUE;
/* Scope duplicate checking */
lex->spcont->push_scope();
}
sp_decls
sp_proc_stmts
......@@ -1771,7 +1835,7 @@ sp_unlabeled_control:
sp_head *sp= lex->sphead;
sp_pcontext *ctx= lex->spcont;
sp->backpatch(ctx->pop_label());
sp->backpatch(ctx->last_label()); /* We always has a label */
ctx->pop_pvar($3.vars);
ctx->pop_cond($3.conds);
ctx->pop_cursor($3.curs);
......@@ -1779,6 +1843,7 @@ sp_unlabeled_control:
sp->add_instr(new sp_instr_hpop(sp->instructions(),$3.hndlrs));
if ($3.curs)
sp->add_instr(new sp_instr_cpop(sp->instructions(), $3.curs));
ctx->pop_scope();
}
| LOOP_SYM
sp_proc_stmts END LOOP_SYM
......
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