Bug#15972635: Incorrect results returned in 32 table join with HAVING
The problem is a shift operation that is not 64-bit safe. The consequence is that used tables information for a join with 32 tables or more will be incorrect. Fixed by adding a type cast in Item_sum::update_used_tables(). Also used the opportunity to fix some other potential bugs by adding an explicit type-cast to an integer in a left-shift operation. Some of them were quite harmless, but was fixed in order to get the same signed-ness as the other operand of the operation it was used in. sql/item_cmpfunc.cc Adjusted signed-ness for some integers in left-shift. sql/item_subselect.cc Added type-cast to nesting_map (which is a 32/64 bit type, so potential bug for deeply nested queries). sql/item_sum.cc Added type-cast to nesting_map (32/64-bit type) and table_map (64-bit type). sql/opt_range.cc Added type-cast to ulonglong (which is a 64-bit type). sql/sql_base.cc Added type-cast to nesting_map (which is a 32/64-bit type). sql/sql_select.cc Added type-cast to nesting_map (32/64-bit type) and key_part_map (64-bit type). sql/strfunc.cc Changed type-cast from longlong to ulonglong, to preserve signed-ness.
This commit is contained in:
parent
56db769e10
commit
96c373c51a
@ -220,15 +220,15 @@ static uint collect_cmp_types(Item **items, uint nitems, bool skip_nulls= FALSE)
|
|||||||
items[i]->result_type() == ROW_RESULT) &&
|
items[i]->result_type() == ROW_RESULT) &&
|
||||||
cmp_row_type(items[0], items[i]))
|
cmp_row_type(items[0], items[i]))
|
||||||
return 0;
|
return 0;
|
||||||
found_types|= 1<< (uint)item_cmp_type(left_result,
|
found_types|= 1U << (uint)item_cmp_type(left_result,
|
||||||
items[i]->result_type());
|
items[i]->result_type());
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Even if all right-hand items are NULLs and we are skipping them all, we need
|
Even if all right-hand items are NULLs and we are skipping them all, we need
|
||||||
at least one type bit in the found_type bitmask.
|
at least one type bit in the found_type bitmask.
|
||||||
*/
|
*/
|
||||||
if (skip_nulls && !found_types)
|
if (skip_nulls && !found_types)
|
||||||
found_types= 1 << (uint)left_result;
|
found_types= 1U << (uint)left_result;
|
||||||
return found_types;
|
return found_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2897,12 +2897,12 @@ Item *Item_func_case::find_item(String *str)
|
|||||||
cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
||||||
DBUG_ASSERT(cmp_type != ROW_RESULT);
|
DBUG_ASSERT(cmp_type != ROW_RESULT);
|
||||||
DBUG_ASSERT(cmp_items[(uint)cmp_type]);
|
DBUG_ASSERT(cmp_items[(uint)cmp_type]);
|
||||||
if (!(value_added_map & (1<<(uint)cmp_type)))
|
if (!(value_added_map & (1U << (uint)cmp_type)))
|
||||||
{
|
{
|
||||||
cmp_items[(uint)cmp_type]->store_value(args[first_expr_num]);
|
cmp_items[(uint)cmp_type]->store_value(args[first_expr_num]);
|
||||||
if ((null_value=args[first_expr_num]->null_value))
|
if ((null_value=args[first_expr_num]->null_value))
|
||||||
return else_expr_num != -1 ? args[else_expr_num] : 0;
|
return else_expr_num != -1 ? args[else_expr_num] : 0;
|
||||||
value_added_map|= 1<<(uint)cmp_type;
|
value_added_map|= 1U << (uint)cmp_type;
|
||||||
}
|
}
|
||||||
if (!cmp_items[(uint)cmp_type]->cmp(args[i]) && !args[i]->null_value)
|
if (!cmp_items[(uint)cmp_type]->cmp(args[i]) && !args[i]->null_value)
|
||||||
return args[i + 1];
|
return args[i + 1];
|
||||||
@ -3107,7 +3107,7 @@ void Item_func_case::fix_length_and_dec()
|
|||||||
nagg++;
|
nagg++;
|
||||||
if (!(found_types= collect_cmp_types(agg, nagg)))
|
if (!(found_types= collect_cmp_types(agg, nagg)))
|
||||||
return;
|
return;
|
||||||
if (found_types & (1 << STRING_RESULT))
|
if (found_types & (1U << STRING_RESULT))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
If we'll do string comparison, we also need to aggregate
|
If we'll do string comparison, we also need to aggregate
|
||||||
@ -3147,7 +3147,7 @@ void Item_func_case::fix_length_and_dec()
|
|||||||
}
|
}
|
||||||
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
||||||
{
|
{
|
||||||
if (found_types & (1 << i) && !cmp_items[i])
|
if (found_types & (1U << i) && !cmp_items[i])
|
||||||
{
|
{
|
||||||
DBUG_ASSERT((Item_result)i != ROW_RESULT);
|
DBUG_ASSERT((Item_result)i != ROW_RESULT);
|
||||||
if (!(cmp_items[i]=
|
if (!(cmp_items[i]=
|
||||||
@ -3956,7 +3956,7 @@ void Item_func_in::fix_length_and_dec()
|
|||||||
}
|
}
|
||||||
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
||||||
{
|
{
|
||||||
if (found_types & 1 << i)
|
if (found_types & (1U << i))
|
||||||
{
|
{
|
||||||
(type_cnt)++;
|
(type_cnt)++;
|
||||||
cmp_type= (Item_result) i;
|
cmp_type= (Item_result) i;
|
||||||
@ -4139,7 +4139,7 @@ void Item_func_in::fix_length_and_dec()
|
|||||||
{
|
{
|
||||||
for (i= 0; i <= (uint) DECIMAL_RESULT; i++)
|
for (i= 0; i <= (uint) DECIMAL_RESULT; i++)
|
||||||
{
|
{
|
||||||
if (found_types & (1 << i) && !cmp_items[i])
|
if (found_types & (1U << i) && !cmp_items[i])
|
||||||
{
|
{
|
||||||
if ((Item_result)i == STRING_RESULT &&
|
if ((Item_result)i == STRING_RESULT &&
|
||||||
agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
|
agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
|
||||||
@ -4229,12 +4229,12 @@ longlong Item_func_in::val_int()
|
|||||||
Item_result cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
Item_result cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
||||||
in_item= cmp_items[(uint)cmp_type];
|
in_item= cmp_items[(uint)cmp_type];
|
||||||
DBUG_ASSERT(in_item);
|
DBUG_ASSERT(in_item);
|
||||||
if (!(value_added_map & (1 << (uint)cmp_type)))
|
if (!(value_added_map & (1U << (uint)cmp_type)))
|
||||||
{
|
{
|
||||||
in_item->store_value(args[0]);
|
in_item->store_value(args[0]);
|
||||||
if ((null_value= args[0]->null_value))
|
if ((null_value= args[0]->null_value))
|
||||||
return 0;
|
return 0;
|
||||||
value_added_map|= 1 << (uint)cmp_type;
|
value_added_map|= 1U << (uint)cmp_type;
|
||||||
}
|
}
|
||||||
if (!in_item->cmp(args[i]) && !args[i]->null_value)
|
if (!in_item->cmp(args[i]) && !args[i]->null_value)
|
||||||
return (longlong) (!negated);
|
return (longlong) (!negated);
|
||||||
|
@ -1042,7 +1042,8 @@ Item_in_subselect::single_value_transformer(JOIN *join,
|
|||||||
}
|
}
|
||||||
|
|
||||||
save_allow_sum_func= thd->lex->allow_sum_func;
|
save_allow_sum_func= thd->lex->allow_sum_func;
|
||||||
thd->lex->allow_sum_func|= 1 << thd->lex->current_select->nest_level;
|
thd->lex->allow_sum_func|=
|
||||||
|
(nesting_map)1 << thd->lex->current_select->nest_level;
|
||||||
/*
|
/*
|
||||||
Item_sum_(max|min) can't substitute other item => we can use 0 as
|
Item_sum_(max|min) can't substitute other item => we can use 0 as
|
||||||
reference, also Item_sum_(max|min) can't be fixed after creation, so
|
reference, also Item_sum_(max|min) can't be fixed after creation, so
|
||||||
|
@ -151,9 +151,10 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref)
|
|||||||
If it is there under a construct where it is not allowed
|
If it is there under a construct where it is not allowed
|
||||||
we report an error.
|
we report an error.
|
||||||
*/
|
*/
|
||||||
invalid= !(allow_sum_func & (1 << max_arg_level));
|
invalid= !(allow_sum_func & ((nesting_map)1 << max_arg_level));
|
||||||
}
|
}
|
||||||
else if (max_arg_level >= 0 || !(allow_sum_func & (1 << nest_level)))
|
else if (max_arg_level >= 0 ||
|
||||||
|
!(allow_sum_func & ((nesting_map)1 << nest_level)))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
The set function can be aggregated only in outer subqueries.
|
The set function can be aggregated only in outer subqueries.
|
||||||
@ -162,7 +163,8 @@ bool Item_sum::check_sum_func(THD *thd, Item **ref)
|
|||||||
*/
|
*/
|
||||||
if (register_sum_func(thd, ref))
|
if (register_sum_func(thd, ref))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
invalid= aggr_level < 0 && !(allow_sum_func & (1 << nest_level));
|
invalid= aggr_level < 0 &&
|
||||||
|
!(allow_sum_func & ((nesting_map)1 << nest_level));
|
||||||
if (!invalid && thd->variables.sql_mode & MODE_ANSI)
|
if (!invalid && thd->variables.sql_mode & MODE_ANSI)
|
||||||
invalid= aggr_level < 0 && max_arg_level < nest_level;
|
invalid= aggr_level < 0 && max_arg_level < nest_level;
|
||||||
}
|
}
|
||||||
@ -310,14 +312,15 @@ bool Item_sum::register_sum_func(THD *thd, Item **ref)
|
|||||||
sl && sl->nest_level > max_arg_level;
|
sl && sl->nest_level > max_arg_level;
|
||||||
sl= sl->master_unit()->outer_select() )
|
sl= sl->master_unit()->outer_select() )
|
||||||
{
|
{
|
||||||
if (aggr_level < 0 && (allow_sum_func & (1 << sl->nest_level)))
|
if (aggr_level < 0 &&
|
||||||
|
(allow_sum_func & ((nesting_map)1 << sl->nest_level)))
|
||||||
{
|
{
|
||||||
/* Found the most nested subquery where the function can be aggregated */
|
/* Found the most nested subquery where the function can be aggregated */
|
||||||
aggr_level= sl->nest_level;
|
aggr_level= sl->nest_level;
|
||||||
aggr_sel= sl;
|
aggr_sel= sl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sl && (allow_sum_func & (1 << sl->nest_level)))
|
if (sl && (allow_sum_func & ((nesting_map)1 << sl->nest_level)))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
We reached the subquery of level max_arg_level and checked
|
We reached the subquery of level max_arg_level and checked
|
||||||
@ -542,7 +545,7 @@ void Item_sum::update_used_tables ()
|
|||||||
used_tables_cache&= PSEUDO_TABLE_BITS;
|
used_tables_cache&= PSEUDO_TABLE_BITS;
|
||||||
|
|
||||||
/* the aggregate function is aggregated into its local context */
|
/* the aggregate function is aggregated into its local context */
|
||||||
used_tables_cache |= (1 << aggr_sel->join->tables) - 1;
|
used_tables_cache|= ((table_map)1 << aggr_sel->join->tables) - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9705,7 +9705,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
|
|||||||
cur_parts have bits set for only used keyparts.
|
cur_parts have bits set for only used keyparts.
|
||||||
*/
|
*/
|
||||||
ulonglong all_parts, cur_parts;
|
ulonglong all_parts, cur_parts;
|
||||||
all_parts= (1<<max_key_part) - 1;
|
all_parts= (1ULL << max_key_part) - 1;
|
||||||
cur_parts= used_key_parts_map.to_ulonglong() >> 1;
|
cur_parts= used_key_parts_map.to_ulonglong() >> 1;
|
||||||
if (all_parts != cur_parts)
|
if (all_parts != cur_parts)
|
||||||
goto next_index;
|
goto next_index;
|
||||||
|
@ -7891,7 +7891,8 @@ bool setup_fields(THD *thd, Item **ref_pointer_array,
|
|||||||
thd->mark_used_columns= mark_used_columns;
|
thd->mark_used_columns= mark_used_columns;
|
||||||
DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
|
DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
|
||||||
if (allow_sum_func)
|
if (allow_sum_func)
|
||||||
thd->lex->allow_sum_func|= 1 << thd->lex->current_select->nest_level;
|
thd->lex->allow_sum_func|=
|
||||||
|
(nesting_map)1 << thd->lex->current_select->nest_level;
|
||||||
thd->where= THD::DEFAULT_WHERE;
|
thd->where= THD::DEFAULT_WHERE;
|
||||||
save_is_item_list_lookup= thd->lex->current_select->is_item_list_lookup;
|
save_is_item_list_lookup= thd->lex->current_select->is_item_list_lookup;
|
||||||
thd->lex->current_select->is_item_list_lookup= 0;
|
thd->lex->current_select->is_item_list_lookup= 0;
|
||||||
|
@ -453,25 +453,25 @@ inline int setup_without_group(THD *thd, Item **ref_pointer_array,
|
|||||||
ORDER *group, bool *hidden_group_fields)
|
ORDER *group, bool *hidden_group_fields)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
nesting_map save_allow_sum_func=thd->lex->allow_sum_func ;
|
st_select_lex *const select= thd->lex->current_select;
|
||||||
|
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
|
||||||
/*
|
/*
|
||||||
Need to save the value, so we can turn off only any new non_agg_field_used
|
Need to save the value, so we can turn off only any new non_agg_field_used
|
||||||
additions coming from the WHERE
|
additions coming from the WHERE
|
||||||
*/
|
*/
|
||||||
const bool saved_non_agg_field_used=
|
const bool saved_non_agg_field_used= select->non_agg_field_used();
|
||||||
thd->lex->current_select->non_agg_field_used();
|
|
||||||
DBUG_ENTER("setup_without_group");
|
DBUG_ENTER("setup_without_group");
|
||||||
|
|
||||||
thd->lex->allow_sum_func&= ~(1 << thd->lex->current_select->nest_level);
|
thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
|
||||||
res= setup_conds(thd, tables, leaves, conds);
|
res= setup_conds(thd, tables, leaves, conds);
|
||||||
|
|
||||||
/* it's not wrong to have non-aggregated columns in a WHERE */
|
/* it's not wrong to have non-aggregated columns in a WHERE */
|
||||||
thd->lex->current_select->set_non_agg_field_used(saved_non_agg_field_used);
|
select->set_non_agg_field_used(saved_non_agg_field_used);
|
||||||
|
|
||||||
thd->lex->allow_sum_func|= 1 << thd->lex->current_select->nest_level;
|
thd->lex->allow_sum_func|= (nesting_map)1 << select->nest_level;
|
||||||
res= res || setup_order(thd, ref_pointer_array, tables, fields, all_fields,
|
res= res || setup_order(thd, ref_pointer_array, tables, fields, all_fields,
|
||||||
order);
|
order);
|
||||||
thd->lex->allow_sum_func&= ~(1 << thd->lex->current_select->nest_level);
|
thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
|
||||||
res= res || setup_group(thd, ref_pointer_array, tables, fields, all_fields,
|
res= res || setup_group(thd, ref_pointer_array, tables, fields, all_fields,
|
||||||
group, hidden_group_fields);
|
group, hidden_group_fields);
|
||||||
thd->lex->allow_sum_func= save_allow_sum_func;
|
thd->lex->allow_sum_func= save_allow_sum_func;
|
||||||
@ -559,7 +559,7 @@ JOIN::prepare(Item ***rref_pointer_array,
|
|||||||
{
|
{
|
||||||
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
|
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
|
||||||
thd->where="having clause";
|
thd->where="having clause";
|
||||||
thd->lex->allow_sum_func|= 1 << select_lex_arg->nest_level;
|
thd->lex->allow_sum_func|= (nesting_map)1 << select_lex_arg->nest_level;
|
||||||
select_lex->having_fix_field= 1;
|
select_lex->having_fix_field= 1;
|
||||||
bool having_fix_rc= (!having->fixed &&
|
bool having_fix_rc= (!having->fixed &&
|
||||||
(having->fix_fields(thd, &having) ||
|
(having->fix_fields(thd, &having) ||
|
||||||
@ -4547,7 +4547,8 @@ best_access_path(JOIN *join,
|
|||||||
in ReuseRangeEstimateForRef-3.
|
in ReuseRangeEstimateForRef-3.
|
||||||
*/
|
*/
|
||||||
if (table->quick_keys.is_set(key) &&
|
if (table->quick_keys.is_set(key) &&
|
||||||
(const_part & ((1 << table->quick_key_parts[key])-1)) ==
|
(const_part &
|
||||||
|
(((key_part_map)1 << table->quick_key_parts[key])-1)) ==
|
||||||
(((key_part_map)1 << table->quick_key_parts[key])-1) &&
|
(((key_part_map)1 << table->quick_key_parts[key])-1) &&
|
||||||
table->quick_n_ranges[key] == 1 &&
|
table->quick_n_ranges[key] == 1 &&
|
||||||
records > (double) table->quick_rows[key])
|
records > (double) table->quick_rows[key])
|
||||||
@ -4714,7 +4715,8 @@ best_access_path(JOIN *join,
|
|||||||
*/
|
*/
|
||||||
if (table->quick_keys.is_set(key) &&
|
if (table->quick_keys.is_set(key) &&
|
||||||
table->quick_key_parts[key] <= max_key_part &&
|
table->quick_key_parts[key] <= max_key_part &&
|
||||||
const_part & (1 << table->quick_key_parts[key]) &&
|
const_part &
|
||||||
|
((key_part_map)1 << table->quick_key_parts[key]) &&
|
||||||
table->quick_n_ranges[key] == 1 + test(ref_or_null_part &
|
table->quick_n_ranges[key] == 1 + test(ref_or_null_part &
|
||||||
const_part) &&
|
const_part) &&
|
||||||
records > (double) table->quick_rows[key])
|
records > (double) table->quick_rows[key])
|
||||||
@ -5950,7 +5952,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
|
|||||||
j->ref.items[i]=keyuse->val; // Save for cond removal
|
j->ref.items[i]=keyuse->val; // Save for cond removal
|
||||||
j->ref.cond_guards[i]= keyuse->cond_guard;
|
j->ref.cond_guards[i]= keyuse->cond_guard;
|
||||||
if (keyuse->null_rejecting)
|
if (keyuse->null_rejecting)
|
||||||
j->ref.null_rejecting |= 1 << i;
|
j->ref.null_rejecting|= (key_part_map)1 << i;
|
||||||
keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables;
|
keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables;
|
||||||
if (!keyuse->used_tables &&
|
if (!keyuse->used_tables &&
|
||||||
!(join->select_options & SELECT_DESCRIBE))
|
!(join->select_options & SELECT_DESCRIBE))
|
||||||
@ -6234,7 +6236,7 @@ static void add_not_null_conds(JOIN *join)
|
|||||||
{
|
{
|
||||||
for (uint keypart= 0; keypart < tab->ref.key_parts; keypart++)
|
for (uint keypart= 0; keypart < tab->ref.key_parts; keypart++)
|
||||||
{
|
{
|
||||||
if (tab->ref.null_rejecting & (1 << keypart))
|
if (tab->ref.null_rejecting & ((key_part_map)1 << keypart))
|
||||||
{
|
{
|
||||||
Item *item= tab->ref.items[keypart];
|
Item *item= tab->ref.items[keypart];
|
||||||
Item *notnull;
|
Item *notnull;
|
||||||
@ -10672,11 +10674,11 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
recinfo->null_bit= 1 << (null_count & 7);
|
recinfo->null_bit= (uint8)1 << (null_count & 7);
|
||||||
recinfo->null_pos= null_count/8;
|
recinfo->null_pos= null_count/8;
|
||||||
}
|
}
|
||||||
field->move_field(pos,null_flags+null_count/8,
|
field->move_field(pos,null_flags+null_count/8,
|
||||||
1 << (null_count & 7));
|
(uint8)1 << (null_count & 7));
|
||||||
null_count++;
|
null_count++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -11035,7 +11037,7 @@ TABLE *create_virtual_tmp_table(THD *thd, List<Create_field> &field_list)
|
|||||||
{
|
{
|
||||||
cur_field->move_field(field_pos, (uchar*) null_pos, null_bit);
|
cur_field->move_field(field_pos, (uchar*) null_pos, null_bit);
|
||||||
null_bit<<= 1;
|
null_bit<<= 1;
|
||||||
if (null_bit == (1 << 8))
|
if (null_bit == (uint)1 << 8)
|
||||||
{
|
{
|
||||||
++null_pos;
|
++null_pos;
|
||||||
null_bit= 1;
|
null_bit= 1;
|
||||||
@ -12386,7 +12388,8 @@ join_read_always_key(JOIN_TAB *tab)
|
|||||||
/* Perform "Late NULLs Filtering" (see internals manual for explanations) */
|
/* Perform "Late NULLs Filtering" (see internals manual for explanations) */
|
||||||
for (uint i= 0 ; i < tab->ref.key_parts ; i++)
|
for (uint i= 0 ; i < tab->ref.key_parts ; i++)
|
||||||
{
|
{
|
||||||
if ((tab->ref.null_rejecting & 1 << i) && tab->ref.items[i]->is_null())
|
if ((tab->ref.null_rejecting & ((key_part_map)1 << i)) &&
|
||||||
|
tab->ref.items[i]->is_null())
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ ulonglong find_set(TYPELIB *lib, const char *str, uint length, CHARSET_INFO *cs,
|
|||||||
*set_warning= 1;
|
*set_warning= 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
found|= ((longlong) 1 << (find - 1));
|
found|= 1ULL << (find - 1);
|
||||||
if (pos >= end)
|
if (pos >= end)
|
||||||
break;
|
break;
|
||||||
start= pos + mblen;
|
start= pos + mblen;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user