Skip to content

Instantly share code, notes, and snippets.

@le1smo
Last active December 8, 2021 03:03
Show Gist options
  • Select an option

  • Save le1smo/395fd3e5fa293aa412c8de94615ad8e3 to your computer and use it in GitHub Desktop.

Select an option

Save le1smo/395fd3e5fa293aa412c8de94615ad8e3 to your computer and use it in GitHub Desktop.
Core function of name resolution in mysql. All code come from mysql source code, version 8.0.27.
/**
Add a table to list of used tables.
@param thd Current session.
@param table_name Table to add
@param alias alias for table (or null if no alias)
@param table_options A set of the following bits:
- TL_OPTION_UPDATING : Table will be updated
- TL_OPTION_FORCE_INDEX : Force usage of index
- TL_OPTION_ALIAS : an alias in multi table DELETE
@param lock_type How table should be locked
@param mdl_type Type of metadata lock to acquire on the table.
@param index_hints_arg a list of index hints(FORCE/USE/IGNORE INDEX).
@param partition_names List to carry partition names from PARTITION (...)
clause in statement
@param option Used by cache index
@param pc Current parsing context, if available.
@return Pointer to TABLE_LIST element added to the total table list
@retval
0 Error
*/
TABLE_LIST *Query_block::add_table_to_list(
THD *thd, Table_ident *table_name, const char *alias, ulong table_options,
thr_lock_type lock_type, enum_mdl_type mdl_type,
List<Index_hint> *index_hints_arg, List<String> *partition_names,
LEX_STRING *option, Parse_context *pc) {
TABLE_LIST *previous_table_ref =
nullptr; /* The table preceding the current one. */
LEX *lex = thd->lex;
DBUG_TRACE;
assert(table_name != nullptr);
// A derived table has no table name, only an alias.
if (!(table_options & TL_OPTION_ALIAS) && !table_name->is_derived_table()) {
Ident_name_check ident_check_status =
check_table_name(table_name->table.str, table_name->table.length);
if (ident_check_status == Ident_name_check::WRONG) {
my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name->table.str);
return nullptr;
} else if (ident_check_status == Ident_name_check::TOO_LONG) {
my_error(ER_TOO_LONG_IDENT, MYF(0), table_name->table.str);
return nullptr;
}
}
LEX_STRING db = to_lex_string(table_name->db);
if (!table_name->is_derived_table() && !table_name->is_table_function() &&
table_name->db.str &&
(check_and_convert_db_name(&db, false) != Ident_name_check::OK))
return nullptr;
const char *alias_str = alias ? alias : table_name->table.str;
if (!alias) /* Alias is case sensitive */
{
if (table_name->sel) {
my_error(ER_DERIVED_MUST_HAVE_ALIAS, MYF(0));
return nullptr;
}
if (!(alias_str =
(char *)thd->memdup(alias_str, table_name->table.length + 1)))
return nullptr;
}
TABLE_LIST *ptr = new (thd->mem_root) TABLE_LIST;
if (ptr == nullptr) return nullptr; /* purecov: inspected */
if (lower_case_table_names && table_name->table.length)
table_name->table.length = my_casedn_str(
files_charset_info, const_cast<char *>(table_name->table.str));
ptr->query_block = this;
ptr->table_name = table_name->table.str;
ptr->table_name_length = table_name->table.length;
ptr->alias = alias_str;
ptr->is_alias = alias != nullptr;
ptr->table_function = table_name->table_function;
if (table_name->table_function) {
table_func_count++;
ptr->derived_key_list.clear();
}
if (table_name->db.str) {
ptr->is_fqtn = true;
ptr->db = table_name->db.str;
ptr->db_length = table_name->db.length;
} else {
bool found_cte;
if (find_common_table_expr(thd, table_name, ptr, pc, &found_cte))
return nullptr;
if (!found_cte && lex->copy_db_to(&ptr->db, &ptr->db_length))
return nullptr;
}
ptr->set_tableno(0);
ptr->set_lock({lock_type, THR_DEFAULT});
ptr->updating = table_options & TL_OPTION_UPDATING;
ptr->ignore_leaves = table_options & TL_OPTION_IGNORE_LEAVES;
ptr->set_derived_query_expression(table_name->sel);
if (!ptr->is_derived() && !ptr->is_table_function() &&
is_infoschema_db(ptr->db, ptr->db_length)) {
dd::info_schema::convert_table_name_case(
const_cast<char *>(ptr->db), const_cast<char *>(ptr->table_name));
bool hidden_system_view = false;
ptr->is_system_view = dd::get_dictionary()->is_system_view_name(
ptr->db, ptr->table_name, &hidden_system_view);
ST_SCHEMA_TABLE *schema_table;
if (ptr->updating &&
/* Special cases which are processed by commands itself */
lex->sql_command != SQLCOM_CHECK &&
lex->sql_command != SQLCOM_CHECKSUM &&
!(lex->sql_command == SQLCOM_CREATE_VIEW && ptr->is_system_view)) {
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
thd->security_context()->priv_user().str,
thd->security_context()->priv_host().str,
INFORMATION_SCHEMA_NAME.str);
return nullptr;
}
if (ptr->is_system_view) {
if (thd->lex->sql_command != SQLCOM_CREATE_VIEW) {
/*
Stop users from using hidden system views, unless
it is used by SHOW commands.
*/
if (thd->lex->query_block && hidden_system_view &&
!(thd->lex->query_block->active_options() &
OPTION_SELECT_FOR_SHOW)) {
my_error(ER_NO_SYSTEM_VIEW_ACCESS, MYF(0), ptr->table_name);
return nullptr;
}
/*
Stop users from accessing I_S.FILES if they do not have
PROCESS privilege.
*/
if (!strcmp(ptr->table_name, "FILES") &&
check_global_access(thd, PROCESS_ACL))
return nullptr;
}
} else {
schema_table = find_schema_table(thd, ptr->table_name);
/*
Report an error
if hidden schema table name is used in the statement other than
SHOW statement OR
if unknown schema table is used in the statement other than
SHOW CREATE VIEW statement.
Invalid view warning is reported for SHOW CREATE VIEW statement in
the table open stage.
*/
if ((!schema_table &&
!(thd->query_plan.get_command() == SQLCOM_SHOW_CREATE &&
thd->query_plan.get_lex()->only_view)) ||
(schema_table && schema_table->hidden &&
(sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0)) {
my_error(ER_UNKNOWN_TABLE, MYF(0), ptr->table_name,
INFORMATION_SCHEMA_NAME.str);
return nullptr;
}
if (schema_table) {
ptr->schema_table = schema_table;
}
}
}
ptr->cacheable_table = true;
ptr->index_hints = index_hints_arg;
ptr->option = option ? option->str : nullptr;
/* check that used name is unique */
if (lock_type != TL_IGNORE) {
TABLE_LIST *first_table = table_list.first;
if (lex->sql_command == SQLCOM_CREATE_VIEW)
first_table = first_table ? first_table->next_local : nullptr;
for (TABLE_LIST *tables = first_table; tables;
tables = tables->next_local) {
if (!my_strcasecmp(table_alias_charset, alias_str, tables->alias) &&
!strcmp(ptr->db, tables->db)) {
my_error(ER_NONUNIQ_TABLE, MYF(0), alias_str); /* purecov: tested */
return nullptr; /* purecov: tested */
}
}
}
/* Store the table reference preceding the current one. */
if (table_list.elements > 0) {
/*
table_list.next points to the last inserted TABLE_LIST->next_local'
element
We don't use the offsetof() macro here to avoid warnings from gcc
*/
previous_table_ref =
(TABLE_LIST *)((char *)table_list.next -
((char *)&(ptr->next_local) - (char *)ptr));
/*
Set next_name_resolution_table of the previous table reference to point
to the current table reference. In effect the list
TABLE_LIST::next_name_resolution_table coincides with
TABLE_LIST::next_local. Later this may be changed in
store_top_level_join_columns() for NATURAL/USING joins.
*/
previous_table_ref->next_name_resolution_table = ptr;
}
/*
Link the current table reference in a local list (list for current select).
Notice that as a side effect here we set the next_local field of the
previous table reference to 'ptr'. Here we also add one element to the
list 'table_list'.
*/
table_list.link_in_list(ptr, &ptr->next_local);
ptr->next_name_resolution_table = nullptr;
ptr->partition_names = partition_names;
/* Link table in global list (all used tables) */
lex->add_to_query_tables(ptr);
// Pure table aliases do not need to be locked:
if (!(table_options & TL_OPTION_ALIAS)) {
MDL_REQUEST_INIT(&ptr->mdl_request, MDL_key::TABLE, ptr->db,
ptr->table_name, mdl_type, MDL_TRANSACTION);
}
if (table_name->is_derived_table()) {
ptr->derived_key_list.clear();
derived_table_count++;
}
// Check access to DD tables. We must allow CHECK and ALTER TABLE
// for the DDSE tables, since this is expected by the upgrade
// client. We must also allow DDL access for the initialize thread,
// since this thread is creating the I_S views.
// Note that at this point, the mdl request for CREATE TABLE is still
// MDL_SHARED, so we must explicitly check for SQLCOM_CREATE_TABLE.
const dd::Dictionary *dictionary = dd::get_dictionary();
if (dictionary &&
!dictionary->is_dd_table_access_allowed(
thd->is_dd_system_thread() || thd->is_initialize_system_thread() ||
thd->is_server_upgrade_thread(),
(ptr->mdl_request.is_ddl_or_lock_tables_lock_request() ||
(lex->sql_command == SQLCOM_CREATE_TABLE &&
ptr == lex->query_tables)) &&
lex->sql_command != SQLCOM_CHECK &&
lex->sql_command != SQLCOM_ALTER_TABLE,
ptr->db, ptr->db_length, ptr->table_name)) {
// We must allow creation of the system views even for non-system
// threads since this is expected by the mysql_upgrade utility.
if (!(lex->sql_command == SQLCOM_CREATE_VIEW &&
dd::get_dictionary()->is_system_view_name(
lex->query_tables->db, lex->query_tables->table_name))) {
my_error(ER_NO_SYSTEM_TABLE_ACCESS, MYF(0),
ER_THD_NONCONST(thd, dictionary->table_type_error_code(
ptr->db, ptr->table_name)),
ptr->db, ptr->table_name);
// Take error handler into account to see if we should return.
if (thd->is_error()) return nullptr;
}
}
return ptr;
}
/**
Find field in a table reference.
@param thd thread handler
@param table_list table reference to search
@param name name of field
@param length length of field name
@param item_name name of item if it will be created (VIEW)
@param db_name optional database name that qualifies the field
@param table_name optional table name that qualifies the field
@param[in,out] ref if 'name' is resolved to a view field, ref
is set to point to the found view field
@param want_privilege privileges to check for column
= 0: no privilege checking is needed
@param allow_rowid do allow finding of "_rowid" field?
@param field_index_ptr position in field list (used to
speedup lookup for fields in prepared tables)
@param register_tree_change TRUE if ref is not stack variable and we
need register changes in item tree
@param[out] actual_table the original table reference where the field
belongs - differs from 'table_list' only for
NATURAL_USING joins.
Find a field in a table reference depending on the type of table
reference. There are three types of table references with respect
to the representation of their result columns:
- an array of Field_translator objects for MERGE views and some
information_schema tables,
- an array of Field objects (and possibly a name hash) for stored
tables,
- a list of Natural_join_column objects for NATURAL/USING joins.
This procedure detects the type of the table reference 'table_list'
and calls the corresponding search routine.
The function checks column-level privileges for the found field
according to argument want_privilege.
The function marks the column in corresponding table's read set or
write set according to THD::mark_used_columns.
@retval NULL field is not found
@retval view_ref_found found value in VIEW (real result is in *ref)
@retval otherwise pointer to field
*/
Field *find_field_in_table_ref(THD *thd, TABLE_LIST *table_list,
const char *name, size_t length,
const char *item_name, const char *db_name,
const char *table_name, Item **ref,
ulong want_privilege, bool allow_rowid,
uint *field_index_ptr, bool register_tree_change,
TABLE_LIST **actual_table) {
Field *fld;
DBUG_TRACE;
assert(table_list->alias);
assert(name);
assert(item_name);
DBUG_PRINT("enter", ("table: '%s' field name: '%s' item name: '%s' ref %p",
table_list->alias, name, item_name, ref));
/*
Check that the table and database that qualify the current field name
are the same as the table reference we are going to search for the field.
Exclude from the test below nested joins because the columns in a
nested join generally originate from different tables. Nested joins
also have no table name, except when a nested join is a merge view
or an information schema table.
We include explicitly table references with a 'field_translation' table,
because if there are views over natural joins we don't want to search
inside the view, but we want to search directly in the view columns
which are represented as a 'field_translation'.
TODO: Ensure that table_name, db_name and tables->db always points to
something !
*/
if (/* Exclude nested joins. */
(!table_list->nested_join ||
/* Include merge views and information schema tables. */
table_list->field_translation) &&
/*
Test if the field qualifiers match the table reference we plan
to search.
*/
table_name && table_name[0] &&
(my_strcasecmp(table_alias_charset, table_list->alias, table_name) ||
(db_name && db_name[0] && table_list->db && table_list->db[0] &&
(table_list->schema_table
? my_strcasecmp(system_charset_info, db_name, table_list->db)
: strcmp(db_name, table_list->db)))))
return nullptr;
*actual_table = nullptr;
if (table_list->field_translation) {
/* 'table_list' is a view or an information schema table. */
if ((fld = find_field_in_view(thd, table_list, name, ref,
register_tree_change)))
*actual_table = table_list;
} else if (!table_list->nested_join) {
/* 'table_list' is a stored table. */
assert(table_list->table);
if ((fld = find_field_in_table(table_list->table, name, allow_rowid,
field_index_ptr)))
*actual_table = table_list;
} else {
/*
'table_list' is a NATURAL/USING join, or an operand of such join that
is a nested join itself.
If the field name we search for is qualified, then search for the field
in the table references used by NATURAL/USING the join.
*/
if (table_name && table_name[0]) {
for (TABLE_LIST *table : table_list->nested_join->join_list) {
if ((fld = find_field_in_table_ref(
thd, table, name, length, item_name, db_name, table_name, ref,
want_privilege, allow_rowid, field_index_ptr,
register_tree_change, actual_table)))
return fld;
}
return nullptr;
}
/*
Non-qualified field, search directly in the result columns of the
natural join. The condition of the outer IF is true for the top-most
natural join, thus if the field is not qualified, we will search
directly the top-most NATURAL/USING join.
*/
fld = find_field_in_natural_join(thd, table_list, name, ref,
register_tree_change, actual_table);
}
if (fld) {
// Check if there are sufficient privileges to the found field.
if (want_privilege) {
if (fld != view_ref_found) {
if (check_column_grant_in_table_ref(thd, *actual_table, name, length,
want_privilege))
return WRONG_GRANT;
} else {
assert(ref && *ref && (*ref)->fixed);
assert(*actual_table == (down_cast<Item_ident *>(*ref))->cached_table);
Column_privilege_tracker tracker(thd, want_privilege);
if ((*ref)->walk(&Item::check_column_privileges, enum_walk::PREFIX,
(uchar *)thd))
return WRONG_GRANT;
}
}
/*
Get read_set correct for this field so that the handler knows that
this field is involved in the query and gets retrieved.
*/
if (fld == view_ref_found) {
Mark_field mf(thd->mark_used_columns);
(*ref)->walk(&Item::mark_field_in_map, enum_walk::SUBQUERY_POSTFIX,
(uchar *)&mf);
} else // surely fld != NULL (see outer if())
fld->table->mark_column_used(fld, thd->mark_used_columns);
}
return fld;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment