mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
90104181f9
Fixes for RENAME TABLE Portability fixes Docs/manual.texi: FLUSH TABLES table_list client/mysql.cc: Ia64 port client/mysqladmin.c: Ia64 port client/mysqldump.c: Ia64 port client/mysqlimport.c: Ia64 port client/mysqlshow.c: Ia64 port client/readline.cc: Ia64 port configure.in: Ia64 port include/config-win.h: Ia64 port include/thr_alarm.h: Ia64 port myisam/myisamdef.h: Ia64 port myisammrg/myrg_rkey.c: Ia64 port mysys/thr_alarm.c: Easier compile of non threaded client mysys/thr_lock.c: Easier compile of non threaded client mysys/thr_mutex.c: Easier compile of non threaded client sql-bench/crash-me.sh: Added test for RENAME + some ANSI SQL99 group functions sql-bench/limits/mysql.cfg: New run sql-bench/server-cfg.sh: Fix sql-bench/test-insert.sh: Fix sql/field.cc: Port to Ia64 sql/ha_berkeley.cc: Port to Ia64 sql/ha_myisam.cc: cleanup sql/handler.cc: Port to Ia64 sql/hostname.cc: Port to Ia64 sql/item.cc: Port to Ia64 sql/item_create.cc: Port to Ia64 sql/item_func.cc: Port to Ia64 sql/item_strfunc.cc: Port to Ia64 sql/item_timefunc.cc: Port to Ia64 sql/lock.cc: Fix for RENAME sql/log.cc: Port to Ia64 sql/log_event.h: Port to Ia64 sql/mini_client.cc: Port to Ia64 sql/mysql_priv.h: Port to Ia64 sql/mysqlbinlog.cc: Port to Ia64 sql/mysqld.cc: FLUSH TABLES table_list sql/net_pkg.cc: Port to Ia64 sql/procedure.h: Port to Ia64 sql/share/swedish/errmsg.OLD: FLUSH TABLES table_list sql/slave.cc: Port to Ia64 sql/sql_acl.cc: Port to Ia64 sql/sql_analyse.cc: Port to Ia64 sql/sql_analyse.h: Port to Ia64 sql/sql_base.cc: FLUSH TABLES table_list sql/sql_class.cc: Port to Ia64 sql/sql_class.h: Port to Ia64 sql/sql_parse.cc: Port to Ia64 sql/sql_rename.cc: Fix of RENAME sql/sql_show.cc: Port to Ia64 sql/sql_string.cc: Port to Ia64 sql/sql_string.h: Port to Ia64 sql/sql_table.cc: Fix of RENAME sql/sql_udf.cc: Port to Ia64 sql/sql_yacc.yy: FLUSH TABLES table_list sql/table.cc: Port to Ia64 sql/thr_malloc.cc: Port to Ia64 sql/time.cc: Port to Ia64 sql/unireg.cc: Port to Ia64
681 lines
15 KiB
C++
681 lines
15 KiB
C++
/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
|
|
#ifdef __GNUC__
|
|
#pragma implementation // gcc: Class implementation
|
|
#endif
|
|
|
|
#include "mysql_priv.h"
|
|
#include <m_ctype.h>
|
|
#include "my_dir.h"
|
|
|
|
/*****************************************************************************
|
|
** Item functions
|
|
*****************************************************************************/
|
|
|
|
/* Init all special items */
|
|
|
|
void item_init(void)
|
|
{
|
|
item_user_lock_init();
|
|
}
|
|
|
|
Item::Item()
|
|
{
|
|
marker=0;
|
|
binary=maybe_null=null_value=with_sum_func=0;
|
|
name=0;
|
|
decimals=0; max_length=0;
|
|
next=current_thd->free_list; // Put in free list
|
|
current_thd->free_list=this;
|
|
}
|
|
|
|
void Item::set_name(char *str,uint length)
|
|
{
|
|
if (!length)
|
|
name=str; // Used by AS
|
|
else
|
|
{
|
|
while (length && !isgraph(*str))
|
|
{ // Fix problem with yacc
|
|
length--;
|
|
str++;
|
|
}
|
|
name=sql_strmake(str,min(length,MAX_FIELD_WIDTH));
|
|
}
|
|
}
|
|
|
|
bool Item::eq(const Item *item) const // Only doing this on conds
|
|
{
|
|
return type() == item->type() && name && item->name &&
|
|
!my_strcasecmp(name,item->name);
|
|
}
|
|
|
|
/*
|
|
Get the value of the function as a TIME structure.
|
|
As a extra convenience the time structure is reset on error!
|
|
*/
|
|
|
|
bool Item::get_date(TIME *ltime,bool fuzzydate)
|
|
{
|
|
char buff[40];
|
|
String tmp(buff,sizeof(buff)),*res;
|
|
if (!(res=val_str(&tmp)) ||
|
|
str_to_TIME(res->ptr(),res->length(),ltime,0) == TIMESTAMP_NONE)
|
|
{
|
|
bzero((char*) ltime,sizeof(*ltime));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Get time of first argument.
|
|
As a extra convenience the time structure is reset on error!
|
|
*/
|
|
|
|
bool Item::get_time(TIME *ltime)
|
|
{
|
|
char buff[40];
|
|
String tmp(buff,sizeof(buff)),*res;
|
|
if (!(res=val_str(&tmp)) ||
|
|
str_to_time(res->ptr(),res->length(),ltime))
|
|
{
|
|
bzero((char*) ltime,sizeof(*ltime));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Item_field::Item_field(Field *f) :Item_ident(NullS,f->table_name,f->field_name)
|
|
{
|
|
set_field(f);
|
|
}
|
|
|
|
|
|
void Item_field::set_field(Field *field_par)
|
|
{
|
|
field=result_field=field_par; // for easy coding with fields
|
|
maybe_null=field->maybe_null();
|
|
max_length=field_par->field_length;
|
|
decimals= field->decimals();
|
|
table_name=field_par->table_name;
|
|
field_name=field_par->field_name;
|
|
binary=field_par->binary();
|
|
}
|
|
|
|
const char *Item_ident::full_name() const
|
|
{
|
|
char *tmp;
|
|
if (!table_name)
|
|
return field_name ? field_name : name ? name : "tmp_field";
|
|
if (db_name)
|
|
{
|
|
tmp=(char*) sql_alloc((uint) strlen(db_name)+(uint) strlen(table_name)+
|
|
(uint) strlen(field_name)+3);
|
|
strxmov(tmp,db_name,".",table_name,".",field_name,NullS);
|
|
}
|
|
else
|
|
{
|
|
tmp=(char*) sql_alloc((uint) strlen(table_name)+
|
|
(uint) strlen(field_name)+2);
|
|
strxmov(tmp,table_name,".",field_name,NullS);
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
String *Item_field::val_str(String *str)
|
|
{
|
|
if ((null_value=field->is_null()))
|
|
return 0;
|
|
return field->val_str(str,&str_value);
|
|
}
|
|
|
|
double Item_field::val()
|
|
{
|
|
if ((null_value=field->is_null()))
|
|
return 0.0;
|
|
return field->val_real();
|
|
}
|
|
|
|
longlong Item_field::val_int()
|
|
{
|
|
if ((null_value=field->is_null()))
|
|
return 0;
|
|
return field->val_int();
|
|
}
|
|
|
|
|
|
String *Item_field::str_result(String *str)
|
|
{
|
|
if ((null_value=result_field->is_null()))
|
|
return 0;
|
|
return result_field->val_str(str,&str_value);
|
|
}
|
|
|
|
bool Item_field::get_date(TIME *ltime,bool fuzzydate)
|
|
{
|
|
if ((null_value=field->is_null()) || field->get_date(ltime,fuzzydate))
|
|
{
|
|
bzero((char*) ltime,sizeof(*ltime));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Item_field::get_time(TIME *ltime)
|
|
{
|
|
if ((null_value=field->is_null()) || field->get_time(ltime))
|
|
{
|
|
bzero((char*) ltime,sizeof(*ltime));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
double Item_field::val_result()
|
|
{
|
|
if ((null_value=result_field->is_null()))
|
|
return 0.0;
|
|
return result_field->val_real();
|
|
}
|
|
|
|
longlong Item_field::val_int_result()
|
|
{
|
|
if ((null_value=result_field->is_null()))
|
|
return 0;
|
|
return result_field->val_int();
|
|
}
|
|
|
|
bool Item_field::eq(const Item *item) const
|
|
{
|
|
return item->type() == FIELD_ITEM && ((Item_field*) item)->field == field;
|
|
}
|
|
|
|
table_map Item_field::used_tables() const
|
|
{
|
|
if (field->table->const_table)
|
|
return 0; // const item
|
|
return field->table->map;
|
|
}
|
|
|
|
|
|
String *Item_int::val_str(String *str)
|
|
{
|
|
str->set(value);
|
|
return str;
|
|
}
|
|
|
|
void Item_int::print(String *str)
|
|
{
|
|
if (!name)
|
|
{
|
|
str_value.set(value);
|
|
name=str_value.c_ptr();
|
|
}
|
|
str->append(name);
|
|
}
|
|
|
|
|
|
String *Item_real::val_str(String *str)
|
|
{
|
|
str->set(value,decimals);
|
|
return str;
|
|
}
|
|
|
|
void Item_string::print(String *str)
|
|
{
|
|
str->append('\'');
|
|
str->append(full_name());
|
|
str->append('\'');
|
|
}
|
|
|
|
bool Item_null::eq(const Item *item) const { return item->type() == type(); }
|
|
double Item_null::val() { null_value=1; return 0.0; }
|
|
longlong Item_null::val_int() { null_value=1; return 0; }
|
|
/* ARGSUSED */
|
|
String *Item_null::val_str(String *str)
|
|
{ null_value=1; return 0;}
|
|
|
|
|
|
void Item_copy_string::copy()
|
|
{
|
|
String *res=item->val_str(&str_value);
|
|
if (res && res != &str_value)
|
|
str_value.copy(*res);
|
|
null_value=item->null_value;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
String *Item_copy_string::val_str(String *str)
|
|
{
|
|
if (null_value)
|
|
return (String*) 0;
|
|
return &str_value;
|
|
}
|
|
|
|
/*
|
|
** Functions to convert item to field (for send_fields)
|
|
*/
|
|
|
|
/* ARGSUSED */
|
|
bool Item::fix_fields(THD *thd,
|
|
struct st_table_list *list)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool Item_field::fix_fields(THD *thd,TABLE_LIST *tables)
|
|
{
|
|
if (!field)
|
|
{
|
|
Field *tmp;
|
|
if (!(tmp=find_field_in_tables(thd,this,tables)))
|
|
return 1;
|
|
set_field(tmp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item::init_make_field(Send_field *tmp_field,
|
|
enum enum_field_types field_type)
|
|
{
|
|
tmp_field->table_name=(char*) "";
|
|
tmp_field->col_name=name;
|
|
tmp_field->flags=maybe_null ? 0 : NOT_NULL_FLAG;
|
|
tmp_field->type=field_type;
|
|
tmp_field->length=max_length;
|
|
tmp_field->decimals=decimals;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
void Item_field::make_field(Send_field *tmp_field)
|
|
{
|
|
field->make_field(tmp_field);
|
|
if (name)
|
|
tmp_field->col_name=name; // Use user supplied name
|
|
}
|
|
|
|
void Item_int::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_LONGLONG);
|
|
}
|
|
|
|
void Item_real::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_DOUBLE);
|
|
}
|
|
|
|
void Item_string::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_STRING);
|
|
}
|
|
|
|
void Item_datetime::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_DATETIME);
|
|
}
|
|
|
|
void Item_null::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_NULL);
|
|
tmp_field->length=4;
|
|
}
|
|
|
|
void Item_func::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field, ((result_type() == STRING_RESULT) ?
|
|
FIELD_TYPE_VAR_STRING :
|
|
(result_type() == INT_RESULT) ?
|
|
FIELD_TYPE_LONGLONG : FIELD_TYPE_DOUBLE));
|
|
}
|
|
|
|
void Item_avg_field::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_DOUBLE);
|
|
}
|
|
|
|
void Item_std_field::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_DOUBLE);
|
|
}
|
|
|
|
/*
|
|
** Set a field:s value from a item
|
|
*/
|
|
|
|
|
|
void Item_field::save_org_in_field(Field *to)
|
|
{
|
|
if (field->is_null())
|
|
{
|
|
null_value=1;
|
|
set_field_to_null(to);
|
|
}
|
|
else
|
|
{
|
|
to->set_notnull();
|
|
field_conv(to,field);
|
|
null_value=0;
|
|
}
|
|
}
|
|
|
|
bool Item_field::save_in_field(Field *to)
|
|
{
|
|
if (result_field->is_null())
|
|
{
|
|
null_value=1;
|
|
return set_field_to_null(to);
|
|
}
|
|
else
|
|
{
|
|
to->set_notnull();
|
|
field_conv(to,result_field);
|
|
null_value=0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_null::save_in_field(Field *field)
|
|
{
|
|
return set_field_to_null(field);
|
|
}
|
|
|
|
|
|
bool Item::save_in_field(Field *field)
|
|
{
|
|
if (result_type() == STRING_RESULT ||
|
|
result_type() == REAL_RESULT &&
|
|
field->result_type() == STRING_RESULT)
|
|
{
|
|
String *result;
|
|
char buff[MAX_FIELD_WIDTH]; // Alloc buffer for small columns
|
|
str_value.set_quick(buff,sizeof(buff));
|
|
result=val_str(&str_value);
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(result->ptr(),result->length());
|
|
str_value.set_quick(0, 0);
|
|
}
|
|
else if (result_type() == REAL_RESULT)
|
|
{
|
|
double nr=val();
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(nr);
|
|
}
|
|
else
|
|
{
|
|
longlong nr=val_int();
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(nr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Item_string::save_in_field(Field *field)
|
|
{
|
|
String *result;
|
|
result=val_str(&str_value);
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(result->ptr(),result->length());
|
|
return 0;
|
|
}
|
|
|
|
bool Item_int::save_in_field(Field *field)
|
|
{
|
|
longlong nr=val_int();
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(nr);
|
|
return 0;
|
|
}
|
|
|
|
bool Item_real::save_in_field(Field *field)
|
|
{
|
|
double nr=val();
|
|
if (null_value)
|
|
return set_field_to_null(field);
|
|
field->set_notnull();
|
|
field->store(nr);
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
** varbinary item
|
|
** In string context this is a binary string
|
|
** In number context this is a longlong value.
|
|
****************************************************************************/
|
|
|
|
static inline uint char_val(char X)
|
|
{
|
|
return (uint) (X >= '0' && X <= '9' ? X-'0' :
|
|
X >= 'A' && X <= 'Z' ? X-'A'+10 :
|
|
X-'a'+10);
|
|
}
|
|
|
|
Item_varbinary::Item_varbinary(const char *str, uint str_length)
|
|
{
|
|
name=(char*) str-2; // Lex makes this start with 0x
|
|
max_length=(str_length+1)/2;
|
|
char *ptr=(char*) sql_alloc(max_length+1);
|
|
if (!ptr)
|
|
return;
|
|
str_value.set(ptr,max_length);
|
|
char *end=ptr+max_length;
|
|
if (max_length*2 != str_length)
|
|
*ptr++=char_val(*str++); // Not even, assume 0 prefix
|
|
while (ptr != end)
|
|
{
|
|
*ptr++= (char) (char_val(str[0])*16+char_val(str[1]));
|
|
str+=2;
|
|
}
|
|
*ptr=0; // Keep purify happy
|
|
binary=1; // Binary is default
|
|
}
|
|
|
|
longlong Item_varbinary::val_int()
|
|
{
|
|
char *end=(char*) str_value.ptr()+str_value.length(),
|
|
*ptr=end-min(str_value.length(),sizeof(longlong));
|
|
|
|
ulonglong value=0;
|
|
for (; ptr != end ; ptr++)
|
|
value=(value << 8)+ (ulonglong) (uchar) *ptr;
|
|
return (longlong) value;
|
|
}
|
|
|
|
|
|
bool Item_varbinary::save_in_field(Field *field)
|
|
{
|
|
field->set_notnull();
|
|
if (field->result_type() == STRING_RESULT)
|
|
{
|
|
field->store(str_value.ptr(),str_value.length());
|
|
}
|
|
else
|
|
{
|
|
longlong nr=val_int();
|
|
field->store(nr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item_varbinary::make_field(Send_field *tmp_field)
|
|
{
|
|
init_make_field(tmp_field,FIELD_TYPE_STRING);
|
|
}
|
|
|
|
/*
|
|
** pack data in buffer for sending
|
|
*/
|
|
|
|
bool Item::send(String *packet)
|
|
{
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String s(buff,sizeof(buff)),*res;
|
|
if (!(res=val_str(&s)))
|
|
return net_store_null(packet);
|
|
CONVERT *convert;
|
|
if ((convert=current_thd->convert_set))
|
|
return convert->store(packet,res->ptr(),res->length());
|
|
return net_store_data(packet,res->ptr(),res->length());
|
|
}
|
|
|
|
bool Item_null::send(String *packet)
|
|
{
|
|
return net_store_null(packet);
|
|
}
|
|
|
|
/*
|
|
This is used for HAVING clause
|
|
Find field in select list having the same name
|
|
*/
|
|
|
|
bool Item_ref::fix_fields(THD *thd,TABLE_LIST *tables)
|
|
{
|
|
if (!ref)
|
|
{
|
|
if (!(ref=find_item_in_list(this,thd->lex.item_list)))
|
|
return 1;
|
|
max_length= (*ref)->max_length;
|
|
maybe_null= (*ref)->maybe_null;
|
|
decimals= (*ref)->decimals;
|
|
binary= (*ref)->binary;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** If item is a const function, calculate it and return a const item
|
|
** The original item is freed if not returned
|
|
*/
|
|
|
|
Item_result item_cmp_type(Item_result a,Item_result b)
|
|
{
|
|
if (a == STRING_RESULT && b == STRING_RESULT)
|
|
return STRING_RESULT;
|
|
else if (a == INT_RESULT && b == INT_RESULT)
|
|
return INT_RESULT;
|
|
else
|
|
return REAL_RESULT;
|
|
}
|
|
|
|
|
|
Item *resolve_const_item(Item *item,Item *comp_item)
|
|
{
|
|
if (item->basic_const_item())
|
|
return item; // Can't be better
|
|
Item_result res_type=item_cmp_type(comp_item->result_type(),
|
|
item->result_type());
|
|
char *name=item->name; // Alloced by sql_alloc
|
|
|
|
if (res_type == STRING_RESULT)
|
|
{
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String tmp(buff,sizeof(buff)),*result;
|
|
result=item->val_str(&tmp);
|
|
if (item->null_value)
|
|
{
|
|
#ifdef DELETE_ITEMS
|
|
delete item;
|
|
#endif
|
|
return new Item_null(name);
|
|
}
|
|
uint length=result->length();
|
|
char *tmp_str=sql_strmake(result->ptr(),length);
|
|
#ifdef DELETE_ITEMS
|
|
delete item;
|
|
#endif
|
|
return new Item_string(name,tmp_str,length);
|
|
}
|
|
if (res_type == INT_RESULT)
|
|
{
|
|
longlong result=item->val_int();
|
|
uint length=item->max_length;
|
|
bool null_value=item->null_value;
|
|
#ifdef DELETE_ITEMS
|
|
delete item;
|
|
#endif
|
|
return (null_value ? (Item*) new Item_null(name) :
|
|
(Item*) new Item_int(name,result,length));
|
|
}
|
|
else
|
|
{ // It must REAL_RESULT
|
|
double result=item->val();
|
|
uint length=item->max_length,decimals=item->decimals;
|
|
bool null_value=item->null_value;
|
|
#ifdef DELETE_ITEMS
|
|
delete item;
|
|
#endif
|
|
return (null_value ? (Item*) new Item_null(name) :
|
|
(Item*) new Item_real(name,result,decimals,length));
|
|
}
|
|
}
|
|
|
|
/*
|
|
Return true if the value stored in the field is equal to the const item
|
|
We need to use this on the range optimizer because in some cases
|
|
we can't store the value in the field without some precision/character loss.
|
|
*/
|
|
|
|
bool field_is_equal_to_item(Field *field,Item *item)
|
|
{
|
|
|
|
Item_result res_type=item_cmp_type(field->result_type(),
|
|
item->result_type());
|
|
if (res_type == STRING_RESULT)
|
|
{
|
|
char item_buff[MAX_FIELD_WIDTH];
|
|
char field_buff[MAX_FIELD_WIDTH];
|
|
String item_tmp(item_buff,sizeof(item_buff)),*item_result;
|
|
String field_tmp(field_buff,sizeof(field_buff));
|
|
item_result=item->val_str(&item_tmp);
|
|
if (item->null_value)
|
|
return 1; // This must be true
|
|
field->val_str(&field_tmp,&field_tmp);
|
|
return !stringcmp(&field_tmp,item_result);
|
|
}
|
|
if (res_type == INT_RESULT)
|
|
return 1; // Both where of type int
|
|
double result=item->val();
|
|
if (item->null_value)
|
|
return 1;
|
|
return result == field->val_real();
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
** Instantiate templates
|
|
*****************************************************************************/
|
|
|
|
#ifdef __GNUC__
|
|
template class List<Item>;
|
|
template class List_iterator<Item>;
|
|
template class List<List_item>;
|
|
#endif
|