mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
7332af49e4
- Changed ER(ER_...) to ER_THD(thd, ER_...) when thd was known or if there was many calls to current_thd in the same function. - Changed ER(ER_..) to ER_THD_OR_DEFAULT(current_thd, ER...) in some places where current_thd is not necessary defined. - Removing calls to current_thd when we have access to thd Part of this is optimization (not calling current_thd when not needed), but part is bug fixing for error condition when current_thd is not defined (For example on startup and end of mysqld) Notable renames done as otherwise a lot of functions would have to be changed: - In JOIN structure renamed: examined_rows -> join_examined_rows record_count -> join_record_count - In Field, renamed new_field() to make_new_field() Other things: - Added DBUG_ASSERT(thd == tmp_thd) in Item_singlerow_subselect() just to be safe. - Removed old 'tab' prefix in JOIN_TAB::save_explain_data() and use members directly - Added 'thd' as argument to a few functions to avoid calling current_thd.
366 lines
13 KiB
C++
366 lines
13 KiB
C++
/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
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; version 2 of the License.
|
|
|
|
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "sql_parse.h" // check_access
|
|
#include "sql_table.h" // mysql_alter_table,
|
|
// mysql_exchange_partition
|
|
#include "sql_base.h" // open_temporary_tables
|
|
#include "sql_alter.h"
|
|
#include "wsrep_mysqld.h"
|
|
|
|
Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
|
|
:drop_list(rhs.drop_list, mem_root),
|
|
alter_list(rhs.alter_list, mem_root),
|
|
key_list(rhs.key_list, mem_root),
|
|
create_list(rhs.create_list, mem_root),
|
|
flags(rhs.flags),
|
|
keys_onoff(rhs.keys_onoff),
|
|
partition_names(rhs.partition_names, mem_root),
|
|
num_parts(rhs.num_parts),
|
|
requested_algorithm(rhs.requested_algorithm),
|
|
requested_lock(rhs.requested_lock)
|
|
{
|
|
/*
|
|
Make deep copies of used objects.
|
|
This is not a fully deep copy - clone() implementations
|
|
of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
|
|
do not copy string constants. At the same length the only
|
|
reason we make a copy currently is that ALTER/CREATE TABLE
|
|
code changes input Alter_info definitions, but string
|
|
constants never change.
|
|
*/
|
|
list_copy_and_replace_each_value(drop_list, mem_root);
|
|
list_copy_and_replace_each_value(alter_list, mem_root);
|
|
list_copy_and_replace_each_value(key_list, mem_root);
|
|
list_copy_and_replace_each_value(create_list, mem_root);
|
|
/* partition_names are not deeply copied currently */
|
|
}
|
|
|
|
|
|
bool Alter_info::set_requested_algorithm(const LEX_STRING *str)
|
|
{
|
|
// To avoid adding new keywords to the grammar, we match strings here.
|
|
if (!my_strcasecmp(system_charset_info, str->str, "INPLACE"))
|
|
requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
|
|
else if (!my_strcasecmp(system_charset_info, str->str, "COPY"))
|
|
requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
|
|
else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
|
|
requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
|
|
else
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Alter_info::set_requested_lock(const LEX_STRING *str)
|
|
{
|
|
// To avoid adding new keywords to the grammar, we match strings here.
|
|
if (!my_strcasecmp(system_charset_info, str->str, "NONE"))
|
|
requested_lock= ALTER_TABLE_LOCK_NONE;
|
|
else if (!my_strcasecmp(system_charset_info, str->str, "SHARED"))
|
|
requested_lock= ALTER_TABLE_LOCK_SHARED;
|
|
else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE"))
|
|
requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
|
|
else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
|
|
requested_lock= ALTER_TABLE_LOCK_DEFAULT;
|
|
else
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
Alter_table_ctx::Alter_table_ctx()
|
|
: datetime_field(NULL), error_if_not_empty(false),
|
|
tables_opened(0),
|
|
db(NULL), table_name(NULL), alias(NULL),
|
|
new_db(NULL), new_name(NULL), new_alias(NULL),
|
|
fk_error_if_delete_row(false), fk_error_id(NULL),
|
|
fk_error_table(NULL)
|
|
#ifndef DBUG_OFF
|
|
, tmp_table(false)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
|
|
Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
|
|
uint tables_opened_arg,
|
|
char *new_db_arg, char *new_name_arg)
|
|
: datetime_field(NULL), error_if_not_empty(false),
|
|
tables_opened(tables_opened_arg),
|
|
new_db(new_db_arg), new_name(new_name_arg),
|
|
fk_error_if_delete_row(false), fk_error_id(NULL),
|
|
fk_error_table(NULL)
|
|
#ifndef DBUG_OFF
|
|
, tmp_table(false)
|
|
#endif
|
|
{
|
|
/*
|
|
Assign members db, table_name, new_db and new_name
|
|
to simplify further comparisions: we want to see if it's a RENAME
|
|
later just by comparing the pointers, avoiding the need for strcmp.
|
|
*/
|
|
db= table_list->db;
|
|
table_name= table_list->table_name;
|
|
alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
|
|
|
|
if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
|
|
new_db= db;
|
|
|
|
if (new_name)
|
|
{
|
|
DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));
|
|
|
|
if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case
|
|
{
|
|
my_casedn_str(files_charset_info, new_name);
|
|
new_alias= new_name;
|
|
}
|
|
else if (lower_case_table_names == 2) // Convert new_name to lower case
|
|
{
|
|
strmov(new_alias= new_alias_buff, new_name);
|
|
my_casedn_str(files_charset_info, new_name);
|
|
}
|
|
else
|
|
new_alias= new_name; // LCTN=0 => case sensitive + case preserving
|
|
|
|
if (!is_database_changed() &&
|
|
!my_strcasecmp(table_alias_charset, new_name, table_name))
|
|
{
|
|
/*
|
|
Source and destination table names are equal:
|
|
make is_table_renamed() more efficient.
|
|
*/
|
|
new_alias= table_name;
|
|
new_name= table_name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
new_alias= alias;
|
|
new_name= table_name;
|
|
}
|
|
|
|
my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
|
|
current_pid, thd->thread_id);
|
|
/* Safety fix for InnoDB */
|
|
if (lower_case_table_names)
|
|
my_casedn_str(files_charset_info, tmp_name);
|
|
|
|
if (table_list->table->s->tmp_table == NO_TMP_TABLE)
|
|
{
|
|
build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
|
|
|
|
build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0);
|
|
|
|
build_table_filename(new_filename, sizeof(new_filename) - 1,
|
|
new_db, new_name, reg_ext, 0);
|
|
|
|
build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "",
|
|
FN_IS_TMP);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
We are not filling path, new_path and new_filename members if
|
|
we are altering temporary table as these members are not used in
|
|
this case. This fact is enforced with assert.
|
|
*/
|
|
build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
|
|
#ifndef DBUG_OFF
|
|
tmp_table= true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
bool Sql_cmd_alter_table::execute(THD *thd)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
/* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
|
|
SELECT_LEX *select_lex= &lex->select_lex;
|
|
/* first table of first SELECT_LEX */
|
|
TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
|
|
/*
|
|
Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
|
|
so we have to use a copy of this structure to make execution
|
|
prepared statement- safe. A shallow copy is enough as no memory
|
|
referenced from this structure will be modified.
|
|
@todo move these into constructor...
|
|
*/
|
|
HA_CREATE_INFO create_info(lex->create_info);
|
|
Alter_info alter_info(lex->alter_info, thd->mem_root);
|
|
ulong priv=0;
|
|
ulong priv_needed= ALTER_ACL;
|
|
bool result;
|
|
|
|
DBUG_ENTER("Sql_cmd_alter_table::execute");
|
|
|
|
if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
|
|
DBUG_RETURN(TRUE);
|
|
/*
|
|
We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
|
|
as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
|
|
*/
|
|
if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION |
|
|
Alter_info::ALTER_RENAME))
|
|
priv_needed|= DROP_ACL;
|
|
|
|
/* Must be set in the parser */
|
|
DBUG_ASSERT(select_lex->db);
|
|
DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION));
|
|
DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION));
|
|
if (check_access(thd, priv_needed, first_table->db,
|
|
&first_table->grant.privilege,
|
|
&first_table->grant.m_internal,
|
|
0, 0) ||
|
|
check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db,
|
|
&priv,
|
|
NULL, /* Don't use first_tab->grant with sel_lex->db */
|
|
0, 0))
|
|
DBUG_RETURN(TRUE); /* purecov: inspected */
|
|
|
|
/* If it is a merge table, check privileges for merge children. */
|
|
if (create_info.merge_list.first)
|
|
{
|
|
/*
|
|
The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
|
|
underlying base tables, even if there are temporary tables with the same
|
|
names.
|
|
|
|
From user's point of view, it might look as if the user must have these
|
|
privileges on temporary tables to create a merge table over them. This is
|
|
one of two cases when a set of privileges is required for operations on
|
|
temporary tables (see also CREATE TABLE).
|
|
|
|
The reason for this behavior stems from the following facts:
|
|
|
|
- For merge tables, the underlying table privileges are checked only
|
|
at CREATE TABLE / ALTER TABLE time.
|
|
|
|
In other words, once a merge table is created, the privileges of
|
|
the underlying tables can be revoked, but the user will still have
|
|
access to the merge table (provided that the user has privileges on
|
|
the merge table itself).
|
|
|
|
- Temporary tables shadow base tables.
|
|
|
|
I.e. there might be temporary and base tables with the same name, and
|
|
the temporary table takes the precedence in all operations.
|
|
|
|
- For temporary MERGE tables we do not track if their child tables are
|
|
base or temporary. As result we can't guarantee that privilege check
|
|
which was done in presence of temporary child will stay relevant later
|
|
as this temporary table might be removed.
|
|
|
|
If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
|
|
the underlying *base* tables, it would create a security breach as in
|
|
Bug#12771903.
|
|
*/
|
|
|
|
if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
|
|
create_info.merge_list.first, FALSE, UINT_MAX, FALSE))
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
|
|
DBUG_RETURN(TRUE); /* purecov: inspected */
|
|
|
|
if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
|
|
{
|
|
// Rename of table
|
|
TABLE_LIST tmp_table;
|
|
memset(&tmp_table, 0, sizeof(tmp_table));
|
|
tmp_table.table_name= lex->name.str;
|
|
tmp_table.db= select_lex->db;
|
|
tmp_table.grant.privilege= priv;
|
|
if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
|
|
UINT_MAX, FALSE))
|
|
DBUG_RETURN(TRUE); /* purecov: inspected */
|
|
}
|
|
|
|
/* Don't yet allow changing of symlinks with ALTER TABLE */
|
|
if (create_info.data_file_name)
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED),
|
|
"DATA DIRECTORY");
|
|
if (create_info.index_file_name)
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED),
|
|
"INDEX DIRECTORY");
|
|
create_info.data_file_name= create_info.index_file_name= NULL;
|
|
|
|
thd->enable_slow_log= opt_log_slow_admin_statements;
|
|
|
|
#ifdef WITH_WSREP
|
|
TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl);
|
|
|
|
if ((!thd->is_current_stmt_binlog_format_row() ||
|
|
!find_temporary_table(thd, first_table)))
|
|
{
|
|
WSREP_TO_ISOLATION_BEGIN(((lex->name.str) ? select_lex->db : NULL),
|
|
((lex->name.str) ? lex->name.str : NULL),
|
|
first_table);
|
|
}
|
|
#endif /* WITH_WSREP */
|
|
|
|
result= mysql_alter_table(thd, select_lex->db, lex->name.str,
|
|
&create_info,
|
|
first_table,
|
|
&alter_info,
|
|
select_lex->order_list.elements,
|
|
select_lex->order_list.first,
|
|
lex->ignore);
|
|
|
|
DBUG_RETURN(result);
|
|
|
|
#ifdef WITH_WSREP
|
|
error:
|
|
WSREP_WARN("ALTER TABLE isolation failure");
|
|
DBUG_RETURN(TRUE);
|
|
#endif /* WITH_WSREP */
|
|
}
|
|
|
|
bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
|
|
{
|
|
/* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
|
|
SELECT_LEX *select_lex= &thd->lex->select_lex;
|
|
/* first table of first SELECT_LEX */
|
|
TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;
|
|
|
|
if (check_access(thd, ALTER_ACL, table_list->db,
|
|
&table_list->grant.privilege,
|
|
&table_list->grant.m_internal,
|
|
0, 0))
|
|
return true;
|
|
|
|
if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false))
|
|
return true;
|
|
|
|
thd->enable_slow_log= opt_log_slow_admin_statements;
|
|
|
|
/*
|
|
Check if we attempt to alter mysql.slow_log or
|
|
mysql.general_log table and return an error if
|
|
it is the case.
|
|
TODO: this design is obsolete and will be removed.
|
|
*/
|
|
if (check_if_log_table(table_list, TRUE, "ALTER"))
|
|
return true;
|
|
|
|
return
|
|
mysql_discard_or_import_tablespace(thd, table_list,
|
|
m_tablespace_op == DISCARD_TABLESPACE);
|
|
}
|