in sql/sql_base.cc [2873:3478]
bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
{
reg1 TABLE *table;
const char *key;
uint key_length;
char *alias= table_list->alias;
uint flags= ot_ctx->get_flags();
MDL_ticket *mdl_ticket;
int error;
TABLE_SHARE *share;
my_hash_value_type hash_value;
ulonglong timer = my_timer_now();
DBUG_ENTER("open_table");
/*
The table must not be opened already. The table can be pre-opened for
some statements if it is a temporary table.
open_temporary_table() must be used to open temporary tables.
*/
DBUG_ASSERT(!table_list->table);
/* an open table operation needs a lot of the stack space */
if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
DBUG_RETURN(TRUE);
if (!(flags & MYSQL_OPEN_IGNORE_KILLED) && thd->killed)
DBUG_RETURN(TRUE);
/*
Check if we're trying to take a write lock in a read only transaction.
Note that we allow write locks on log tables as otherwise logging
to general/slow log would be disabled in read only transactions.
*/
if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
thd->tx_read_only &&
!(flags & (MYSQL_LOCK_LOG_TABLE | MYSQL_OPEN_HAS_MDL_LOCK)))
{
my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
DBUG_RETURN(true);
}
key_length= get_table_def_key(table_list, &key);
/*
If we're in pre-locked or LOCK TABLES mode, let's try to find the
requested table in the list of pre-opened and locked tables. If the
table is not there, return an error - we can't open not pre-opened
tables in pre-locked/LOCK TABLES mode.
TODO: move this block into a separate function.
*/
if (thd->locked_tables_mode &&
! (flags & MYSQL_OPEN_GET_NEW_TABLE))
{ // Using table locks
TABLE *best_table= 0;
int best_distance= INT_MIN;
for (table=thd->open_tables; table ; table=table->next)
{
if (table->s->table_cache_key.length == key_length &&
!memcmp(table->s->table_cache_key.str, key, key_length))
{
if (!my_strcasecmp(system_charset_info, table->alias, alias) &&
table->query_id != thd->query_id && /* skip tables already used */
(thd->locked_tables_mode == LTM_LOCK_TABLES ||
table->query_id == 0))
{
int distance= ((int) table->reginfo.lock_type -
(int) table_list->lock_type);
/*
Find a table that either has the exact lock type requested,
or has the best suitable lock. In case there is no locked
table that has an equal or higher lock than requested,
we us the closest matching lock to be able to produce an error
message about wrong lock mode on the table. The best_table
is changed if bd < 0 <= d or bd < d < 0 or 0 <= d < bd.
distance < 0 - No suitable lock found
distance > 0 - we have lock mode higher then we require
distance == 0 - we have lock mode exactly which we need
*/
if ((best_distance < 0 && distance > best_distance) ||
(distance >= 0 && distance < best_distance))
{
best_distance= distance;
best_table= table;
if (best_distance == 0)
{
/*
We have found a perfect match and can finish iterating
through open tables list. Check for table use conflict
between calling statement and SP/trigger is done in
lock_tables().
*/
break;
}
}
}
}
}
if (best_table)
{
table= best_table;
table->query_id= thd->query_id;
DBUG_PRINT("info",("Using locked table"));
goto reset;
}
/*
Is this table a view and not a base table?
(it is work around to allow to open view with locked tables,
real fix will be made after definition cache will be made)
Since opening of view which was not explicitly locked by LOCK
TABLES breaks metadata locking protocol (potentially can lead
to deadlocks) it should be disallowed.
*/
if (thd->mdl_context.is_lock_owner(MDL_key::TABLE,
table_list->db,
table_list->table_name,
MDL_SHARED))
{
char path[FN_REFLEN + 1];
enum legacy_db_type not_used;
build_table_filename(path, sizeof(path) - 1,
table_list->db, table_list->table_name, reg_ext, 0);
/*
Note that we can't be 100% sure that it is a view since it's
possible that we either simply have not found unused TABLE
instance in THD::open_tables list or were unable to open table
during prelocking process (in this case in theory we still
should hold shared metadata lock on it).
*/
if (dd_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW)
{
/*
If parent_l of the table_list is non null then a merge table
has this view as child table, which is not supported.
*/
if (table_list->parent_l)
{
my_error(ER_WRONG_MRG_TABLE, MYF(0));
DBUG_RETURN(true);
}
if (!tdc_open_view(thd, table_list, alias, key, key_length,
CHECK_METADATA_VERSION))
{
DBUG_ASSERT(table_list->view != 0);
DBUG_RETURN(FALSE); // VIEW
}
}
}
/*
No table in the locked tables list. In case of explicit LOCK TABLES
this can happen if a user did not include the table into the list.
In case of pre-locked mode locked tables list is generated automatically,
so we may only end up here if the table did not exist when
locked tables list was created.
*/
if (thd->locked_tables_mode == LTM_PRELOCKED)
my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
else
my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
DBUG_RETURN(TRUE);
}
/* Non pre-locked/LOCK TABLES mode. This is the normal use case. */
if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
{
/*
We are not under LOCK TABLES and going to acquire write-lock/
modify the base table. We need to acquire protection against
global read lock until end of this statement in order to have
this statement blocked by active FLUSH TABLES WITH READ LOCK.
We don't block acquire this protection under LOCK TABLES as
such protection already acquired at LOCK TABLES time and
not released until UNLOCK TABLES.
We don't block statements which modify only temporary tables
as these tables are not preserved by backup by any form of
backup which uses FLUSH TABLES WITH READ LOCK.
TODO: The fact that we sometimes acquire protection against
GRL only when we encounter table to be write-locked
slightly increases probability of deadlock.
This problem will be solved once Alik pushes his
temporary table refactoring patch and we can start
pre-acquiring metadata locks at the beggining of
open_tables() call.
*/
if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
! (flags & (MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |
MYSQL_OPEN_FORCE_SHARED_MDL |
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL |
MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) &&
! ot_ctx->has_protection_against_grl())
{
MDL_request protection_request;
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
if (thd->global_read_lock.can_acquire_protection())
DBUG_RETURN(TRUE);
protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
/*
Install error handler which if possible will convert deadlock error
into request to back-off and restart process of opening tables.
*/
thd->push_internal_handler(&mdl_deadlock_handler);
bool result= thd->mdl_context.acquire_lock_nsec(&protection_request,
ot_ctx->get_timeout_nsec());
thd->pop_internal_handler();
if (result)
DBUG_RETURN(TRUE);
ot_ctx->set_has_protection_against_grl();
}
if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request,
flags, &mdl_ticket) ||
mdl_ticket == NULL)
{
DEBUG_SYNC(thd, "before_open_table_wait_refresh");
DBUG_RETURN(TRUE);
}
DEBUG_SYNC(thd, "after_open_table_mdl_shared");
}
else
{
/*
Grab reference to the MDL lock ticket that was acquired
by the caller.
*/
mdl_ticket= table_list->mdl_request.ticket;
}
DBUG_EXECUTE_IF("sql_opening_table",
{
if (thd->slave_thread) {
const char act[]=
"now signal opening wait_for slave_killed";
DBUG_ASSERT(!debug_sync_set_action(current_thd,
STRING_WITH_LEN(act)));
}
}
);
hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length);
if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS ||
table_list->open_strategy == TABLE_LIST::OPEN_FOR_CREATE)
{
bool exists;
if (check_if_table_exists(thd, table_list, &exists))
DBUG_RETURN(TRUE);
if (!exists)
{
if (table_list->open_strategy == TABLE_LIST::OPEN_FOR_CREATE &&
! (flags & (MYSQL_OPEN_FORCE_SHARED_MDL |
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)))
{
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
thd->push_internal_handler(&mdl_deadlock_handler);
DEBUG_SYNC(thd, "before_upgrading_lock_from_S_to_X_for_create_table");
bool wait_result= thd->mdl_context.upgrade_shared_lock_nsec(
table_list->mdl_request.ticket,
MDL_EXCLUSIVE,
thd->variables.lock_wait_timeout_nsec);
thd->pop_internal_handler();
DEBUG_SYNC(thd, "after_upgrading_lock_from_S_to_X_for_create_table");
/* Deadlock or timeout occurred while upgrading the lock. */
if (wait_result)
DBUG_RETURN(TRUE);
}
DBUG_RETURN(FALSE);
}
/* Table exists. Let us try to open it. */
}
else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB)
DBUG_RETURN(FALSE);
retry_share:
{
Table_cache *tc= table_cache_manager.get_cache(thd);
tc->lock();
/*
Try to get unused TABLE object or at least pointer to
TABLE_SHARE from the table cache.
*/
table= tc->get_table(thd, hash_value, key, key_length, &share);
if (table)
{
/* We have found an unused TABLE object. */
if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
/*
TABLE_SHARE::version can only be initialised while holding the
LOCK_open and in this case no one has a reference to the share
object, if a reference exists to the share object it is necessary
to lock both LOCK_open AND all table caches in order to update
TABLE_SHARE::version. The same locks are required to increment
refresh_version global variable.
As result it is safe to compare TABLE_SHARE::version and
refresh_version values while having only lock on the table
cache for this thread.
Table cache should not contain any unused TABLE objects with
old versions.
*/
DBUG_ASSERT(!share->has_old_version());
/*
Still some of already opened might become outdated (e.g. due to
concurrent table flush). So we need to compare version of opened
tables with version of TABLE object we just have got.
*/
if (thd->open_tables &&
thd->open_tables->s->version != share->version)
{
tc->release_table(thd, table);
tc->unlock();
(void)ot_ctx->request_backoff_action(
Open_table_context::OT_REOPEN_TABLES,
NULL);
DBUG_RETURN(TRUE);
}
}
tc->unlock();
/* Call rebind_psi outside of the critical section. */
DBUG_ASSERT(table->file != NULL);
table->file->rebind_psi();
thd->status_var.table_open_cache_hits++;
goto table_found;
}
else if (share)
{
/*
We weren't able to get an unused TABLE object. Still we have
found TABLE_SHARE for it. So let us try to create new TABLE
for it. We start by incrementing share's reference count and
checking its version.
*/
mysql_mutex_lock(&LOCK_open);
tc->unlock();
share->ref_count++;
goto share_found;
}
else
{
/*
We have not found neither TABLE nor TABLE_SHARE object in
table cache (this means that there are no TABLE objects for
it in it).
Let us try to get TABLE_SHARE from table definition cache or
from disk and then to create TABLE object for it.
*/
tc->unlock();
}
}
mysql_mutex_lock(&LOCK_open);
if (!(share= get_table_share_with_discover(thd, table_list, key,
key_length, OPEN_VIEW,
&error,
hash_value)))
{
mysql_mutex_unlock(&LOCK_open);
/*
If thd->is_error() is not set, we either need discover
(error == 7), or the error was silenced by the prelocking
handler (error == 0), in which case we should skip this
table.
*/
if (error == 7 && !thd->is_error())
{
(void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
table_list);
}
DBUG_RETURN(TRUE);
}
/*
Check if this TABLE_SHARE-object corresponds to a view. Note, that there is
no need to call TABLE_SHARE::has_old_version() as we do for regular tables,
because view shares are always up to date.
*/
if (share->is_view)
{
/*
If parent_l of the table_list is non null then a merge table
has this view as child table, which is not supported.
*/
if (table_list->parent_l)
{
my_error(ER_WRONG_MRG_TABLE, MYF(0));
goto err_unlock;
}
/*
This table is a view. Validate its metadata version: in particular,
that it was a view when the statement was prepared.
*/
if (check_and_update_table_version(thd, table_list, share))
goto err_unlock;
if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
{
my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db,
table_list->table_name);
goto err_unlock;
}
/* Open view */
if (mysql_make_view(thd, share, table_list, false))
goto err_unlock;
/* TODO: Don't free this */
release_table_share(share);
DBUG_ASSERT(table_list->view);
mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(FALSE);
}
/*
Note that situation when we are trying to open a table for what
was a view during previous execution of PS will be handled in by
the caller. Here we should simply open our table even if
TABLE_LIST::view is true.
*/
if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
{
my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db,
table_list->table_name);
goto err_unlock;
}
share_found:
if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
if (share->has_old_version())
{
/*
We already have an MDL lock. But we have encountered an old
version of table in the table definition cache which is possible
when someone changes the table version directly in the cache
without acquiring a metadata lock (e.g. this can happen during
"rolling" FLUSH TABLE(S)).
Release our reference to share, wait until old version of
share goes away and then try to get new version of table share.
*/
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
bool wait_result;
thd->push_internal_handler(&mdl_deadlock_handler);
wait_result= tdc_wait_for_old_version_nsec(thd, table_list->db,
table_list->table_name,
ot_ctx->get_timeout_nsec(),
mdl_ticket->get_deadlock_weight());
thd->pop_internal_handler();
if (wait_result)
DBUG_RETURN(TRUE);
goto retry_share;
}
if (thd->open_tables && thd->open_tables->s->version != share->version)
{
/*
If the version changes while we're opening the tables,
we have to back off, close all the tables opened-so-far,
and try to reopen them. Note: refresh_version is currently
changed only during FLUSH TABLES.
*/
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
(void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
NULL);
DBUG_RETURN(TRUE);
}
}
mysql_mutex_unlock(&LOCK_open);
/* make a new table */
if (!(table= (TABLE*) my_malloc(sizeof(*table), MYF(MY_WME))))
goto err_lock;
error= open_table_from_share(thd, share, alias,
(uint) (HA_OPEN_KEYFILE |
HA_OPEN_RNDFILE |
HA_GET_INDEX |
HA_TRY_READ_ONLY),
(READ_KEYINFO | COMPUTE_TYPES |
EXTRA_RECORD),
thd->open_options, table, FALSE);
if (error)
{
my_free(table);
if (error == 7)
(void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
table_list);
else if (share->crashed)
(void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR,
table_list);
goto err_lock;
}
if (open_table_entry_fini(thd, share, table))
{
closefrm(table, 0);
my_free(table);
goto err_lock;
}
{
/* Add new TABLE object to table cache for this connection. */
Table_cache *tc= table_cache_manager.get_cache(thd);
tc->lock();
if (tc->add_used_table(thd, table))
{
tc->unlock();
goto err_lock;
}
tc->unlock();
}
thd->status_var.open_table_time += my_timer_since(timer);
thd->status_var.table_open_cache_misses++;
table_found:
table->mdl_ticket= mdl_ticket;
table->next= thd->open_tables; /* Link into simple list */
thd->set_open_tables(table);
table->count_comment_bytes= thd->count_comment_bytes; /* Assigning the
comment bytes count to the relevant table */
table->reginfo.lock_type=TL_READ; /* Assume read */
reset:
table->set_created();
/*
Check that there is no reference to a condition from an earlier query
(cf. Bug#58553).
*/
DBUG_ASSERT(table->file->pushed_cond == NULL);
table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
table_list->table= table;
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (table->part_info)
{
/* Set all [named] partitions as used. */
if (table->part_info->set_partition_bitmaps(table_list))
DBUG_RETURN(true);
}
else if (table_list->partition_names)
{
/* Don't allow PARTITION () clause on a nonpartitioned table */
my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
DBUG_RETURN(true);
}
#endif
table->init(thd, table_list);
DBUG_RETURN(FALSE);
err_lock:
mysql_mutex_lock(&LOCK_open);
err_unlock:
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(TRUE);
}