WL#1972 - manual merge with latest bk source tree
sql/item.cc: manual merge with latest bk source tree
This commit is contained in:
parent
b203c6c806
commit
a5585f62d8
473
sql/item.cc
473
sql/item.cc
@ -46,11 +46,11 @@ void item_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item::Item():
|
Item::Item():
|
||||||
name_length(0), fixed(0)
|
name_length(0), fixed(0),
|
||||||
|
collation(default_charset(), DERIVATION_COERCIBLE)
|
||||||
{
|
{
|
||||||
marker= 0;
|
marker= 0;
|
||||||
maybe_null=null_value=with_sum_func=unsigned_flag=0;
|
maybe_null=null_value=with_sum_func=unsigned_flag=0;
|
||||||
collation.set(default_charset(), DERIVATION_COERCIBLE);
|
|
||||||
name= 0;
|
name= 0;
|
||||||
decimals= 0; max_length= 0;
|
decimals= 0; max_length= 0;
|
||||||
|
|
||||||
@ -114,13 +114,31 @@ void Item::cleanup()
|
|||||||
DBUG_PRINT("info", ("Item: 0x%lx", this));
|
DBUG_PRINT("info", ("Item: 0x%lx", this));
|
||||||
DBUG_PRINT("info", ("Type: %d", (int)type()));
|
DBUG_PRINT("info", ("Type: %d", (int)type()));
|
||||||
fixed=0;
|
fixed=0;
|
||||||
|
marker= 0;
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
cleanup() item if it is 'fixed'
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
cleanup_processor()
|
||||||
|
arg - a dummy parameter, is not used here
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Item::cleanup_processor(byte *arg)
|
||||||
|
{
|
||||||
|
if (fixed)
|
||||||
|
cleanup();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Item_ident::Item_ident(const char *db_name_par,const char *table_name_par,
|
Item_ident::Item_ident(const char *db_name_par,const char *table_name_par,
|
||||||
const char *field_name_par)
|
const char *field_name_par)
|
||||||
:orig_db_name(db_name_par), orig_table_name(table_name_par),
|
:orig_db_name(db_name_par), orig_table_name(table_name_par),
|
||||||
orig_field_name(field_name_par), changed_during_fix_field(0),
|
orig_field_name(field_name_par),
|
||||||
db_name(db_name_par), table_name(table_name_par),
|
db_name(db_name_par), table_name(table_name_par),
|
||||||
field_name(field_name_par), cached_field_index(NO_CACHED_FIELD_INDEX),
|
field_name(field_name_par), cached_field_index(NO_CACHED_FIELD_INDEX),
|
||||||
cached_table(0), depended_from(0)
|
cached_table(0), depended_from(0)
|
||||||
@ -134,7 +152,6 @@ Item_ident::Item_ident(THD *thd, Item_ident *item)
|
|||||||
orig_db_name(item->orig_db_name),
|
orig_db_name(item->orig_db_name),
|
||||||
orig_table_name(item->orig_table_name),
|
orig_table_name(item->orig_table_name),
|
||||||
orig_field_name(item->orig_field_name),
|
orig_field_name(item->orig_field_name),
|
||||||
changed_during_fix_field(0),
|
|
||||||
db_name(item->db_name),
|
db_name(item->db_name),
|
||||||
table_name(item->table_name),
|
table_name(item->table_name),
|
||||||
field_name(item->field_name),
|
field_name(item->field_name),
|
||||||
@ -151,11 +168,6 @@ void Item_ident::cleanup()
|
|||||||
table_name, orig_table_name,
|
table_name, orig_table_name,
|
||||||
field_name, orig_field_name));
|
field_name, orig_field_name));
|
||||||
Item::cleanup();
|
Item::cleanup();
|
||||||
if (changed_during_fix_field)
|
|
||||||
{
|
|
||||||
*changed_during_fix_field= this;
|
|
||||||
changed_during_fix_field= 0;
|
|
||||||
}
|
|
||||||
db_name= orig_db_name;
|
db_name= orig_db_name;
|
||||||
table_name= orig_table_name;
|
table_name= orig_table_name;
|
||||||
field_name= orig_field_name;
|
field_name= orig_field_name;
|
||||||
@ -267,6 +279,41 @@ bool Item::eq(const Item *item, bool binary_cmp) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item *Item::safe_charset_converter(CHARSET_INFO *tocs)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Don't allow automatic conversion to non-Unicode charsets,
|
||||||
|
as it potentially loses data.
|
||||||
|
*/
|
||||||
|
if (!(tocs->state & MY_CS_UNICODE))
|
||||||
|
return NULL; // safe conversion is not possible
|
||||||
|
return new Item_func_conv_charset(this, tocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item *Item_string::safe_charset_converter(CHARSET_INFO *tocs)
|
||||||
|
{
|
||||||
|
Item_string *conv;
|
||||||
|
uint conv_errors;
|
||||||
|
String tmp, cstr, *ostr= val_str(&tmp);
|
||||||
|
cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors);
|
||||||
|
if (conv_errors || !(conv= new Item_string(cstr.ptr(), cstr.length(),
|
||||||
|
cstr.charset(),
|
||||||
|
collation.derivation)))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Safe conversion is not possible (or EOM).
|
||||||
|
We could not convert a string into the requested character set
|
||||||
|
without data loss. The target charset does not cover all the
|
||||||
|
characters from the string. Operation cannot be done correctly.
|
||||||
|
*/
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
conv->str_value.copy();
|
||||||
|
return conv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Item_string::eq(const Item *item, bool binary_cmp) const
|
bool Item_string::eq(const Item *item, bool binary_cmp) const
|
||||||
{
|
{
|
||||||
if (type() == item->type())
|
if (type() == item->type())
|
||||||
@ -361,7 +408,43 @@ Item_splocal::type() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DTCollation::aggregate(DTCollation &dt, bool superset_conversion)
|
|
||||||
|
/*
|
||||||
|
Aggregate two collations together taking
|
||||||
|
into account their coercibility (aka derivation):
|
||||||
|
|
||||||
|
0 == DERIVATION_EXPLICIT - an explicitely written COLLATE clause
|
||||||
|
1 == DERIVATION_NONE - a mix of two different collations
|
||||||
|
2 == DERIVATION_IMPLICIT - a column
|
||||||
|
3 == DERIVATION_COERCIBLE - a string constant
|
||||||
|
|
||||||
|
The most important rules are:
|
||||||
|
|
||||||
|
1. If collations are the same:
|
||||||
|
chose this collation, and the strongest derivation.
|
||||||
|
|
||||||
|
2. If collations are different:
|
||||||
|
- Character sets may differ, but only if conversion without
|
||||||
|
data loss is possible. The caller provides flags whether
|
||||||
|
character set conversion attempts should be done. If no
|
||||||
|
flags are substituted, then the character sets must be the same.
|
||||||
|
Currently processed flags are:
|
||||||
|
MY_COLL_ALLOW_SUPERSET_CONV - allow conversion to a superset
|
||||||
|
MY_COLL_ALLOW_COERCIBLE_CONV - allow conversion of a coercible value
|
||||||
|
- two EXPLICIT collations produce an error, e.g. this is wrong:
|
||||||
|
CONCAT(expr1 collate latin1_swedish_ci, expr2 collate latin1_german_ci)
|
||||||
|
- the side with smaller derivation value wins,
|
||||||
|
i.e. a column is stronger than a string constant,
|
||||||
|
an explicit COLLATE clause is stronger than a column.
|
||||||
|
- if derivations are the same, we have DERIVATION_NONE,
|
||||||
|
we'll wait for an explicit COLLATE clause which possibly can
|
||||||
|
come from another argument later: for example, this is valid,
|
||||||
|
but we don't know yet when collecting the first two arguments:
|
||||||
|
CONCAT(latin1_swedish_ci_column,
|
||||||
|
latin1_german1_ci_column,
|
||||||
|
expr COLLATE latin1_german2_ci)
|
||||||
|
*/
|
||||||
|
bool DTCollation::aggregate(DTCollation &dt, uint flags)
|
||||||
{
|
{
|
||||||
nagg++;
|
nagg++;
|
||||||
if (!my_charset_same(collation, dt.collation))
|
if (!my_charset_same(collation, dt.collation))
|
||||||
@ -392,26 +475,35 @@ bool DTCollation::aggregate(DTCollation &dt, bool superset_conversion)
|
|||||||
else
|
else
|
||||||
; // Do nothing
|
; // Do nothing
|
||||||
}
|
}
|
||||||
else if (superset_conversion)
|
else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) &&
|
||||||
|
derivation < dt.derivation &&
|
||||||
|
collation->state & MY_CS_UNICODE)
|
||||||
{
|
{
|
||||||
if (derivation < dt.derivation &&
|
// Do nothing
|
||||||
collation->state & MY_CS_UNICODE)
|
}
|
||||||
; // Do nothing
|
else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) &&
|
||||||
else if (dt.derivation < derivation &&
|
dt.derivation < derivation &&
|
||||||
dt.collation->state & MY_CS_UNICODE)
|
dt.collation->state & MY_CS_UNICODE)
|
||||||
{
|
{
|
||||||
set(dt);
|
set(dt);
|
||||||
strong= nagg;
|
strong= nagg;
|
||||||
}
|
}
|
||||||
else
|
else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) &&
|
||||||
{
|
derivation < dt.derivation &&
|
||||||
// Cannot convert to superset
|
dt.derivation == DERIVATION_COERCIBLE)
|
||||||
set(0, DERIVATION_NONE);
|
{
|
||||||
return 1;
|
// Do nothing;
|
||||||
}
|
}
|
||||||
|
else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) &&
|
||||||
|
dt.derivation < derivation &&
|
||||||
|
derivation == DERIVATION_COERCIBLE)
|
||||||
|
{
|
||||||
|
set(dt);
|
||||||
|
strong= nagg;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Cannot apply conversion
|
||||||
set(0, DERIVATION_NONE);
|
set(0, DERIVATION_NONE);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -452,19 +544,47 @@ Item_field::Item_field(Field *f)
|
|||||||
have_privileges(0), any_privileges(0)
|
have_privileges(0), any_privileges(0)
|
||||||
{
|
{
|
||||||
set_field(f);
|
set_field(f);
|
||||||
collation.set(DERIVATION_IMPLICIT);
|
/*
|
||||||
fixed= 1;
|
field_name and talbe_name should not point to garbage
|
||||||
|
if this item is to be reused
|
||||||
|
*/
|
||||||
|
orig_table_name= orig_field_name= "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Item_field::Item_field(THD *thd, Field *f)
|
Item_field::Item_field(THD *thd, Field *f)
|
||||||
:Item_ident(NullS, thd->strdup(f->table_name),
|
:Item_ident(f->table->table_cache_key, f->table_name, f->field_name),
|
||||||
thd->strdup(f->field_name)),
|
|
||||||
item_equal(0), no_const_subst(0),
|
item_equal(0), no_const_subst(0),
|
||||||
have_privileges(0), any_privileges(0)
|
have_privileges(0), any_privileges(0)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
We always need to provide Item_field with a fully qualified field
|
||||||
|
name to avoid ambiguity when executing prepared statements like
|
||||||
|
SELECT * from d1.t1, d2.t1; (assuming d1.t1 and d2.t1 have columns
|
||||||
|
with same names).
|
||||||
|
This is because prepared statements never deal with wildcards in
|
||||||
|
select list ('*') and always fix fields using fully specified path
|
||||||
|
(i.e. db.table.column).
|
||||||
|
No check for OOM: if db_name is NULL, we'll just get
|
||||||
|
"Field not found" error.
|
||||||
|
We need to copy db_name, table_name and field_name because they must
|
||||||
|
be allocated in the statement memory, not in table memory (the table
|
||||||
|
structure can go away and pop up again between subsequent executions
|
||||||
|
of a prepared statement).
|
||||||
|
*/
|
||||||
|
if (thd->current_arena->is_stmt_prepare())
|
||||||
|
{
|
||||||
|
if (db_name)
|
||||||
|
orig_db_name= thd->strdup(db_name);
|
||||||
|
orig_table_name= thd->strdup(table_name);
|
||||||
|
orig_field_name= thd->strdup(field_name);
|
||||||
|
/*
|
||||||
|
We don't restore 'name' in cleanup because it's not changed
|
||||||
|
during execution. Still we need it to point to persistent
|
||||||
|
memory if this item is to be reused.
|
||||||
|
*/
|
||||||
|
name= (char*) orig_field_name;
|
||||||
|
}
|
||||||
set_field(f);
|
set_field(f);
|
||||||
collation.set(DERIVATION_IMPLICIT);
|
|
||||||
fixed= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor need to process subselect with temporary tables (see Item)
|
// Constructor need to process subselect with temporary tables (see Item)
|
||||||
@ -491,6 +611,21 @@ void Item_field::set_field(Field *field_par)
|
|||||||
db_name=field_par->table->table_cache_key;
|
db_name=field_par->table->table_cache_key;
|
||||||
unsigned_flag=test(field_par->flags & UNSIGNED_FLAG);
|
unsigned_flag=test(field_par->flags & UNSIGNED_FLAG);
|
||||||
collation.set(field_par->charset(), DERIVATION_IMPLICIT);
|
collation.set(field_par->charset(), DERIVATION_IMPLICIT);
|
||||||
|
fixed= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset this item to point to a field from the new temporary table.
|
||||||
|
This is used when we create a new temporary table for each execution
|
||||||
|
of prepared statement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Item_field::reset_field(Field *f)
|
||||||
|
{
|
||||||
|
set_field(f);
|
||||||
|
/* 'name' is pointing at field->field_name of old field */
|
||||||
|
name= (char*) f->field_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *Item_ident::full_name() const
|
const char *Item_ident::full_name() const
|
||||||
@ -793,6 +928,12 @@ String *Item_null::val_str(String *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item *Item_null::safe_charset_converter(CHARSET_INFO *tocs)
|
||||||
|
{
|
||||||
|
collation.set(tocs);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/*********************** Item_param related ******************************/
|
/*********************** Item_param related ******************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -888,7 +1029,9 @@ bool Item_param::set_str(const char *str, ulong length)
|
|||||||
Assign string with no conversion: data is converted only after it's
|
Assign string with no conversion: data is converted only after it's
|
||||||
been written to the binary log.
|
been written to the binary log.
|
||||||
*/
|
*/
|
||||||
if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin))
|
uint dummy_errors;
|
||||||
|
if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin,
|
||||||
|
&dummy_errors))
|
||||||
DBUG_RETURN(TRUE);
|
DBUG_RETURN(TRUE);
|
||||||
state= STRING_VALUE;
|
state= STRING_VALUE;
|
||||||
maybe_null= 0;
|
maybe_null= 0;
|
||||||
@ -1043,7 +1186,7 @@ int Item_param::save_in_field(Field *field, bool no_conversions)
|
|||||||
return field->store(str_value.ptr(), str_value.length(),
|
return field->store(str_value.ptr(), str_value.length(),
|
||||||
str_value.charset());
|
str_value.charset());
|
||||||
case NULL_VALUE:
|
case NULL_VALUE:
|
||||||
return set_field_to_null(field);
|
return set_field_to_null_with_conversions(field, no_conversions);
|
||||||
case NO_VALUE:
|
case NO_VALUE:
|
||||||
default:
|
default:
|
||||||
DBUG_ASSERT(0);
|
DBUG_ASSERT(0);
|
||||||
@ -1146,9 +1289,10 @@ String *Item_param::val_str(String* str)
|
|||||||
return str;
|
return str;
|
||||||
case TIME_VALUE:
|
case TIME_VALUE:
|
||||||
{
|
{
|
||||||
if (str->reserve(MAX_DATE_REP_LENGTH))
|
if (str->reserve(MAX_DATE_STRING_REP_LENGTH))
|
||||||
break;
|
break;
|
||||||
TIME_to_string(&value.time, str);
|
str->length((uint) my_TIME_to_str(&value.time, (char*) str->ptr()));
|
||||||
|
str->set_charset(&my_charset_bin);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
case NULL_VALUE:
|
case NULL_VALUE:
|
||||||
@ -1178,24 +1322,19 @@ const String *Item_param::query_val_str(String* str) const
|
|||||||
case TIME_VALUE:
|
case TIME_VALUE:
|
||||||
{
|
{
|
||||||
char *buf, *ptr;
|
char *buf, *ptr;
|
||||||
String tmp;
|
|
||||||
str->length(0);
|
str->length(0);
|
||||||
/*
|
/*
|
||||||
TODO: in case of error we need to notify replication
|
TODO: in case of error we need to notify replication
|
||||||
that binary log contains wrong statement
|
that binary log contains wrong statement
|
||||||
*/
|
*/
|
||||||
if (str->reserve(MAX_DATE_REP_LENGTH+3))
|
if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Create date string inplace */
|
/* Create date string inplace */
|
||||||
buf= str->c_ptr_quick();
|
buf= str->c_ptr_quick();
|
||||||
ptr= buf;
|
ptr= buf;
|
||||||
*ptr++= '\'';
|
*ptr++= '\'';
|
||||||
tmp.set(ptr, MAX_DATE_REP_LENGTH, &my_charset_bin);
|
ptr+= (uint) my_TIME_to_str(&value.time, ptr);
|
||||||
tmp.length(0);
|
|
||||||
TIME_to_string(&value.time, &tmp);
|
|
||||||
|
|
||||||
ptr+= tmp.length();
|
|
||||||
*ptr++= '\'';
|
*ptr++= '\'';
|
||||||
str->length((uint32) (ptr - buf));
|
str->length((uint32) (ptr - buf));
|
||||||
break;
|
break;
|
||||||
@ -1249,6 +1388,10 @@ bool Item_param::convert_str_value(THD *thd)
|
|||||||
value.cs_info.character_set_client,
|
value.cs_info.character_set_client,
|
||||||
value.cs_info.final_character_set_of_str_value);
|
value.cs_info.final_character_set_of_str_value);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
str_value.set_charset(value.cs_info.final_character_set_of_str_value);
|
||||||
|
/* Here str_value is guaranteed to be in final_character_set_of_str_value */
|
||||||
|
|
||||||
max_length= str_value.length();
|
max_length= str_value.length();
|
||||||
decimals= 0;
|
decimals= 0;
|
||||||
/*
|
/*
|
||||||
@ -1373,7 +1516,7 @@ bool Item_ref_null_helper::get_date(TIME *ltime, uint fuzzydate)
|
|||||||
static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current,
|
static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current,
|
||||||
Item_ident *item)
|
Item_ident *item)
|
||||||
{
|
{
|
||||||
// store pointer on SELECT_LEX from wich item is dependent
|
// store pointer on SELECT_LEX from which item is dependent
|
||||||
item->depended_from= last;
|
item->depended_from= last;
|
||||||
current->mark_as_dependent(last);
|
current->mark_as_dependent(last);
|
||||||
if (thd->lex->describe & DESCRIBE_EXTENDED)
|
if (thd->lex->describe & DESCRIBE_EXTENDED)
|
||||||
@ -1390,6 +1533,8 @@ static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Search a GROUP BY clause for a field with a certain name.
|
Search a GROUP BY clause for a field with a certain name.
|
||||||
|
|
||||||
@ -1534,13 +1679,14 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select)
|
|||||||
ORDER *group_list= (ORDER*) select->group_list.first;
|
ORDER *group_list= (ORDER*) select->group_list.first;
|
||||||
bool ambiguous_fields= FALSE;
|
bool ambiguous_fields= FALSE;
|
||||||
uint counter;
|
uint counter;
|
||||||
|
bool not_used;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Search for a column or derived column named as 'ref' in the SELECT
|
Search for a column or derived column named as 'ref' in the SELECT
|
||||||
clause of the current select.
|
clause of the current select.
|
||||||
*/
|
*/
|
||||||
if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter,
|
if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter,
|
||||||
REPORT_EXCEPT_NOT_FOUND)))
|
REPORT_EXCEPT_NOT_FOUND, ¬_used)))
|
||||||
return NULL; /* Some error occurred. */
|
return NULL; /* Some error occurred. */
|
||||||
|
|
||||||
/* If this is a non-aggregated field inside HAVING, search in GROUP BY. */
|
/* If this is a non-aggregated field inside HAVING, search in GROUP BY. */
|
||||||
@ -1687,10 +1833,10 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
enum_parsing_place place= prev_subselect_item->parsing_place;
|
enum_parsing_place place= prev_subselect_item->parsing_place;
|
||||||
/*
|
/*
|
||||||
Check table fields only if the subquery is used somewhere out of
|
Check table fields only if the subquery is used somewhere out of
|
||||||
HAVING or SELECT list, or the outer SELECT does not use grouping
|
HAVING, or the outer SELECT does not use grouping (i.e. tables are
|
||||||
(i.e. tables are accessible).
|
accessible).
|
||||||
*/
|
*/
|
||||||
if (((place != IN_HAVING && place != SELECT_LIST) ||
|
if ((place != IN_HAVING ||
|
||||||
(outer_sel->with_sum_func == 0 &&
|
(outer_sel->with_sum_func == 0 &&
|
||||||
outer_sel->group_list.elements == 0)) &&
|
outer_sel->group_list.elements == 0)) &&
|
||||||
(from_field= find_field_in_tables(thd, this, table_list,
|
(from_field= find_field_in_tables(thd, this, table_list,
|
||||||
@ -1762,15 +1908,14 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
}
|
}
|
||||||
else if (ref != not_found_item)
|
else if (ref != not_found_item)
|
||||||
{
|
{
|
||||||
/* Should be checked in resolve_ref_in_select_and_group(). */
|
/* Should have been checked in resolve_ref_in_select_and_group(). */
|
||||||
DBUG_ASSERT(*ref && (*ref)->fixed);
|
DBUG_ASSERT(*ref && (*ref)->fixed);
|
||||||
|
|
||||||
Item_ref *rf;
|
Item_ref *rf= new Item_ref(last->ref_pointer_array + counter,
|
||||||
*reference= rf= new Item_ref(ref, reference, (char *) table_name,
|
(char *)table_name, (char *)field_name);
|
||||||
(char *) field_name);
|
|
||||||
register_item_tree_changing(reference);
|
|
||||||
if (!rf)
|
if (!rf)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
thd->change_item_tree(reference, rf);
|
||||||
/*
|
/*
|
||||||
rf is Item_ref => never substitute other items (in this case)
|
rf is Item_ref => never substitute other items (in this case)
|
||||||
during fix_fields() => we can use rf after fix_fields()
|
during fix_fields() => we can use rf after fix_fields()
|
||||||
@ -1787,12 +1932,11 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
if (last->having_fix_field)
|
if (last->having_fix_field)
|
||||||
{
|
{
|
||||||
Item_ref *rf;
|
Item_ref *rf;
|
||||||
*reference= rf= new Item_ref(reference, *reference,
|
rf= new Item_ref((cached_table->db[0] ? cached_table->db : 0),
|
||||||
(cached_table->db[0]?cached_table->db:0),
|
(char*) cached_table->alias, (char*) field_name);
|
||||||
(char *)cached_table->alias,
|
|
||||||
(char *)field_name);
|
|
||||||
if (!rf)
|
if (!rf)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
thd->change_item_tree(reference, rf);
|
||||||
/*
|
/*
|
||||||
rf is Item_ref => never substitute other items (in this case)
|
rf is Item_ref => never substitute other items (in this case)
|
||||||
during fix_fields() => we can use rf after fix_fields()
|
during fix_fields() => we can use rf after fix_fields()
|
||||||
@ -1861,6 +2005,14 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item *Item_field::safe_charset_converter(CHARSET_INFO *tocs)
|
||||||
|
{
|
||||||
|
no_const_subst= 1;
|
||||||
|
return Item::safe_charset_converter(tocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Item_field::cleanup()
|
void Item_field::cleanup()
|
||||||
{
|
{
|
||||||
DBUG_ENTER("Item_field::cleanup");
|
DBUG_ENTER("Item_field::cleanup");
|
||||||
@ -1871,6 +2023,7 @@ void Item_field::cleanup()
|
|||||||
I.e. we can drop 'field'.
|
I.e. we can drop 'field'.
|
||||||
*/
|
*/
|
||||||
field= result_field= 0;
|
field= result_field= 0;
|
||||||
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1915,7 +2068,8 @@ Item_equal *Item_field::find_item_equal(COND_EQUAL *cond_equal)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set a pointer to the multiple equality the field reference belongs to (if any)
|
Set a pointer to the multiple equality the field reference belongs to
|
||||||
|
(if any)
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
equal_fields_propagator()
|
equal_fields_propagator()
|
||||||
@ -1954,7 +2108,21 @@ Item *Item_field::equal_fields_propagator(byte *arg)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set a pointer to the multiple equality the field reference belongs to (if any)
|
Mark the item to not be part of substitution if it's not a binary item
|
||||||
|
See comments in Arg_comparator::set_compare_func() for details
|
||||||
|
*/
|
||||||
|
|
||||||
|
Item *Item_field::set_no_const_sub(byte *arg)
|
||||||
|
{
|
||||||
|
if (field->charset() != &my_charset_bin)
|
||||||
|
no_const_subst=1;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set a pointer to the multiple equality the field reference belongs to
|
||||||
|
(if any)
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
replace_equal_field_processor()
|
replace_equal_field_processor()
|
||||||
@ -1990,6 +2158,7 @@ bool Item_field::replace_equal_field_processor(byte *arg)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Item::init_make_field(Send_field *tmp_field,
|
void Item::init_make_field(Send_field *tmp_field,
|
||||||
enum enum_field_types field_type)
|
enum enum_field_types field_type)
|
||||||
{
|
{
|
||||||
@ -2000,7 +2169,9 @@ void Item::init_make_field(Send_field *tmp_field,
|
|||||||
tmp_field->table_name= empty_name;
|
tmp_field->table_name= empty_name;
|
||||||
tmp_field->col_name= name;
|
tmp_field->col_name= name;
|
||||||
tmp_field->charsetnr= collation.collation->number;
|
tmp_field->charsetnr= collation.collation->number;
|
||||||
tmp_field->flags=maybe_null ? 0 : NOT_NULL_FLAG;
|
tmp_field->flags= (maybe_null ? 0 : NOT_NULL_FLAG) |
|
||||||
|
(my_binary_compare(collation.collation) ?
|
||||||
|
BINARY_FLAG : 0);
|
||||||
tmp_field->type=field_type;
|
tmp_field->type=field_type;
|
||||||
tmp_field->length=max_length;
|
tmp_field->length=max_length;
|
||||||
tmp_field->decimals=decimals;
|
tmp_field->decimals=decimals;
|
||||||
@ -2629,15 +2800,15 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
enum_parsing_place place= prev_subselect_item->parsing_place;
|
enum_parsing_place place= prev_subselect_item->parsing_place;
|
||||||
/*
|
/*
|
||||||
Check table fields only if the subquery is used somewhere out of
|
Check table fields only if the subquery is used somewhere out of
|
||||||
HAVING or SELECT list, or the outer SELECT does not use grouping
|
HAVING or the outer SELECT does not use grouping (i.e. tables are
|
||||||
(i.e. tables are accessible).
|
accessible).
|
||||||
TODO:
|
TODO:
|
||||||
Here we could first find the field anyway, and then test this
|
Here we could first find the field anyway, and then test this
|
||||||
condition, so that we can give a better error message -
|
condition, so that we can give a better error message -
|
||||||
ER_WRONG_FIELD_WITH_GROUP, instead of the less informative
|
ER_WRONG_FIELD_WITH_GROUP, instead of the less informative
|
||||||
ER_BAD_FIELD_ERROR which we produce now.
|
ER_BAD_FIELD_ERROR which we produce now.
|
||||||
*/
|
*/
|
||||||
if (((place != IN_HAVING && place != SELECT_LIST) ||
|
if ((place != IN_HAVING ||
|
||||||
(!outer_sel->with_sum_func &&
|
(!outer_sel->with_sum_func &&
|
||||||
outer_sel->group_list.elements == 0)))
|
outer_sel->group_list.elements == 0)))
|
||||||
{
|
{
|
||||||
@ -2679,19 +2850,24 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference)
|
|||||||
{
|
{
|
||||||
my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0),
|
my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0),
|
||||||
this->full_name(), current_thd->where);
|
this->full_name(), current_thd->where);
|
||||||
ref= 0;
|
ref= 0; // Safety
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
else if (from_field != not_found_field)
|
if (from_field != not_found_field)
|
||||||
{
|
{
|
||||||
ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item
|
/*
|
||||||
|
Set ref to 0 as we are replacing this item with the found item and
|
||||||
|
this will ensure we get an error if this item would be used
|
||||||
|
elsewhere
|
||||||
|
*/
|
||||||
|
ref= 0; // Safety
|
||||||
if (from_field != view_ref_found)
|
if (from_field != view_ref_found)
|
||||||
{
|
{
|
||||||
Item_field* fld= new Item_field(from_field);
|
Item_field* fld;
|
||||||
if (!((*reference)= fld))
|
if (!(fld= new Item_field(tmp)))
|
||||||
return TRUE;
|
return TRUE;
|
||||||
mark_as_dependent(thd, last, current_sel, fld);
|
thd->change_item_tree(reference, fld);
|
||||||
register_item_tree_changing(reference);
|
mark_as_dependent(thd, last, thd->lex->current_select, fld);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -2757,8 +2933,6 @@ void Item_ref::cleanup()
|
|||||||
DBUG_ENTER("Item_ref::cleanup");
|
DBUG_ENTER("Item_ref::cleanup");
|
||||||
Item_ident::cleanup();
|
Item_ident::cleanup();
|
||||||
result_field= 0;
|
result_field= 0;
|
||||||
if (hook_ptr)
|
|
||||||
*hook_ptr= orig_item;
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2881,7 +3055,6 @@ bool Item_default_value::fix_fields(THD *thd,
|
|||||||
def_field->move_field(def_field->table->default_values -
|
def_field->move_field(def_field->table->default_values -
|
||||||
def_field->table->record[0]);
|
def_field->table->record[0]);
|
||||||
set_field(def_field);
|
set_field(def_field);
|
||||||
fixed= 1;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2939,7 +3112,6 @@ bool Item_insert_value::fix_fields(THD *thd,
|
|||||||
set_field(new Field_null(0, 0, Field::NONE, tmp_field->field_name,
|
set_field(new Field_null(0, 0, Field::NONE, tmp_field->field_name,
|
||||||
tmp_field->table, &my_charset_bin));
|
tmp_field->table, &my_charset_bin));
|
||||||
}
|
}
|
||||||
fixed= 1;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3058,10 +3230,12 @@ Item_result item_cmp_type(Item_result a,Item_result b)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Item *resolve_const_item(Item *item,Item *comp_item)
|
void resolve_const_item(THD *thd, Item **ref, Item *comp_item)
|
||||||
{
|
{
|
||||||
|
Item *item= *ref;
|
||||||
|
Item *new_item;
|
||||||
if (item->basic_const_item())
|
if (item->basic_const_item())
|
||||||
return item; // Can't be better
|
return; // Can't be better
|
||||||
Item_result res_type=item_cmp_type(comp_item->result_type(),
|
Item_result res_type=item_cmp_type(comp_item->result_type(),
|
||||||
item->result_type());
|
item->result_type());
|
||||||
char *name=item->name; // Alloced by sql_alloc
|
char *name=item->name; // Alloced by sql_alloc
|
||||||
@ -3072,27 +3246,32 @@ Item *resolve_const_item(Item *item,Item *comp_item)
|
|||||||
String tmp(buff,sizeof(buff),&my_charset_bin),*result;
|
String tmp(buff,sizeof(buff),&my_charset_bin),*result;
|
||||||
result=item->val_str(&tmp);
|
result=item->val_str(&tmp);
|
||||||
if (item->null_value)
|
if (item->null_value)
|
||||||
return new Item_null(name);
|
new_item= new Item_null(name);
|
||||||
uint length=result->length();
|
else
|
||||||
char *tmp_str=sql_strmake(result->ptr(),length);
|
{
|
||||||
return new Item_string(name,tmp_str,length,result->charset());
|
uint length= result->length();
|
||||||
|
char *tmp_str= sql_strmake(result->ptr(), length);
|
||||||
|
new_item= new Item_string(name, tmp_str, length, result->charset());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (res_type == INT_RESULT)
|
else if (res_type == INT_RESULT)
|
||||||
{
|
{
|
||||||
longlong result=item->val_int();
|
longlong result=item->val_int();
|
||||||
uint length=item->max_length;
|
uint length=item->max_length;
|
||||||
bool null_value=item->null_value;
|
bool null_value=item->null_value;
|
||||||
return (null_value ? (Item*) new Item_null(name) :
|
new_item= (null_value ? (Item*) new Item_null(name) :
|
||||||
(Item*) new Item_int(name,result,length));
|
(Item*) new Item_int(name, result, length));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // It must REAL_RESULT
|
{ // It must REAL_RESULT
|
||||||
double result=item->val();
|
double result=item->val();
|
||||||
uint length=item->max_length,decimals=item->decimals;
|
uint length=item->max_length,decimals=item->decimals;
|
||||||
bool null_value=item->null_value;
|
bool null_value=item->null_value;
|
||||||
return (null_value ? (Item*) new Item_null(name) :
|
new_item= (null_value ? (Item*) new Item_null(name) : (Item*)
|
||||||
(Item*) new Item_real(name,result,decimals,length));
|
new Item_real(name, result, decimals, length));
|
||||||
}
|
}
|
||||||
|
if (new_item)
|
||||||
|
thd->change_item_tree(ref, new_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -3319,6 +3498,7 @@ Item_type_holder::Item_type_holder(THD *thd, Item *item)
|
|||||||
else
|
else
|
||||||
field_example= 0;
|
field_example= 0;
|
||||||
max_length= real_length(item);
|
max_length= real_length(item);
|
||||||
|
maybe_null= item->maybe_null;
|
||||||
collation.set(item->collation);
|
collation.set(item->collation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3336,62 +3516,90 @@ static Item_result type_convertor[4][4]=
|
|||||||
{STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT},
|
{STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT},
|
||||||
{ROW_RESULT, ROW_RESULT, ROW_RESULT, ROW_RESULT}};
|
{ROW_RESULT, ROW_RESULT, ROW_RESULT, ROW_RESULT}};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Values of 'from' field can be stored in 'to' field.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
is_attr_compatible()
|
||||||
|
from Item which values should be saved
|
||||||
|
to Item where values should be saved
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
1 can be saved
|
||||||
|
0 can not be saved
|
||||||
|
*/
|
||||||
|
|
||||||
|
inline bool is_attr_compatible(Item *from, Item *to)
|
||||||
|
{
|
||||||
|
return ((to->max_length >= from->max_length) &&
|
||||||
|
(to->maybe_null || !from->maybe_null) &&
|
||||||
|
(to->result_type() != STRING_RESULT ||
|
||||||
|
from->result_type() != STRING_RESULT ||
|
||||||
|
my_charset_same(from->collation.collation,
|
||||||
|
to->collation.collation)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Item_type_holder::join_types(THD *thd, Item *item)
|
bool Item_type_holder::join_types(THD *thd, Item *item)
|
||||||
{
|
{
|
||||||
uint32 new_length= real_length(item);
|
uint32 new_length= real_length(item);
|
||||||
bool change_field= 0, skip_store_field= 0;
|
bool use_new_field= 0, use_expression_type= 0;
|
||||||
Item_result new_type= type_convertor[item_type][item->result_type()];
|
Item_result new_result_type= type_convertor[item_type][item->result_type()];
|
||||||
|
bool item_is_a_field= item->type() == Item::FIELD_ITEM;
|
||||||
|
|
||||||
// we have both fields
|
/*
|
||||||
if (field_example && item->type() == Item::FIELD_ITEM)
|
Check if both items point to fields: in this case we
|
||||||
|
can adjust column types of result table in the union smartly.
|
||||||
|
*/
|
||||||
|
if (field_example && item_is_a_field)
|
||||||
{
|
{
|
||||||
Field *field= ((Item_field *)item)->field;
|
Field *field= ((Item_field *)item)->field;
|
||||||
if (field_example->field_cast_type() != field->field_cast_type())
|
/* Can 'field_example' field store data of the column? */
|
||||||
|
if ((use_new_field=
|
||||||
|
(!field->field_cast_compatible(field_example->field_cast_type()) ||
|
||||||
|
!is_attr_compatible(item, this))))
|
||||||
{
|
{
|
||||||
if (!(change_field=
|
/*
|
||||||
field_example->field_cast_compatible(field->field_cast_type())))
|
The old field can't store value of the new field.
|
||||||
{
|
Check if the new field can store value of the old one.
|
||||||
/*
|
*/
|
||||||
if old field can't store value of 'worse' new field we will make
|
use_expression_type|=
|
||||||
decision about result field type based only on Item result type
|
(!field_example->field_cast_compatible(field->field_cast_type()) ||
|
||||||
*/
|
!is_attr_compatible(this, item));
|
||||||
if (!field->field_cast_compatible(field_example->field_cast_type()))
|
|
||||||
skip_store_field= 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (field_example || item_is_a_field)
|
||||||
// size/type should be changed
|
|
||||||
if (change_field ||
|
|
||||||
(new_type != item_type) ||
|
|
||||||
(max_length < new_length) ||
|
|
||||||
((new_type == INT_RESULT) &&
|
|
||||||
(decimals < item->decimals)) ||
|
|
||||||
(!maybe_null && item->maybe_null) ||
|
|
||||||
(item_type == STRING_RESULT && new_type == STRING_RESULT &&
|
|
||||||
!my_charset_same(collation.collation, item->collation.collation)))
|
|
||||||
{
|
{
|
||||||
// new field has some parameters worse then current
|
|
||||||
skip_store_field|= (change_field &&
|
|
||||||
(max_length > new_length) ||
|
|
||||||
((new_type == INT_RESULT) &&
|
|
||||||
(decimals > item->decimals)) ||
|
|
||||||
(maybe_null && !item->maybe_null) ||
|
|
||||||
(item_type == STRING_RESULT &&
|
|
||||||
new_type == STRING_RESULT &&
|
|
||||||
!my_charset_same(collation.collation,
|
|
||||||
item->collation.collation)));
|
|
||||||
/*
|
/*
|
||||||
It is safe assign pointer on field, because it will be used just after
|
Expression types can't be mixed with field types, we have to use
|
||||||
all JOIN::prepare calls and before any SELECT execution
|
expression types.
|
||||||
*/
|
*/
|
||||||
if (skip_store_field || item->type() != Item::FIELD_ITEM)
|
use_new_field= 1; // make next if test easier
|
||||||
|
use_expression_type= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check whether size/type of the result item should be changed */
|
||||||
|
if (use_new_field ||
|
||||||
|
(new_result_type != item_type) || (new_length > max_length) ||
|
||||||
|
(!maybe_null && item->maybe_null) ||
|
||||||
|
(item_type == STRING_RESULT &&
|
||||||
|
collation.collation != item->collation.collation))
|
||||||
|
{
|
||||||
|
const char *old_cs,*old_derivation;
|
||||||
|
if (use_expression_type || !item_is_a_field)
|
||||||
field_example= 0;
|
field_example= 0;
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
It is safe to assign a pointer to field here, because it will be used
|
||||||
|
before any table is closed.
|
||||||
|
*/
|
||||||
field_example= ((Item_field*) item)->field;
|
field_example= ((Item_field*) item)->field;
|
||||||
|
}
|
||||||
|
|
||||||
const char *old_cs= collation.collation->name,
|
old_cs= collation.collation->name;
|
||||||
*old_derivation= collation.derivation_name();
|
old_derivation= collation.derivation_name();
|
||||||
if (item_type == STRING_RESULT && collation.aggregate(item->collation))
|
if (item_type == STRING_RESULT && collation.aggregate(item->collation))
|
||||||
{
|
{
|
||||||
my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0),
|
my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0),
|
||||||
@ -3405,18 +3613,18 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
|
|||||||
max_length= max(max_length, new_length);
|
max_length= max(max_length, new_length);
|
||||||
decimals= max(decimals, item->decimals);
|
decimals= max(decimals, item->decimals);
|
||||||
maybe_null|= item->maybe_null;
|
maybe_null|= item->maybe_null;
|
||||||
item_type= new_type;
|
item_type= new_result_type;
|
||||||
}
|
}
|
||||||
DBUG_ASSERT(item_type != ROW_RESULT);
|
DBUG_ASSERT(item_type != ROW_RESULT);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint32 Item_type_holder::real_length(Item *item)
|
uint32 Item_type_holder::real_length(Item *item)
|
||||||
{
|
{
|
||||||
if (item->type() == Item::FIELD_ITEM)
|
if (item->type() == Item::FIELD_ITEM)
|
||||||
{
|
|
||||||
return ((Item_field *)item)->max_disp_length();
|
return ((Item_field *)item)->max_disp_length();
|
||||||
}
|
|
||||||
switch (item->result_type())
|
switch (item->result_type())
|
||||||
{
|
{
|
||||||
case STRING_RESULT:
|
case STRING_RESULT:
|
||||||
@ -3452,6 +3660,14 @@ String *Item_type_holder::val_str(String*)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Item_result_field::cleanup()
|
||||||
|
{
|
||||||
|
DBUG_ENTER("Item_result_field::cleanup()");
|
||||||
|
Item::cleanup();
|
||||||
|
result_field= 0;
|
||||||
|
DBUG_VOID_RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
** Instantiate templates
|
** Instantiate templates
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
@ -3460,5 +3676,6 @@ String *Item_type_holder::val_str(String*)
|
|||||||
template class List<Item>;
|
template class List<Item>;
|
||||||
template class List_iterator<Item>;
|
template class List_iterator<Item>;
|
||||||
template class List_iterator_fast<Item>;
|
template class List_iterator_fast<Item>;
|
||||||
|
template class List_iterator_fast<Item_field>;
|
||||||
template class List<List_item>;
|
template class List<List_item>;
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user