Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
MariaDB
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
MariaDB
Commits
c39a7446
Commit
c39a7446
authored
Sep 22, 2017
by
Alexander Barkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MDEV-13864 (final) Change Item_func_case to store the predicant in args[0]
parent
e12390a3
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
294 additions
and
182 deletions
+294
-182
mysql-test/r/case.result
mysql-test/r/case.result
+48
-0
mysql-test/r/func_debug.result
mysql-test/r/func_debug.result
+7
-7
mysql-test/t/case.test
mysql-test/t/case.test
+28
-0
sql/item_cmpfunc.cc
sql/item_cmpfunc.cc
+78
-76
sql/item_cmpfunc.h
sql/item_cmpfunc.h
+93
-43
sql/sql_yacc.yy
sql/sql_yacc.yy
+19
-15
sql/sql_yacc_ora.yy
sql/sql_yacc_ora.yy
+21
-41
No files found.
mysql-test/r/case.result
View file @
c39a7446
...
...
@@ -447,3 +447,51 @@ EXECUTE stmt;
good was_bad_now_good
one one
DEALLOCATE PREPARE stmt;
#
# MDEV-13864 Change Item_func_case to store the predicant in args[0]
#
SET NAMES latin1;
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('b'),('c');
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE a WHEN 'a' THEN 'a' ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN a THEN 'a' ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN 'a' THEN a ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a' and (case 'a' when 'a' then `test`.`t1`.`a` else 'a' end) = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN 'a' THEN 'a' ELSE a END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a' and (case 'a' when 'a' then 'a' else `test`.`t1`.`a` end) = 'a'
ALTER TABLE t1 MODIFY a VARBINARY(10);
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE a WHEN 'a' THEN 'a' ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN a THEN 'a' ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN 'a' THEN a ELSE 'a' END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
EXPLAIN EXTENDED SELECT * FROM t1 WHERE a='a' AND CASE 'a' WHEN 'a' THEN 'a' ELSE a END='a';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = 'a'
DROP TABLE t1;
mysql-test/r/func_debug.result
View file @
c39a7446
...
...
@@ -1629,8 +1629,8 @@ WHEN -9223372036854775808 THEN 'one'
c
NULL
Warnings:
Note 1105 DBUG: [0] arg=
0
handler=0 (bigint)
Note 1105 DBUG: [1] arg=
2
handler=1 (decimal)
Note 1105 DBUG: [0] arg=
1
handler=0 (bigint)
Note 1105 DBUG: [1] arg=
3
handler=1 (decimal)
DROP TABLE t1;
#
# MDEV-11555 CASE with a mixture of TIME and DATETIME returns a wrong result
...
...
@@ -1648,10 +1648,10 @@ CASE TIME'10:20:30'
good was_bad_now_good
one one
Warnings:
Note 1105 DBUG: [0] arg=
0
handler=0 (time)
Note 1105 DBUG: [1] arg=
2
handler=0 (time)
Note 1105 DBUG: [0] arg=
0
handler=0 (time)
Note 1105 DBUG: [1] arg=
2
handler=0 (time)
Note 1105 DBUG: [2] arg=
4
handler=2 (datetime)
Note 1105 DBUG: [0] arg=
1
handler=0 (time)
Note 1105 DBUG: [1] arg=
3
handler=0 (time)
Note 1105 DBUG: [0] arg=
1
handler=0 (time)
Note 1105 DBUG: [1] arg=
3
handler=0 (time)
Note 1105 DBUG: [2] arg=
5
handler=2 (datetime)
SET SESSION debug_dbug="-d,Predicant_to_list_comparator";
SET SESSION debug_dbug="-d,Item_func_in";
mysql-test/t/case.test
View file @
c39a7446
...
...
@@ -330,3 +330,31 @@ PREPARE stmt FROM "SELECT
EXECUTE
stmt
;
EXECUTE
stmt
;
DEALLOCATE
PREPARE
stmt
;
--
echo
#
--
echo
# MDEV-13864 Change Item_func_case to store the predicant in args[0]
--
echo
#
SET
NAMES
latin1
;
CREATE
TABLE
t1
(
a
VARCHAR
(
10
)
CHARACTER
SET
latin1
);
INSERT
INTO
t1
VALUES
(
'a'
),(
'b'
),(
'c'
);
# should propagate the predicant and the WHEN arguments (they are in comparison and use ANY_SUBST)
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
a
WHEN
'a'
THEN
'a'
ELSE
'a'
END
=
'a'
;
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
a
THEN
'a'
ELSE
'a'
END
=
'a'
;
# should not propagate the THEN and the ELSE arguments (they are not in comparison and use IDENTITY_SUBST)
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
'a'
THEN
a
ELSE
'a'
END
=
'a'
;
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
'a'
THEN
'a'
ELSE
a
END
=
'a'
;
ALTER
TABLE
t1
MODIFY
a
VARBINARY
(
10
);
# with VARBINARY it should propagate all arguments
# as IDENTITY_SUBST for VARBINARY allows substitution
# of even those arguments that are not in comparison
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
a
WHEN
'a'
THEN
'a'
ELSE
'a'
END
=
'a'
;
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
a
THEN
'a'
ELSE
'a'
END
=
'a'
;
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
'a'
THEN
a
ELSE
'a'
END
=
'a'
;
EXPLAIN
EXTENDED
SELECT
*
FROM
t1
WHERE
a
=
'a'
AND
CASE
'a'
WHEN
'a'
THEN
'a'
ELSE
a
END
=
'a'
;
DROP
TABLE
t1
;
sql/item_cmpfunc.cc
View file @
c39a7446
This diff is collapsed.
Click to expand it.
sql/item_cmpfunc.h
View file @
c39a7446
...
...
@@ -2016,79 +2016,129 @@ class Predicant_to_list_comparator
/*
The class Item_func_case is the CASE ... WHEN ... THEN ... END function
implementation.
When there is no expression between CASE and the first WHEN
(the CASE expression) then this function simple checks all WHEN expressions
one after another. When some WHEN expression evaluated to TRUE then the
value of the corresponding THEN expression is returned.
When the CASE expression is specified then it is compared to each WHEN
expression individually. When an equal WHEN expression is found
corresponding THEN expression is returned.
In order to do correct comparisons several comparators are used. One for
each result type. Different result types that are used in particular
CASE ... END expression are collected in the fix_length_and_dec() member
function and only comparators for there result types are used.
*/
class
Item_func_case
:
public
Item_func_case_expression
,
public
Predicant_to_list_comparator
class
Item_func_case
:
public
Item_func_case_expression
{
int
first_expr_num
,
else_expr_num
;
protected:
String
tmp_value
;
uint
ncases
;
DTCollation
cmp_collation
;
Item
**
arg_buffer
;
uint
m_found_types
;
bool
prepare_predicant_and_values
(
THD
*
thd
,
uint
*
found_types
);
bool
aggregate_then_and_else_arguments
(
THD
*
thd
);
bool
aggregate_switch_and_when_arguments
(
THD
*
thd
);
Item
*
find_item_searched
();
Item
*
find_item_simple
();
Item
*
find_item
()
{
return
first_expr_num
==
-
1
?
find_item_searched
()
:
find_item_simple
();
}
bool
aggregate_then_and_else_arguments
(
THD
*
thd
,
Item
**
items
,
uint
count
,
Item
**
else_expr
);
virtual
Item
**
else_expr_addr
()
const
=
0
;
virtual
Item
*
find_item
()
=
0
;
void
print_when_then_arguments
(
String
*
str
,
enum_query_type
query_type
,
Item
**
items
,
uint
count
);
void
print_else_argument
(
String
*
str
,
enum_query_type
query_type
,
Item
*
item
);
public:
Item_func_case
(
THD
*
thd
,
List
<
Item
>
&
list
,
Item
*
first_expr_arg
,
Item
*
else_expr_arg
);
Item_func_case
(
THD
*
thd
,
List
<
Item
>
&
list
)
:
Item_func_case_expression
(
thd
,
list
)
{
}
double
real_op
();
longlong
int_op
();
String
*
str_op
(
String
*
);
my_decimal
*
decimal_op
(
my_decimal
*
);
bool
date_op
(
MYSQL_TIME
*
ltime
,
uint
fuzzydate
);
bool
fix_fields
(
THD
*
thd
,
Item
**
ref
);
void
fix_length_and_dec
();
table_map
not_null_tables
()
const
{
return
0
;
}
const
char
*
func_name
()
const
{
return
"case"
;
}
enum
precedence
precedence
()
const
{
return
BETWEEN_PRECEDENCE
;
}
virtual
void
print
(
String
*
str
,
enum_query_type
query_type
);
CHARSET_INFO
*
compare_collation
()
const
{
return
cmp_collation
.
collation
;
}
bool
need_parentheses_in_default
()
{
return
true
;
}
Item
*
build_clone
(
THD
*
thd
,
MEM_ROOT
*
mem_root
)
{
Item_func_case
*
clone
=
(
Item_func_case
*
)
Item_func
::
build_clone
(
thd
,
mem_root
);
if
(
clone
)
clone
->
arg_buffer
=
0
;
return
clone
;
}
};
/*
CASE WHEN cond THEN res [WHEN cond THEN res...] [ELSE res] END
Searched CASE checks all WHEN expressions one after another.
When some WHEN expression evaluated to TRUE then the
value of the corresponding THEN expression is returned.
*/
class
Item_func_case_searched
:
public
Item_func_case
{
uint
when_count
()
const
{
return
arg_count
/
2
;
}
bool
with_else
()
const
{
return
arg_count
%
2
;
}
Item
**
else_expr_addr
()
const
{
return
with_else
()
?
&
args
[
arg_count
-
1
]
:
0
;
}
public:
Item_func_case_searched
(
THD
*
thd
,
List
<
Item
>
&
list
)
:
Item_func_case
(
thd
,
list
)
{
DBUG_ASSERT
(
arg_count
>=
2
);
}
void
print
(
String
*
str
,
enum_query_type
query_type
);
void
fix_length_and_dec
();
Item
*
propagate_equal_fields
(
THD
*
thd
,
const
Context
&
ctx
,
COND_EQUAL
*
cond
)
{
// None of the arguments are in a comparison context
Item_args
::
propagate_equal_fields
(
thd
,
Context_identity
(),
cond
);
return
this
;
}
Item
*
find_item
();
Item
*
get_copy
(
THD
*
thd
,
MEM_ROOT
*
mem_root
)
{
return
get_item_copy
<
Item_func_case_searched
>
(
thd
,
mem_root
,
this
);
}
};
/*
CASE pred WHEN value THEN res [WHEN value THEN res...] [ELSE res] END
When the predicant expression is specified then it is compared to each WHEN
expression individually. When an equal WHEN expression is found
the corresponding THEN expression is returned.
In order to do correct comparisons several comparators are used. One for
each result type. Different result types that are used in particular
CASE ... END expression are collected in the fix_length_and_dec() member
function and only comparators for there result types are used.
*/
class
Item_func_case_simple
:
public
Item_func_case
,
public
Predicant_to_list_comparator
{
uint
m_found_types
;
uint
when_count
()
const
{
return
(
arg_count
-
1
)
/
2
;
}
bool
with_else
()
const
{
return
arg_count
%
2
==
0
;
}
Item
**
else_expr_addr
()
const
{
return
with_else
()
?
&
args
[
arg_count
-
1
]
:
0
;
}
bool
aggregate_switch_and_when_arguments
(
THD
*
thd
);
bool
prepare_predicant_and_values
(
THD
*
thd
,
uint
*
found_types
);
public:
Item_func_case_simple
(
THD
*
thd
,
List
<
Item
>
&
list
)
:
Item_func_case
(
thd
,
list
),
Predicant_to_list_comparator
(
thd
,
arg_count
),
m_found_types
(
0
)
{
DBUG_ASSERT
(
arg_count
>=
3
);
}
void
cleanup
()
{
DBUG_ENTER
(
"Item_func_case::cleanup"
);
DBUG_ENTER
(
"Item_func_case
_simple
::cleanup"
);
Item_func
::
cleanup
();
Predicant_to_list_comparator
::
cleanup
();
DBUG_VOID_RETURN
;
}
Item
*
propagate_equal_fields
(
THD
*
thd
,
const
Context
&
ctx
,
COND_EQUAL
*
cond
);
bool
need_parentheses_in_default
()
{
return
true
;
}
Item
*
get_copy
(
THD
*
thd
,
MEM_ROOT
*
mem_root
)
{
return
get_item_copy
<
Item_func_case
>
(
thd
,
mem_root
,
this
);
}
void
print
(
String
*
str
,
enum_query_type
query_type
);
void
fix_length_and_dec
();
Item
*
propagate_equal_fields
(
THD
*
thd
,
const
Context
&
ctx
,
COND_EQUAL
*
cond
);
Item
*
find_item
();
Item
*
build_clone
(
THD
*
thd
,
MEM_ROOT
*
mem_root
)
{
Item_func_case
*
clone
=
(
Item_func_case
*
)
Item_func
::
build_clone
(
thd
,
mem_root
);
if
(
clone
)
{
clone
->
arg_buffer
=
0
;
if
(
clone
->
Predicant_to_list_comparator
::
init_clone
(
thd
,
ncases
))
return
NULL
;
}
Item_func_case_simple
*
clone
=
(
Item_func_case_simple
*
)
Item_func_case
::
build_clone
(
thd
,
mem_root
);
uint
ncases
=
when_count
();
if
(
clone
&&
clone
->
Predicant_to_list_comparator
::
init_clone
(
thd
,
ncases
))
return
NULL
;
return
clone
;
}
Item
*
get_copy
(
THD
*
thd
,
MEM_ROOT
*
mem_root
)
{
return
get_item_copy
<
Item_func_case_simple
>
(
thd
,
mem_root
,
this
);
}
};
...
...
sql/sql_yacc.yy
View file @
c39a7446
...
...
@@ -1716,7 +1716,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%type <item>
literal text_literal insert_ident order_ident temporal_literal
simple_ident expr
opt_expr opt_else
sum_expr in_sum_expr
simple_ident expr sum_expr in_sum_expr
variable variable_aux bool_pri
predicate bit_expr parenthesized_expr
table_wild simple_expr column_default_non_parenthesized_expr udf_expr
...
...
@@ -1744,7 +1744,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
NUM_literal
%type <item_list>
expr_list opt_udf_expr_list udf_expr_list when_list
expr_list opt_udf_expr_list udf_expr_list when_list
when_list_opt_else
ident_list ident_list_arg opt_expr_list
%type <sp_cursor_stmt>
...
...
@@ -9419,10 +9419,15 @@ column_default_non_parenthesized_expr:
if (!($$= $5.create_typecast_item(thd, $3, Lex->charset)))
MYSQL_YYABORT;
}
| CASE_SYM
opt_expr when_list
opt_else END
| CASE_SYM
when_list_
opt_else END
{
$$= new (thd->mem_root) Item_func_case(thd, *$3, $2, $4);
if ($$ == NULL)
if (!($$= new(thd->mem_root) Item_func_case_searched(thd, *$2)))
MYSQL_YYABORT;
}
| CASE_SYM expr when_list_opt_else END
{
$3->push_front($2, thd->mem_root);
if (!($$= new (thd->mem_root) Item_func_case_simple(thd, *$3)))
MYSQL_YYABORT;
}
| CONVERT_SYM '(' expr ',' cast_type ')'
...
...
@@ -10844,16 +10849,6 @@ ident_list:
}
;
opt_expr:
/* empty */ { $$= NULL; }
| expr { $$= $1; }
;
opt_else:
/* empty */ { $$= NULL; }
| ELSE expr { $$= $2; }
;
when_list:
WHEN_SYM expr THEN_SYM expr
{
...
...
@@ -10871,6 +10866,15 @@ when_list:
}
;
when_list_opt_else:
when_list
| when_list ELSE expr
{
$1->push_back($3, thd->mem_root);
$$= $1;
}
;
/* Equivalent to <table reference> in the SQL:2003 standard. */
/* Warning - may return NULL in case of incomplete SELECT */
table_ref:
...
...
sql/sql_yacc_ora.yy
View file @
c39a7446
...
...
@@ -1133,7 +1133,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%type <item>
literal text_literal insert_ident order_ident temporal_literal
simple_ident expr
opt_expr opt_else
sum_expr in_sum_expr
simple_ident expr sum_expr in_sum_expr
variable variable_aux bool_pri
predicate bit_expr parenthesized_expr
table_wild simple_expr column_default_non_parenthesized_expr udf_expr
...
...
@@ -1163,7 +1163,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
NUM_literal
%type <item_list>
expr_list opt_udf_expr_list udf_expr_list when_list
expr_list opt_udf_expr_list udf_expr_list when_list
when_list_opt_else
ident_list ident_list_arg opt_expr_list
decode_when_list
...
...
@@ -9440,10 +9440,15 @@ column_default_non_parenthesized_expr:
if (!($$= $5.create_typecast_item(thd, $3, Lex->charset)))
MYSQL_YYABORT;
}
| CASE_SYM
opt_expr when_list
opt_else END
| CASE_SYM
when_list_
opt_else END
{
$$= new (thd->mem_root) Item_func_case(thd, *$3, $2, $4);
if ($$ == NULL)
if (!($$= new(thd->mem_root) Item_func_case_searched(thd, *$2)))
MYSQL_YYABORT;
}
| CASE_SYM expr when_list_opt_else END
{
$3->push_front($2, thd->mem_root);
if (!($$= new (thd->mem_root) Item_func_case_simple(thd, *$3)))
MYSQL_YYABORT;
}
| CONVERT_SYM '(' expr ',' cast_type ')'
...
...
@@ -9459,32 +9464,8 @@ column_default_non_parenthesized_expr:
}
| DECODE_SYM '(' expr ',' decode_when_list ')'
{
if (($5->elements % 2) == 0)
{
// No default expression
$$= new (thd->mem_root) Item_func_case(thd, *$5, $3, NULL);
}
else
{
/*
There is a default expression at the end of the list $5.
Create a new list without the default expression.
*/
List<Item> tmp;
List_iterator_fast<Item> it(*$5);
for (uint i= 0; i < $5->elements - 1; i++) // copy all but last
{
Item *item= it++;
tmp.push_back(item);
}
/*
Now the new list "tmp" contains only WHEN-THEN pairs,
The default expression is pointed by the iterator "it"
and will be returned by the next call for it++ below.
*/
$$= new (thd->mem_root) Item_func_case(thd, tmp, $3, it++);
}
if ($$ == NULL)
$5->push_front($3, thd->mem_root);
if (!($$= new (thd->mem_root) Item_func_case_simple(thd, *$5)))
MYSQL_YYABORT;
}
| DEFAULT '(' simple_ident ')'
...
...
@@ -10907,16 +10888,6 @@ ident_list:
}
;
opt_expr:
/* empty */ { $$= NULL; }
| expr { $$= $1; }
;
opt_else:
/* empty */ { $$= NULL; }
| ELSE expr { $$= $2; }
;
when_list:
WHEN_SYM expr THEN_SYM expr
{
...
...
@@ -10935,6 +10906,15 @@ when_list:
;
when_list_opt_else:
when_list
| when_list ELSE expr
{
$1->push_back($3, thd->mem_root);
$$= $1;
}
;
decode_when_list:
expr ',' expr
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment