polardbxengine/sql/sql_cmd_ddl_table.cc

398 lines
15 KiB
C++

/* Copyright (c) 2016, 2019, 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, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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/sql_cmd_ddl_table.h"
#include <string.h>
#include <sys/types.h>
#include "my_inttypes.h"
#include "my_sys.h"
#include "mysqld_error.h"
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h" // create_table_precheck()
#include "sql/binlog.h" // mysql_bin_log
#include "sql/dd/cache/dictionary_client.h"
#include "sql/derror.h" // ER_THD
#include "sql/error_handler.h" // Ignore_error_handler
#include "sql/handler.h"
#include "sql/item.h"
#include "sql/mysqld.h" // opt_log_slow_admin_statements
#include "sql/partition_info.h" // check_partition_tablespace_names()
#include "sql/query_options.h"
#include "sql/query_result.h"
#include "sql/session_tracker.h"
#include "sql/sql_alter.h"
#include "sql/sql_base.h" // open_tables_for_query()
#include "sql/sql_class.h"
#include "sql/sql_data_change.h"
#include "sql/sql_error.h"
#include "sql/sql_insert.h" // Query_result_create
#include "sql/sql_lex.h"
#include "sql/sql_list.h"
#include "sql/sql_parse.h" // prepare_index_and_data_dir_path()
#include "sql/sql_select.h" // handle_query()
#include "sql/sql_table.h" // mysql_create_like_table()
#include "sql/sql_tablespace.h" // validate_tablespace_name()
#include "sql/strfunc.h"
#include "sql/system_variables.h"
#include "sql/table.h"
#include "thr_lock.h"
#ifndef DBUG_OFF
#include "sql/current_thd.h"
#endif // DBUG_OFF
Sql_cmd_ddl_table::Sql_cmd_ddl_table(Alter_info *alter_info)
: m_alter_info(alter_info) {
#ifndef DBUG_OFF
LEX *lex = current_thd->lex;
DBUG_ASSERT(lex->alter_info == m_alter_info);
DBUG_ASSERT(lex->sql_command == SQLCOM_ALTER_TABLE ||
lex->sql_command == SQLCOM_ANALYZE ||
lex->sql_command == SQLCOM_ASSIGN_TO_KEYCACHE ||
lex->sql_command == SQLCOM_CHECK ||
lex->sql_command == SQLCOM_CREATE_INDEX ||
lex->sql_command == SQLCOM_CREATE_TABLE ||
lex->sql_command == SQLCOM_DROP_INDEX ||
lex->sql_command == SQLCOM_OPTIMIZE ||
lex->sql_command == SQLCOM_PRELOAD_KEYS ||
lex->sql_command == SQLCOM_REPAIR);
#endif // DBUG_OFF
DBUG_ASSERT(m_alter_info != nullptr);
}
bool Sql_cmd_create_table::execute(THD *thd) {
LEX *const lex = thd->lex;
SELECT_LEX *const select_lex = lex->select_lex;
SELECT_LEX_UNIT *const unit = lex->unit;
TABLE_LIST *const first_table = select_lex->get_table_list();
TABLE_LIST *const all_tables = first_table;
bool link_to_local;
TABLE_LIST *create_table = first_table;
/*
Code below (especially in mysql_create_table() and Query_result_create
methods) may modify HA_CREATE_INFO structure in LEX, so we have to
use a copy of this structure to make execution prepared statement-
safe. A shallow copy is enough as this code won't modify any memory
referenced from this structure.
*/
HA_CREATE_INFO create_info(*lex->create_info);
/*
We need to copy alter_info for the same reasons of re-execution
safety, only in case of Alter_info we have to do (almost) a deep
copy.
*/
Alter_info alter_info(*m_alter_info, thd->mem_root);
if (thd->is_error()) {
/* If out of memory when creating a copy of alter_info. */
return true;
}
if (((lex->create_info->used_fields & HA_CREATE_USED_DATADIR) != 0 ||
(lex->create_info->used_fields & HA_CREATE_USED_INDEXDIR) != 0) &&
check_access(thd, FILE_ACL, any_db, NULL, NULL, false, false)) {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "FILE");
return true;
}
if (!thd->is_plugin_fake_ddl()) {
if (create_table_precheck(thd, query_expression_tables, create_table))
return true;
}
/* Might have been updated in create_table_precheck */
create_info.alias = create_table->alias;
/*
If no engine type was given, work out the default now
rather than at parse-time.
*/
if (!(create_info.used_fields & HA_CREATE_USED_ENGINE))
create_info.db_type = create_info.options & HA_LEX_CREATE_TMP_TABLE
? ha_default_temp_handlerton(thd)
: ha_default_handlerton(thd);
/*
Assign target tablespace name to enable locking in lock_table_names().
Reject invalid names.
*/
if (create_info.tablespace) {
if (validate_tablespace_name_length(create_info.tablespace) ||
validate_tablespace_name(TS_CMD_NOT_DEFINED, create_info.tablespace,
create_info.db_type))
return true;
if (lex_string_strmake(thd->mem_root, &create_table->target_tablespace_name,
create_info.tablespace,
strlen(create_info.tablespace)))
return true;
}
// Reject invalid tablespace names specified for partitions.
if (validate_partition_tablespace_name_lengths(thd->lex->part_info) ||
validate_partition_tablespace_names(thd->lex->part_info,
create_info.db_type))
return true;
/* Fix names if symlinked or relocated tables */
if (prepare_index_and_data_dir_path(thd, &create_info.data_file_name,
&create_info.index_file_name,
create_table->table_name))
return true;
{
partition_info *part_info = thd->lex->part_info;
if (part_info != NULL && has_external_data_or_index_dir(*part_info) &&
check_access(thd, FILE_ACL, any_db, NULL, NULL, false, false)) {
return true;
}
if (part_info && !(part_info = thd->lex->part_info->get_clone(thd, true)))
return true;
thd->work_part_info = part_info;
}
bool res = false;
if (select_lex->item_list.elements) // With select
{
Query_result *result;
/*
CREATE TABLE...IGNORE/REPLACE SELECT... can be unsafe, unless
ORDER BY PRIMARY KEY clause is used in SELECT statement. We therefore
use row based logging if mixed or row based logging is available.
TODO: Check if the order of the output of the select statement is
deterministic. Waiting for BUG#42415
*/
if (lex->is_ignore())
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT);
if (lex->duplicates == DUP_REPLACE)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT);
/*
If:
a) we inside an SP and there was NAME_CONST substitution,
b) binlogging is on (STMT mode),
c) we log the SP as separate statements
raise a warning, as it may cause problems
(see 'NAME_CONST issues' in 'Binary Logging of Stored Programs')
*/
if (thd->query_name_consts && mysql_bin_log.is_open() &&
thd->variables.binlog_format == BINLOG_FORMAT_STMT &&
!mysql_bin_log.is_query_in_union(thd, thd->query_id)) {
List_iterator_fast<Item> it(select_lex->item_list);
Item *item;
uint splocal_refs = 0;
/* Count SP local vars in the top-level SELECT list */
while ((item = it++)) {
if (item->is_splocal()) splocal_refs++;
}
/*
If it differs from number of NAME_CONST substitution applied,
we may have a SOME_FUNC(NAME_CONST()) in the SELECT list,
that may cause a problem with binary log (see BUG#35383),
raise a warning.
*/
if (splocal_refs != thd->query_name_consts)
push_warning(
thd, Sql_condition::SL_WARNING, ER_UNKNOWN_ERROR,
"Invoked routine ran a statement that may cause problems with "
"binary log, see 'NAME_CONST issues' in 'Binary Logging of Stored "
"Programs' "
"section of the manual.");
}
if (unit->set_limit(thd, select_lex)) return true;
/*
Disable non-empty MERGE tables with CREATE...SELECT. Too
complicated. See Bug #26379. Empty MERGE tables are read-only
and don't allow CREATE...SELECT anyway.
*/
if (create_info.used_fields & HA_CREATE_USED_UNION) {
my_error(ER_WRONG_OBJECT, MYF(0), create_table->db,
create_table->table_name, "BASE TABLE");
return true;
}
if (open_tables_for_query(thd, all_tables, false)) return true;
/* The table already exists */
if (create_table->table || create_table->is_view()) {
if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) {
push_warning_printf(thd, Sql_condition::SL_NOTE, ER_TABLE_EXISTS_ERROR,
ER_THD(thd, ER_TABLE_EXISTS_ERROR),
create_info.alias);
my_ok(thd);
return false;
} else {
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias);
return false;
}
}
/*
Remove target table from main select and name resolution
context. This can't be done earlier as it will break view merging in
statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT".
*/
lex->unlink_first_table(&link_to_local);
/* Updating any other table is prohibited in CTS statement */
for (TABLE_LIST *table = lex->query_tables; table;
table = table->next_global) {
if (table->lock_descriptor().type >= TL_WRITE_ALLOW_WRITE) {
lex->link_first_table_back(create_table, link_to_local);
my_error(ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT, MYF(0),
table->table_name, create_info.alias);
return true;
}
}
/*
Query_result_create is currently not re-execution friendly and
needs to be created for every execution of a PS/SP.
*/
if ((result = new (thd->mem_root) Query_result_create(
create_table, &create_info, &alter_info, select_lex->item_list,
lex->duplicates, query_expression_tables))) {
// For objects acquired during table creation.
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
Ignore_error_handler ignore_handler;
Strict_error_handler strict_handler;
if (thd->lex->is_ignore())
thd->push_internal_handler(&ignore_handler);
else if (thd->is_strict_mode())
thd->push_internal_handler(&strict_handler);
/*
CREATE from SELECT give its SELECT_LEX for SELECT,
and item_list belong to SELECT
*/
res = handle_query(thd, lex, result, SELECT_NO_UNLOCK, 0);
if (thd->lex->is_ignore() || thd->is_strict_mode())
thd->pop_internal_handler();
destroy(result);
}
lex->link_first_table_back(create_table, link_to_local);
} else {
Strict_error_handler strict_handler;
/* Push Strict_error_handler */
if (!thd->lex->is_ignore() && thd->is_strict_mode())
thd->push_internal_handler(&strict_handler);
/* regular create */
if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) {
/* CREATE TABLE ... LIKE ... */
res = mysql_create_like_table(thd, create_table, query_expression_tables,
&create_info);
} else {
/* Regular CREATE TABLE */
res = mysql_create_table(thd, create_table, &create_info, &alter_info);
}
/* Pop Strict_error_handler */
if (!thd->lex->is_ignore() && thd->is_strict_mode())
thd->pop_internal_handler();
if (!res) {
/* in case of create temp tables if @@session_track_state_change is
ON then send session state notification in OK packet */
if (create_info.options & HA_LEX_CREATE_TMP_TABLE &&
thd->session_tracker.get_tracker(SESSION_STATE_CHANGE_TRACKER)
->is_enabled())
thd->session_tracker.get_tracker(SESSION_STATE_CHANGE_TRACKER)
->mark_as_changed(thd, NULL);
my_ok(thd);
}
}
return res;
}
bool Sql_cmd_create_or_drop_index_base::execute(THD *thd) {
/*
CREATE INDEX and DROP INDEX are implemented by calling ALTER
TABLE with proper arguments.
In the future ALTER TABLE will notice that the request is to
only add indexes and create these one by one for the existing
table without having to do a full rebuild.
*/
LEX *const lex = thd->lex;
SELECT_LEX *const select_lex = lex->select_lex;
TABLE_LIST *const first_table = select_lex->get_table_list();
TABLE_LIST *const all_tables = first_table;
/* Prepare stack copies to be re-execution safe */
HA_CREATE_INFO create_info;
Alter_info alter_info(*m_alter_info, thd->mem_root);
if (thd->is_fatal_error()) /* out of memory creating a copy of alter_info */
return true; // OOM
if (check_one_table_access(thd, INDEX_ACL, all_tables)) return true;
/*
Currently CREATE INDEX or DROP INDEX cause a full table rebuild
and thus classify as slow administrative statements just like
ALTER TABLE.
*/
thd->enable_slow_log = opt_log_slow_admin_statements;
create_info.db_type = 0;
create_info.row_type = ROW_TYPE_NOT_USED;
create_info.default_table_charset = thd->variables.collation_database;
/* Push Strict_error_handler */
Strict_error_handler strict_handler;
if (thd->is_strict_mode()) thd->push_internal_handler(&strict_handler);
DBUG_ASSERT(!select_lex->order_list.elements);
const bool res =
mysql_alter_table(thd, first_table->db, first_table->table_name,
&create_info, first_table, &alter_info);
/* Pop Strict_error_handler */
if (thd->is_strict_mode()) thd->pop_internal_handler();
return res;
}
bool Sql_cmd_cache_index::execute(THD *thd) {
TABLE_LIST *const first_table = thd->lex->select_lex->get_table_list();
if (check_table_access(thd, INDEX_ACL, first_table, true, UINT_MAX, false))
return true;
return assign_to_keycache(thd, first_table);
}
bool Sql_cmd_load_index::execute(THD *thd) {
TABLE_LIST *const first_table = thd->lex->select_lex->get_table_list();
if (check_table_access(thd, INDEX_ACL, first_table, true, UINT_MAX, false))
return true;
return preload_keys(thd, first_table);
}