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
e2ffbc1a
Commit
e2ffbc1a
authored
Feb 02, 2004
by
bar@bar.intranet.mysql.r18.ru
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Indexes can be used for optimization if the operation
collation is the same with the index collation.
parent
8a3e00a0
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
153 additions
and
34 deletions
+153
-34
mysql-test/r/ctype_collate.result
mysql-test/r/ctype_collate.result
+47
-0
mysql-test/t/ctype_collate.test
mysql-test/t/ctype_collate.test
+38
-0
sql/item.h
sql/item.h
+1
-0
sql/item_cmpfunc.h
sql/item_cmpfunc.h
+7
-0
sql/opt_range.cc
sql/opt_range.cc
+44
-29
sql/sql_select.cc
sql/sql_select.cc
+16
-5
No files found.
mysql-test/r/ctype_collate.result
View file @
e2ffbc1a
...
...
@@ -541,3 +541,50 @@ s2 CHAR(5) COLLATE latin1_swedish_ci);
SELECT * FROM t1 WHERE s1 = s2;
ERROR HY000: Illegal mix of collations (latin1_german1_ci,IMPLICIT) and (latin1_swedish_ci,IMPLICIT) for operation '='
DROP TABLE t1;
SET NAMES latin1;
CREATE TABLE t1
(s1 char(10) COLLATE latin1_german1_ci,
s2 char(10) COLLATE latin1_swedish_ci,
KEY(s1),
KEY(s2));
INSERT INTO t1 VALUES ('a','a');
INSERT INTO t1 VALUES ('b','b');
INSERT INTO t1 VALUES ('c','c');
INSERT INTO t1 VALUES ('d','d');
INSERT INTO t1 VALUES ('e','e');
INSERT INTO t1 VALUES ('f','f');
INSERT INTO t1 VALUES ('g','g');
INSERT INTO t1 VALUES ('h','h');
INSERT INTO t1 VALUES ('i','i');
INSERT INTO t1 VALUES ('j','j');
EXPLAIN SELECT * FROM t1 WHERE s1='a';
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref s1 s1 11 const 1 Using where
EXPLAIN SELECT * FROM t1 WHERE s2='a';
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref s2 s2 11 const 1 Using where
EXPLAIN SELECT * FROM t1 WHERE s1='a' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref s1 s1 11 const 1 Using where
EXPLAIN SELECT * FROM t1 WHERE s2='a' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL s2 NULL NULL NULL 10 Using where
EXPLAIN SELECT * FROM t1 WHERE s1 BETWEEN 'a' AND 'b' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range s1 s1 11 NULL 2 Using where
EXPLAIN SELECT * FROM t1 WHERE s2 BETWEEN 'a' AND 'b' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL s2 NULL NULL NULL 10 Using where
EXPLAIN SELECT * FROM t1 WHERE s1 IN ('a','b' COLLATE latin1_german1_ci);
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range s1 s1 11 NULL 2 Using where
EXPLAIN SELECT * FROM t1 WHERE s2 IN ('a','b' COLLATE latin1_german1_ci);
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL s2 NULL NULL NULL 10 Using where
EXPLAIN SELECT * FROM t1 WHERE s1 LIKE 'a' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range s1 s1 11 NULL 1 Using where
EXPLAIN SELECT * FROM t1 WHERE s2 LIKE 'a' COLLATE latin1_german1_ci;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL s2 NULL NULL NULL 10 Using where
DROP TABLE t1;
mysql-test/t/ctype_collate.test
View file @
e2ffbc1a
...
...
@@ -156,3 +156,41 @@ CREATE TABLE t1
--
error
1266
SELECT
*
FROM
t1
WHERE
s1
=
s2
;
DROP
TABLE
t1
;
#
# Test that optimizer doesn't use indexes with wrong collation
#
SET
NAMES
latin1
;
CREATE
TABLE
t1
(
s1
char
(
10
)
COLLATE
latin1_german1_ci
,
s2
char
(
10
)
COLLATE
latin1_swedish_ci
,
KEY
(
s1
),
KEY
(
s2
));
INSERT
INTO
t1
VALUES
(
'a'
,
'a'
);
INSERT
INTO
t1
VALUES
(
'b'
,
'b'
);
INSERT
INTO
t1
VALUES
(
'c'
,
'c'
);
INSERT
INTO
t1
VALUES
(
'd'
,
'd'
);
INSERT
INTO
t1
VALUES
(
'e'
,
'e'
);
INSERT
INTO
t1
VALUES
(
'f'
,
'f'
);
INSERT
INTO
t1
VALUES
(
'g'
,
'g'
);
INSERT
INTO
t1
VALUES
(
'h'
,
'h'
);
INSERT
INTO
t1
VALUES
(
'i'
,
'i'
);
INSERT
INTO
t1
VALUES
(
'j'
,
'j'
);
EXPLAIN
SELECT
*
FROM
t1
WHERE
s1
=
'a'
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s2
=
'a'
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s1
=
'a'
COLLATE
latin1_german1_ci
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s2
=
'a'
COLLATE
latin1_german1_ci
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s1
BETWEEN
'a'
AND
'b'
COLLATE
latin1_german1_ci
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s2
BETWEEN
'a'
AND
'b'
COLLATE
latin1_german1_ci
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s1
IN
(
'a'
,
'b'
COLLATE
latin1_german1_ci
);
EXPLAIN
SELECT
*
FROM
t1
WHERE
s2
IN
(
'a'
,
'b'
COLLATE
latin1_german1_ci
);
EXPLAIN
SELECT
*
FROM
t1
WHERE
s1
LIKE
'a'
COLLATE
latin1_german1_ci
;
EXPLAIN
SELECT
*
FROM
t1
WHERE
s2
LIKE
'a'
COLLATE
latin1_german1_ci
;
DROP
TABLE
t1
;
sql/item.h
View file @
e2ffbc1a
...
...
@@ -204,6 +204,7 @@ class Item {
virtual
Item
*
get_tmp_table_item
(
THD
*
thd
)
{
return
copy_or_same
(
thd
);
}
CHARSET_INFO
*
default_charset
()
const
;
virtual
CHARSET_INFO
*
compare_collation
()
{
return
NULL
;
}
virtual
bool
walk
(
Item_processor
processor
,
byte
*
arg
)
{
...
...
sql/item_cmpfunc.h
View file @
e2ffbc1a
...
...
@@ -191,6 +191,7 @@ class Item_bool_func2 :public Item_int_func
bool
have_rev_func
()
const
{
return
rev_functype
()
!=
UNKNOWN_FUNC
;
}
void
print
(
String
*
str
)
{
Item_func
::
print_op
(
str
);
}
bool
is_null
()
{
return
test
(
args
[
0
]
->
is_null
()
||
args
[
1
]
->
is_null
());
}
CHARSET_INFO
*
compare_collation
()
{
return
cmp
.
cmp_collation
.
collation
;
}
friend
class
Arg_comparator
;
};
...
...
@@ -340,6 +341,7 @@ class Item_func_between :public Item_int_func
const
char
*
func_name
()
const
{
return
"between"
;
}
void
fix_length_and_dec
();
void
print
(
String
*
str
);
CHARSET_INFO
*
compare_collation
()
{
return
cmp_collation
.
collation
;
}
};
...
...
@@ -479,6 +481,7 @@ class Item_func_case :public Item_func
const
char
*
func_name
()
const
{
return
"case"
;
}
void
print
(
String
*
str
);
Item
*
find_item
(
String
*
str
);
CHARSET_INFO
*
compare_collation
()
{
return
cmp_collation
.
collation
;
}
};
...
...
@@ -726,6 +729,7 @@ class Item_func_in :public Item_int_func
enum
Functype
functype
()
const
{
return
IN_FUNC
;
}
const
char
*
func_name
()
const
{
return
" IN "
;
}
bool
nulls_in_row
();
CHARSET_INFO
*
compare_collation
()
{
return
cmp_collation
.
collation
;
}
};
/* Functions used by where clause */
...
...
@@ -766,6 +770,7 @@ class Item_func_isnull :public Item_bool_func
table_map
not_null_tables
()
const
{
return
0
;
}
optimize_type
select_optimize
()
const
{
return
OPTIMIZE_NULL
;
}
Item
*
neg_transformer
();
CHARSET_INFO
*
compare_collation
()
{
return
args
[
0
]
->
collation
.
collation
;
}
};
/* Functions used by HAVING for rewriting IN subquery */
...
...
@@ -800,6 +805,7 @@ class Item_func_isnotnull :public Item_bool_func
table_map
not_null_tables
()
const
{
return
0
;
}
Item
*
neg_transformer
();
void
print
(
String
*
str
);
CHARSET_INFO
*
compare_collation
()
{
return
args
[
0
]
->
collation
.
collation
;
}
};
...
...
@@ -854,6 +860,7 @@ class Item_func_regex :public Item_bool_func
bool
fix_fields
(
THD
*
thd
,
struct
st_table_list
*
tlist
,
Item
**
ref
);
const
char
*
func_name
()
const
{
return
"regexp"
;
}
void
print
(
String
*
str
)
{
print_op
(
str
);
}
CHARSET_INFO
*
compare_collation
()
{
return
cmp_collation
.
collation
;
}
};
#else
...
...
sql/opt_range.cc
View file @
e2ffbc1a
...
...
@@ -291,10 +291,11 @@ typedef struct st_qsel_param {
bool
quick
;
// Don't calulate possible keys
}
PARAM
;
static
SEL_TREE
*
get_mm_parts
(
PARAM
*
param
,
Field
*
field
,
static
SEL_TREE
*
get_mm_parts
(
PARAM
*
param
,
COND
*
cond_func
,
Field
*
field
,
Item_func
::
Functype
type
,
Item
*
value
,
Item_result
cmp_type
);
static
SEL_ARG
*
get_mm_leaf
(
PARAM
*
param
,
Field
*
field
,
KEY_PART
*
key_part
,
static
SEL_ARG
*
get_mm_leaf
(
PARAM
*
param
,
COND
*
cond_func
,
Field
*
field
,
KEY_PART
*
key_part
,
Item_func
::
Functype
type
,
Item
*
value
);
static
SEL_TREE
*
get_mm_tree
(
PARAM
*
param
,
COND
*
cond
);
static
ha_rows
check_quick_select
(
PARAM
*
param
,
uint
index
,
SEL_ARG
*
key_tree
);
...
...
@@ -834,10 +835,10 @@ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
Field
*
field
=
((
Item_field
*
)
(
cond_func
->
arguments
()[
0
]))
->
field
;
Item_result
cmp_type
=
field
->
cmp_type
();
DBUG_RETURN
(
tree_and
(
param
,
get_mm_parts
(
param
,
field
,
get_mm_parts
(
param
,
cond_func
,
field
,
Item_func
::
GE_FUNC
,
cond_func
->
arguments
()[
1
],
cmp_type
),
get_mm_parts
(
param
,
field
,
get_mm_parts
(
param
,
cond_func
,
field
,
Item_func
::
LE_FUNC
,
cond_func
->
arguments
()[
2
],
cmp_type
)));
}
...
...
@@ -850,13 +851,14 @@ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
{
Field
*
field
=
((
Item_field
*
)
(
func
->
key_item
()))
->
field
;
Item_result
cmp_type
=
field
->
cmp_type
();
tree
=
get_mm_parts
(
param
,
field
,
Item_func
::
EQ_FUNC
,
tree
=
get_mm_parts
(
param
,
cond_func
,
field
,
Item_func
::
EQ_FUNC
,
func
->
arguments
()[
1
],
cmp_type
);
if
(
!
tree
)
DBUG_RETURN
(
tree
);
// Not key field
for
(
uint
i
=
2
;
i
<
func
->
argument_count
();
i
++
)
{
SEL_TREE
*
new_tree
=
get_mm_parts
(
param
,
field
,
Item_func
::
EQ_FUNC
,
SEL_TREE
*
new_tree
=
get_mm_parts
(
param
,
cond_func
,
field
,
Item_func
::
EQ_FUNC
,
func
->
arguments
()[
i
],
cmp_type
);
tree
=
tree_or
(
param
,
tree
,
new_tree
);
}
...
...
@@ -875,7 +877,7 @@ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
/* btw, ft_func's arguments()[0] isn't FIELD_ITEM. SerG*/
if
(
cond_func
->
arguments
()[
0
]
->
type
()
==
Item
::
FIELD_ITEM
)
{
tree
=
get_mm_parts
(
param
,
tree
=
get_mm_parts
(
param
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
arguments
()[
0
]))
->
field
,
cond_func
->
functype
(),
cond_func
->
arg_count
>
1
?
cond_func
->
arguments
()[
1
]
:
...
...
@@ -888,7 +890,7 @@ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
cond_func
->
have_rev_func
()
&&
cond_func
->
arguments
()[
1
]
->
type
()
==
Item
::
FIELD_ITEM
)
{
DBUG_RETURN
(
get_mm_parts
(
param
,
DBUG_RETURN
(
get_mm_parts
(
param
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
arguments
()[
1
]))
->
field
,
((
Item_bool_func2
*
)
cond_func
)
->
rev_functype
(),
...
...
@@ -902,7 +904,8 @@ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
static
SEL_TREE
*
get_mm_parts
(
PARAM
*
param
,
Field
*
field
,
Item_func
::
Functype
type
,
get_mm_parts
(
PARAM
*
param
,
COND
*
cond_func
,
Field
*
field
,
Item_func
::
Functype
type
,
Item
*
value
,
Item_result
cmp_type
)
{
bool
ne_func
=
FALSE
;
...
...
@@ -931,7 +934,8 @@ get_mm_parts(PARAM *param, Field *field, Item_func::Functype type,
DBUG_RETURN
(
0
);
// OOM
if
(
!
value
||
!
(
value
->
used_tables
()
&
~
param
->
read_tables
))
{
sel_arg
=
get_mm_leaf
(
param
,
key_part
->
field
,
key_part
,
type
,
value
);
sel_arg
=
get_mm_leaf
(
param
,
cond_func
,
key_part
->
field
,
key_part
,
type
,
value
);
if
(
!
sel_arg
)
continue
;
if
(
sel_arg
->
type
==
SEL_ARG
::
IMPOSSIBLE
)
...
...
@@ -953,7 +957,8 @@ get_mm_parts(PARAM *param, Field *field, Item_func::Functype type,
if
(
ne_func
)
{
SEL_TREE
*
tree2
=
get_mm_parts
(
param
,
field
,
Item_func
::
GT_FUNC
,
SEL_TREE
*
tree2
=
get_mm_parts
(
param
,
cond_func
,
field
,
Item_func
::
GT_FUNC
,
value
,
cmp_type
);
if
(
tree2
)
tree
=
tree_or
(
param
,
tree
,
tree2
);
...
...
@@ -963,7 +968,7 @@ get_mm_parts(PARAM *param, Field *field, Item_func::Functype type,
static
SEL_ARG
*
get_mm_leaf
(
PARAM
*
param
,
Field
*
field
,
KEY_PART
*
key_part
,
get_mm_leaf
(
PARAM
*
param
,
COND
*
conf_func
,
Field
*
field
,
KEY_PART
*
key_part
,
Item_func
::
Functype
type
,
Item
*
value
)
{
uint
maybe_null
=
(
uint
)
field
->
real_maybe_null
(),
copies
;
...
...
@@ -972,6 +977,32 @@ get_mm_leaf(PARAM *param, Field *field, KEY_PART *key_part,
char
*
str
,
*
str2
;
DBUG_ENTER
(
"get_mm_leaf"
);
if
(
!
value
)
// IS NULL or IS NOT NULL
{
if
(
field
->
table
->
outer_join
)
// Can't use a key on this
DBUG_RETURN
(
0
);
if
(
!
maybe_null
)
// Not null field
DBUG_RETURN
(
type
==
Item_func
::
ISNULL_FUNC
?
&
null_element
:
0
);
if
(
!
(
tree
=
new
SEL_ARG
(
field
,
is_null_string
,
is_null_string
)))
DBUG_RETURN
(
0
);
// out of memory
if
(
type
==
Item_func
::
ISNOTNULL_FUNC
)
{
tree
->
min_flag
=
NEAR_MIN
;
/* IS NOT NULL -> X > NULL */
tree
->
max_flag
=
NO_MAX_RANGE
;
}
DBUG_RETURN
(
tree
);
}
/*
We can't use an index when comparing stings of
different collations
*/
if
(
field
->
result_type
()
==
STRING_RESULT
&&
value
->
result_type
()
==
STRING_RESULT
&&
key_part
->
image_type
==
Field
::
itRAW
&&
((
Field_str
*
)
field
)
->
charset
()
!=
conf_func
->
compare_collation
())
DBUG_RETURN
(
0
);
if
(
type
==
Item_func
::
LIKE_FUNC
)
{
bool
like_error
;
...
...
@@ -1035,22 +1066,6 @@ get_mm_leaf(PARAM *param, Field *field, KEY_PART *key_part,
DBUG_RETURN
(
new
SEL_ARG
(
field
,
min_str
,
max_str
));
}
if
(
!
value
)
// IS NULL or IS NOT NULL
{
if
(
field
->
table
->
outer_join
)
// Can't use a key on this
DBUG_RETURN
(
0
);
if
(
!
maybe_null
)
// Not null field
DBUG_RETURN
(
type
==
Item_func
::
ISNULL_FUNC
?
&
null_element
:
0
);
if
(
!
(
tree
=
new
SEL_ARG
(
field
,
is_null_string
,
is_null_string
)))
DBUG_RETURN
(
0
);
// out of memory
if
(
type
==
Item_func
::
ISNOTNULL_FUNC
)
{
tree
->
min_flag
=
NEAR_MIN
;
/* IS NOT NULL -> X > NULL */
tree
->
max_flag
=
NO_MAX_RANGE
;
}
DBUG_RETURN
(
tree
);
}
if
(
!
field
->
optimize_range
(
param
->
real_keynr
[
key_part
->
key
])
&&
type
!=
Item_func
::
EQ_FUNC
&&
type
!=
Item_func
::
EQUAL_FUNC
)
...
...
sql/sql_select.cc
View file @
e2ffbc1a
...
...
@@ -2131,7 +2131,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
*/
static
void
add_key_field
(
KEY_FIELD
**
key_fields
,
uint
and_level
,
add_key_field
(
KEY_FIELD
**
key_fields
,
uint
and_level
,
COND
*
cond
,
Field
*
field
,
bool
eq_func
,
Item
**
value
,
uint
num_values
,
table_map
usable_tables
)
{
...
...
@@ -2198,6 +2198,17 @@ add_key_field(KEY_FIELD **key_fields,uint and_level,
(
*
value
)
->
result_type
()
!=
STRING_RESULT
&&
field
->
cmp_type
()
!=
(
*
value
)
->
result_type
())
return
;
/*
We can't use indexes if the effective collation
of the operation differ from the field collation.
*/
if
(
field
->
result_type
()
==
STRING_RESULT
&&
(
*
value
)
->
result_type
()
==
STRING_RESULT
&&
field
->
cmp_type
()
==
STRING_RESULT
&&
((
Field_str
*
)
field
)
->
charset
()
!=
cond
->
compare_collation
())
return
;
}
}
DBUG_ASSERT
(
num_values
==
1
);
...
...
@@ -2261,7 +2272,7 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
// BETWEEN or IN
if
(
cond_func
->
key_item
()
->
real_item
()
->
type
()
==
Item
::
FIELD_ITEM
&&
!
(
cond_func
->
used_tables
()
&
OUTER_REF_TABLE_BIT
))
add_key_field
(
key_fields
,
*
and_level
,
add_key_field
(
key_fields
,
*
and_level
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
key_item
()
->
real_item
()))
->
field
,
0
,
cond_func
->
arguments
()
+
1
,
cond_func
->
argument_count
()
-
1
,
...
...
@@ -2275,7 +2286,7 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
if
(
cond_func
->
arguments
()[
0
]
->
real_item
()
->
type
()
==
Item
::
FIELD_ITEM
&&
!
(
cond_func
->
arguments
()[
0
]
->
used_tables
()
&
OUTER_REF_TABLE_BIT
))
{
add_key_field
(
key_fields
,
*
and_level
,
add_key_field
(
key_fields
,
*
and_level
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
arguments
()[
0
])
->
real_item
())
->
field
,
equal_func
,
...
...
@@ -2285,7 +2296,7 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
cond_func
->
functype
()
!=
Item_func
::
LIKE_FUNC
&&
!
(
cond_func
->
arguments
()[
1
]
->
used_tables
()
&
OUTER_REF_TABLE_BIT
))
{
add_key_field
(
key_fields
,
*
and_level
,
add_key_field
(
key_fields
,
*
and_level
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
arguments
()[
1
])
->
real_item
())
->
field
,
equal_func
,
...
...
@@ -2301,7 +2312,7 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
Item
*
tmp
=
new
Item_null
;
if
(
!
tmp
)
// Should never be true
return
;
add_key_field
(
key_fields
,
*
and_level
,
add_key_field
(
key_fields
,
*
and_level
,
cond_func
,
((
Item_field
*
)
(
cond_func
->
arguments
()[
0
])
->
real_item
())
->
field
,
cond_func
->
functype
()
==
Item_func
::
ISNULL_FUNC
,
...
...
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