1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-01 03:47:19 +03:00

Merge bk-internal.mysql.com:/home/bk/mysql-5.1

into  vajra.(none):/opt/local/work/mysql-5.1-runtime


mysql-test/include/mix1.inc:
  Auto merged
mysql-test/r/innodb_mysql.result:
  Auto merged
mysql-test/r/ps_1general.result:
  Auto merged
mysql-test/t/disabled.def:
  Auto merged
mysql-test/t/ps_1general.test:
  Auto merged
sql/ha_ndbcluster.cc:
  Auto merged
sql/ha_ndbcluster_binlog.cc:
  Auto merged
sql/item.cc:
  Auto merged
sql/item_func.cc:
  Auto merged
sql/mysql_priv.h:
  Auto merged
sql/set_var.cc:
  Auto merged
sql/sql_base.cc:
  Auto merged
sql/sql_cache.cc:
  Auto merged
sql/sql_class.cc:
  Auto merged
sql/sql_class.h:
  Auto merged
sql/sql_lex.cc:
  Auto merged
sql/sql_lex.h:
  Auto merged
sql/sql_parse.cc:
  Auto merged
sql/sql_partition.cc:
  Auto merged
sql/sql_prepare.cc:
  Auto merged
sql/sql_table.cc:
  Auto merged
sql/sql_yacc.yy:
  Auto merged
sql/table.h:
  Auto merged
sql/sql_insert.cc:
  SCCS merged
This commit is contained in:
unknown
2007-05-15 17:54:11 +04:00
47 changed files with 2635 additions and 699 deletions

View File

@ -29,7 +29,7 @@
waited for to open and lock the table.
If accessing the thread succeeded, in
delayed_insert::get_local_table() the table of the thread is copied
Delayed_insert::get_local_table() the table of the thread is copied
for local use. A copy is required because the normal insert logic
works on a target table, but the other threads table object must not
be used. The insert logic uses the record buffer to create a record.
@ -384,6 +384,88 @@ void prepare_triggers_for_insert_stmt(TABLE *table)
}
/**
Upgrade table-level lock of INSERT statement to TL_WRITE if
a more concurrent lock is infeasible for some reason. This is
necessary for engines without internal locking support (MyISAM).
An engine with internal locking implementation might later
downgrade the lock in handler::store_lock() method.
*/
void upgrade_lock_type(THD *thd, thr_lock_type *lock_type,
enum_duplicates duplic,
bool is_multi_insert)
{
if (duplic == DUP_UPDATE ||
duplic == DUP_REPLACE && *lock_type == TL_WRITE_CONCURRENT_INSERT)
{
*lock_type= TL_WRITE;
return;
}
if (*lock_type == TL_WRITE_DELAYED)
{
#ifdef EMBEDDED_LIBRARY
/* No auxiliary threads in the embedded server. */
*lock_type= TL_WRITE;
return;
#else
/*
We do not use delayed threads if:
- we're running in the safe mode or skip-new - the feature
is disabled in these modes
- we're running this query in statement level replication,
on a replication slave - because we must ensure serial
execution of queries on the slave
- it is INSERT .. ON DUPLICATE KEY UPDATE - in this case the
insert cannot be concurrent
*/
if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) ||
thd->slave_thread ||
thd->variables.max_insert_delayed_threads == 0)
{
*lock_type= TL_WRITE;
return;
}
#endif
bool log_on= (thd->options & OPTION_BIN_LOG ||
! (thd->security_ctx->master_access & SUPER_ACL));
if (global_system_variables.binlog_format == BINLOG_FORMAT_STMT &&
log_on && mysql_bin_log.is_open() && is_multi_insert)
{
/*
Statement-based binary logging does not work in this case, because:
a) two concurrent statements may have their rows intermixed in the
queue, leading to autoincrement replication problems on slave (because
the values generated used for one statement don't depend only on the
value generated for the first row of this statement, so are not
replicable)
b) if first row of the statement has an error the full statement is
not binlogged, while next rows of the statement may be inserted.
c) if first row succeeds, statement is binlogged immediately with a
zero error code (i.e. "no error"), if then second row fails, query
will fail on slave too and slave will stop (wrongly believing that the
master got no error).
So we fallback to non-delayed INSERT.
Note that to be fully correct, we should test the "binlog format which
the delayed thread is going to use for this row". But in the common case
where the global binlog format is not changed and the session binlog
format may be changed, that is equal to the global binlog format.
We test it without mutex for speed reasons (condition rarely true), and
in the common case (global not changed) it is as good as without mutex;
if global value is changed, anyway there is uncertainty as the delayed
thread may be old and use the before-the-change value.
*/
*lock_type= TL_WRITE;
}
}
}
/**
INSERT statement implementation
*/
bool mysql_insert(THD *thd,TABLE_LIST *table_list,
List<Item> &fields,
List<List_item> &values_list,
@ -406,7 +488,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
Name_resolution_context_state ctx_state;
#ifndef EMBEDDED_LIBRARY
char *query= thd->query;
#endif
/*
log_on is about delayed inserts only.
By default, both logs are enabled (this won't cause problems if the server
@ -414,72 +495,37 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
*/
bool log_on= ((thd->options & OPTION_BIN_LOG) ||
(!(thd->security_ctx->master_access & SUPER_ACL)));
#endif
thr_lock_type lock_type = table_list->lock_type;
Item *unused_conds= 0;
DBUG_ENTER("mysql_insert");
/*
in safe mode or with skip-new change delayed insert to be regular
if we are told to replace duplicates, the insert cannot be concurrent
delayed insert changed to regular in slave thread
*/
#ifdef EMBEDDED_LIBRARY
if (lock_type == TL_WRITE_DELAYED)
lock_type=TL_WRITE;
#else
if ((lock_type == TL_WRITE_DELAYED &&
((specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE)) ||
thd->slave_thread || !thd->variables.max_insert_delayed_threads)) ||
(lock_type == TL_WRITE_CONCURRENT_INSERT && duplic == DUP_REPLACE) ||
(duplic == DUP_UPDATE))
lock_type=TL_WRITE;
#endif
if ((lock_type == TL_WRITE_DELAYED) &&
(global_system_variables.binlog_format == BINLOG_FORMAT_STMT) &&
log_on && mysql_bin_log.is_open() &&
(values_list.elements > 1))
Upgrade lock type if the requested lock is incompatible with
the current connection mode or table operation.
*/
upgrade_lock_type(thd, &table_list->lock_type, duplic,
values_list.elements > 1);
lock_type= table_list->lock_type;
/*
We can't write-delayed into a table locked with LOCK TABLES:
this will lead to a deadlock, since the delayed thread will
never be able to get a lock on the table. QQQ: why not
upgrade the lock here instead?
*/
if (lock_type == TL_WRITE_DELAYED && thd->locked_tables &&
find_locked_table(thd, table_list->db, table_list->table_name))
{
/*
Statement-based binary logging does not work in this case, because:
a) two concurrent statements may have their rows intermixed in the
queue, leading to autoincrement replication problems on slave (because
the values generated used for one statement don't depend only on the
value generated for the first row of this statement, so are not
replicable)
b) if first row of the statement has an error the full statement is
not binlogged, while next rows of the statement may be inserted.
c) if first row succeeds, statement is binlogged immediately with a
zero error code (i.e. "no error"), if then second row fails, query
will fail on slave too and slave will stop (wrongly believing that the
master got no error).
So we fallback to non-delayed INSERT.
Note that to be fully correct, we should test the "binlog format which
the delayed thread is going to use for this row". But in the common case
where the global binlog format is not changed and the session binlog
format may be changed, that is equal to the global binlog format.
We test it without mutex for speed reasons (condition rarely true), and
in the common case (global not changed) it is as good as without mutex;
if global value is changed, anyway there is uncertainty as the delayed
thread may be old and use the before-the-change value.
*/
lock_type= TL_WRITE;
my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
table_list->table_name);
DBUG_RETURN(TRUE);
}
table_list->lock_type= lock_type;
#ifndef EMBEDDED_LIBRARY
if (lock_type == TL_WRITE_DELAYED)
{
res= 1;
if (thd->locked_tables)
{
DBUG_ASSERT(table_list->db); /* Must be set in the parser */
if (find_locked_table(thd, table_list->db, table_list->table_name))
{
my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
table_list->table_name);
DBUG_RETURN(TRUE);
}
}
if ((table= delayed_get_table(thd,table_list)) && !thd->is_fatal_error)
{
/*
@ -1503,8 +1549,14 @@ public:
}
};
/**
Delayed_insert - context of a thread responsible for delayed insert
into one table. When processing delayed inserts, we create an own
thread for every distinct table. Later on all delayed inserts directed
into that table are handled by a dedicated thread.
*/
class delayed_insert :public ilink {
class Delayed_insert :public ilink {
uint locks_in_memory;
public:
THD thd;
@ -1518,7 +1570,7 @@ public:
ulong group_count;
TABLE_LIST table_list; // Argument
delayed_insert()
Delayed_insert()
:locks_in_memory(0),
table(0),tables_in_use(0),stacked_inserts(0), status(0), dead(0),
group_count(0)
@ -1548,7 +1600,7 @@ public:
delayed_insert_threads++;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
}
~delayed_insert()
~Delayed_insert()
{
/* The following is not really needed, but just for safety */
delayed_row *row;
@ -1596,15 +1648,21 @@ public:
};
I_List<delayed_insert> delayed_threads;
I_List<Delayed_insert> delayed_threads;
delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
/**
Return an instance of delayed insert thread that can handle
inserts into a given table, if it exists. Otherwise return NULL.
*/
static
Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
{
thd->proc_info="waiting for delay_list";
pthread_mutex_lock(&LOCK_delayed_insert); // Protect master list
I_List_iterator<delayed_insert> it(delayed_threads);
delayed_insert *tmp;
I_List_iterator<Delayed_insert> it(delayed_threads);
Delayed_insert *tmp;
while ((tmp=it++))
{
if (!strcmp(tmp->thd.db, table_list->db) &&
@ -1619,10 +1677,22 @@ delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
}
/**
Attempt to find or create a delayed insert thread to handle inserts
into this table.
@return Return a local copy of the table in the delayed thread
@retval NULL too many delayed threads OR
this thread ran out of resources OR
a newly created delayed insert thread ran out of resources OR
the delayed insert thread failed to open the table.
In the last three cases an error is set in THD.
*/
static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
{
int error;
delayed_insert *tmp;
Delayed_insert *tmp;
TABLE *table;
DBUG_ENTER("delayed_get_table");
@ -1646,9 +1716,9 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
*/
if (! (tmp= find_handler(thd, table_list)))
{
if (!(tmp=new delayed_insert()))
if (!(tmp=new Delayed_insert()))
{
my_error(ER_OUTOFMEMORY,MYF(0),sizeof(delayed_insert));
my_error(ER_OUTOFMEMORY,MYF(0),sizeof(Delayed_insert));
goto err1;
}
pthread_mutex_lock(&LOCK_thread_count);
@ -1727,21 +1797,27 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
}
/*
As we can't let many threads modify the same TABLE structure, we create
an own structure for each tread. This includes a row buffer to save the
column values and new fields that points to the new row buffer.
The memory is allocated in the client thread and is freed automaticly.
/**
As we can't let many client threads modify the same TABLE
structure of the dedicated delayed insert thread, we create an
own structure for each client thread. This includes a row
buffer to save the column values and new fields that point to
the new row buffer. The memory is allocated in the client
thread and is freed automatically.
@pre This function is called from the client thread. Delayed
insert thread mutex must be acquired before invoking this
function.
*/
TABLE *delayed_insert::get_local_table(THD* client_thd)
TABLE *Delayed_insert::get_local_table(THD* client_thd)
{
my_ptrdiff_t adjust_ptrs;
Field **field,**org_field, *found_next_number_field;
TABLE *copy;
TABLE_SHARE *share= table->s;
byte *bitmap;
DBUG_ENTER("delayed_insert::get_local_table");
DBUG_ENTER("Delayed_insert::get_local_table");
/* First request insert thread to get a lock */
status=1;
@ -1851,7 +1927,7 @@ write_delayed(THD *thd,TABLE *table, enum_duplicates duplic,
LEX_STRING query, bool ignore, bool log_on)
{
delayed_row *row= 0;
delayed_insert *di=thd->di;
Delayed_insert *di=thd->di;
const Discrete_interval *forced_auto_inc;
DBUG_ENTER("write_delayed");
DBUG_PRINT("enter", ("query = '%s' length %u", query.str, query.length));
@ -1934,7 +2010,7 @@ write_delayed(THD *thd,TABLE *table, enum_duplicates duplic,
static void end_delayed_insert(THD *thd)
{
DBUG_ENTER("end_delayed_insert");
delayed_insert *di=thd->di;
Delayed_insert *di=thd->di;
pthread_mutex_lock(&di->mutex);
DBUG_PRINT("info",("tables in use: %d",di->tables_in_use));
if (!--di->tables_in_use || di->thd.killed)
@ -1953,8 +2029,8 @@ void kill_delayed_threads(void)
{
VOID(pthread_mutex_lock(&LOCK_delayed_insert)); // For unlink from list
I_List_iterator<delayed_insert> it(delayed_threads);
delayed_insert *tmp;
I_List_iterator<Delayed_insert> it(delayed_threads);
Delayed_insert *tmp;
while ((tmp=it++))
{
tmp->thd.killed= THD::KILL_CONNECTION;
@ -1986,7 +2062,7 @@ void kill_delayed_threads(void)
pthread_handler_t handle_delayed_insert(void *arg)
{
delayed_insert *di=(delayed_insert*) arg;
Delayed_insert *di=(Delayed_insert*) arg;
THD *thd= &di->thd;
pthread_detach_this_thread();
@ -2237,7 +2313,7 @@ static void free_delayed_insert_blobs(register TABLE *table)
}
bool delayed_insert::handle_inserts(void)
bool Delayed_insert::handle_inserts(void)
{
int error;
ulong max_rows;
@ -2262,7 +2338,7 @@ bool delayed_insert::handle_inserts(void)
thd.proc_info="insert";
max_rows= delayed_insert_limit;
if (thd.killed || table->s->version != refresh_version)
if (thd.killed || table->needs_reopen_or_name_lock())
{
thd.killed= THD::KILL_CONNECTION;
max_rows= ULONG_MAX; // Do as much as possible
@ -2963,8 +3039,8 @@ bool select_insert::send_eof()
***************************************************************************/
/*
Create table from lists of fields and items (or open existing table
with same name).
Create table from lists of fields and items (or just return TABLE
object for pre-opened existing table).
SYNOPSIS
create_table_from_items()
@ -2979,19 +3055,25 @@ bool select_insert::send_eof()
of fields for the table (corresponding fields will
be added to the end of 'extra_fields' list)
lock out Pointer to the MYSQL_LOCK object for table created
(open) will be returned in this parameter. Since
this table is not included in THD::lock caller is
responsible for explicitly unlocking this table.
(or open temporary table) will be returned in this
parameter. Since this table is not included in
THD::lock caller is responsible for explicitly
unlocking this table.
hooks
NOTES
If 'create_info->options' bitmask has HA_LEX_CREATE_IF_NOT_EXISTS
flag and table with name provided already exists then this function will
simply open existing table.
Also note that create, open and lock sequence in this function is not
atomic and thus contains gap for deadlock and can cause other troubles.
Since this function contains some logic specific to CREATE TABLE ... SELECT
it should be changed before it can be used in other contexts.
This function behaves differently for base and temporary tables:
- For base table we assume that either table exists and was pre-opened
and locked at open_and_lock_tables() stage (and in this case we just
emit error or warning and return pre-opened TABLE object) or special
placeholder was put in table cache that guarantees that this table
won't be created or opened until the placeholder will be removed
(so there is an exclusive lock on this table).
- We don't pre-open existing temporary table, instead we either open
or create and then open table in this function.
Since this function contains some logic specific to CREATE TABLE ...
SELECT it should be changed before it can be used in other contexts.
RETURN VALUES
non-zero Pointer to TABLE object for table created or opened
@ -3017,6 +3099,25 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
bool not_used;
DBUG_ENTER("create_table_from_items");
DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000););
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
create_table->table->db_stat)
{
/* Table already exists and was open at open_and_lock_tables() stage. */
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
create_info->table_existed= 1; // Mark that table existed
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
create_table->table_name);
DBUG_RETURN(create_table->table);
}
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name);
DBUG_RETURN(0);
}
tmp_table.alias= 0;
tmp_table.timestamp_field= 0;
tmp_table.s= &share;
@ -3048,8 +3149,15 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
cr_field->flags &= ~NOT_NULL_FLAG;
extra_fields->push_back(cr_field);
}
DBUG_EXECUTE_IF("sleep_create_select_before_create", my_sleep(6000000););
/*
create and lock table
Create and lock table.
Note that we either creating (or opening existing) temporary table or
creating base table on which name we have exclusive lock. So code below
should not cause deadlocks or races.
We don't log the statement, it will be logged later.
@ -3059,63 +3167,74 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
don't want to delete from it) 2) it would be written before the CREATE
TABLE, which is a wrong order. So we keep binary logging disabled when we
open_table().
NOTE: By locking table which we just have created (or for which we just
have have found that it already exists) separately from other tables used
by the statement we create potential window for deadlock.
TODO: create and open should be done atomic !
*/
{
tmp_disable_binlog(thd);
if (!mysql_create_table(thd, create_table->db, create_table->table_name,
create_info, *extra_fields, *keys, 0,
select_field_count, 0))
if (!mysql_create_table_no_lock(thd, create_table->db,
create_table->table_name,
create_info, *extra_fields, *keys, 0,
select_field_count, 0))
{
/*
If we are here in prelocked mode we either create temporary table
or prelocked mode is caused by the SELECT part of this statement.
*/
DBUG_ASSERT(!thd->prelocked_mode ||
create_info->options & HA_LEX_CREATE_TMP_TABLE ||
thd->lex->requires_prelocking());
/*
NOTE: We don't want to ignore set of locked tables here if we are
under explicit LOCK TABLES since it will open gap for deadlock
too wide (and also is not backward compatible).
*/
if (create_info->table_existed &&
!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
/*
This means that someone created table underneath server
or it was created via different mysqld front-end to the
cluster. We don't have much options but throw an error.
*/
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name);
DBUG_RETURN(0);
}
DBUG_EXECUTE_IF("sleep_create_select_before_open", my_sleep(6000000););
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
VOID(pthread_mutex_lock(&LOCK_open));
if (reopen_name_locked_table(thd, create_table, FALSE))
{
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name),
0);
}
else
table= create_table->table;
VOID(pthread_mutex_unlock(&LOCK_open));
}
else
{
if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
MYSQL_OPEN_TEMPORARY_ONLY)) &&
!create_info->table_existed)
{
/*
This shouldn't happen as creation of temporary table should make
it preparable for open. But let us do close_temporary_table() here
just in case.
*/
close_temporary_table(thd, create_table);
}
}
if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
(MYSQL_LOCK_IGNORE_FLUSH |
((thd->prelocked_mode == PRELOCKED) ?
MYSQL_OPEN_IGNORE_LOCKED_TABLES:0)))))
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name),
0);
}
reenable_binlog(thd);
if (!table) // open failed
DBUG_RETURN(0);
}
/*
FIXME: What happens if trigger manages to be created while we are
obtaining this lock ? May be it is sensible just to disable
trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH
save us from that ?
*/
DBUG_EXECUTE_IF("sleep_create_select_before_lock", my_sleep(6000000););
table->reginfo.lock_type=TL_WRITE;
hooks->prelock(&table, 1); // Call prelock hooks
if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
MYSQL_LOCK_IGNORE_FLUSH, &not_used)))
{
VOID(pthread_mutex_lock(&LOCK_open));
hash_delete(&open_cache,(byte*) table);
VOID(pthread_mutex_unlock(&LOCK_open));
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name), 0);
if (!create_info->table_existed)
drop_open_table(thd, table, create_table->db, create_table->table_name);
DBUG_RETURN(0);
}
table->file->extra(HA_EXTRA_WRITE_CACHE);
DBUG_RETURN(table);
}
@ -3218,6 +3337,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
if (check_that_all_fields_are_given_values(thd, table, table_list))
DBUG_RETURN(1);
table->mark_columns_needed_for_insert();
table->file->extra(HA_EXTRA_WRITE_CACHE);
DBUG_RETURN(0);
}
@ -3320,24 +3440,19 @@ bool select_create::send_eof()
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
VOID(pthread_mutex_lock(&LOCK_open));
mysql_unlock_tables(thd, thd->extra_lock);
if (!table->s->tmp_table)
if (thd->extra_lock)
{
if (close_thread_table(thd, &table))
broadcast_refresh();
mysql_unlock_tables(thd, thd->extra_lock);
thd->extra_lock=0;
}
thd->extra_lock=0;
table=0;
VOID(pthread_mutex_unlock(&LOCK_open));
}
return tmp;
}
void select_create::abort()
{
DBUG_ENTER("select_create::abort");
VOID(pthread_mutex_lock(&LOCK_open));
/*
We roll back the statement, including truncating the transaction
@ -3365,24 +3480,10 @@ void select_create::abort()
{
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
handlerton *table_type=table->s->db_type();
if (!table->s->tmp_table)
{
ulong version= table->s->version;
table->s->version= 0;
hash_delete(&open_cache,(byte*) table);
if (!create_info->table_existed)
quick_rm_table(table_type, create_table->db,
create_table->table_name, 0);
/* Tell threads waiting for refresh that something has happened */
if (version != refresh_version)
broadcast_refresh();
}
else if (!create_info->table_existed)
close_temporary_table(thd, table, 1, 1);
if (!create_info->table_existed)
drop_open_table(thd, table, create_table->db, create_table->table_name);
table=0; // Safety
}
VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_VOID_RETURN;
}
@ -3394,8 +3495,8 @@ void select_create::abort()
#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION
template class List_iterator_fast<List_item>;
#ifndef EMBEDDED_LIBRARY
template class I_List<delayed_insert>;
template class I_List_iterator<delayed_insert>;
template class I_List<Delayed_insert>;
template class I_List_iterator<Delayed_insert>;
template class I_List<delayed_row>;
#endif /* EMBEDDED_LIBRARY */
#endif /* HAVE_EXPLICIT_TEMPLATE_INSTANTIATION */