summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rwxr-xr-xsql/CMakeLists.txt4
-rw-r--r--sql/field.cc7
-rw-r--r--sql/filesort.cc3
-rw-r--r--sql/ha_berkeley.cc6
-rw-r--r--sql/ha_federated.cc310
-rw-r--r--sql/ha_federated.h2
-rw-r--r--sql/ha_innodb.cc24
-rw-r--r--sql/ha_ndbcluster.cc2
-rw-r--r--sql/handler.cc15
-rw-r--r--sql/handler.h68
-rw-r--r--sql/item.cc56
-rw-r--r--sql/item.h17
-rw-r--r--sql/item_create.cc3
-rw-r--r--sql/item_func.cc22
-rw-r--r--sql/item_func.h2
-rw-r--r--sql/item_strfunc.h4
-rw-r--r--sql/lock.cc35
-rw-r--r--sql/log.cc5
-rw-r--r--sql/log_event.cc11
-rw-r--r--sql/mysql_priv.h3
-rw-r--r--sql/mysqld.cc7
-rw-r--r--sql/opt_range.cc6
-rw-r--r--sql/set_var.cc4
-rw-r--r--sql/sp.cc4
-rw-r--r--sql/sp_head.cc164
-rw-r--r--sql/sp_rcontext.cc18
-rw-r--r--sql/sp_rcontext.h6
-rw-r--r--sql/sql_base.cc59
-rw-r--r--sql/sql_class.cc26
-rw-r--r--sql/sql_class.h65
-rw-r--r--sql/sql_delete.cc31
-rw-r--r--sql/sql_insert.cc178
-rw-r--r--sql/sql_lex.cc133
-rw-r--r--sql/sql_load.cc16
-rw-r--r--sql/sql_parse.cc23
-rw-r--r--sql/sql_select.cc37
-rw-r--r--sql/sql_select.h19
-rw-r--r--sql/sql_show.cc4
-rw-r--r--sql/sql_table.cc11
-rw-r--r--sql/sql_union.cc57
-rw-r--r--sql/sql_update.cc42
-rw-r--r--sql/sql_yacc.yy5
-rw-r--r--sql/table.cc133
-rw-r--r--sql/table.h1
44 files changed, 1010 insertions, 638 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 0cbeb97184f..b0553f622f8 100755
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -138,6 +138,10 @@ ADD_CUSTOM_COMMAND(
)
ADD_DEPENDENCIES(mysqld${MYSQLD_EXE_SUFFIX} gen_lex_hash)
+# Remove the auto-generated files as part of 'Clean Solution'
+SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES
+ "lex_hash.h;message.rc;message.h;sql_yacc.h;sql_yacc.cc")
+
ADD_LIBRARY(udf_example MODULE udf_example.c udf_example.def)
ADD_DEPENDENCIES(udf_example strings)
TARGET_LINK_LIBRARIES(udf_example wsock32)
diff --git a/sql/field.cc b/sql/field.cc
index 78b2515c55f..3f74210807b 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -7641,8 +7641,11 @@ int Field_enum::store(longlong nr, bool unsigned_val)
if ((ulonglong) nr > typelib->count || nr == 0)
{
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, 1);
- nr=0;
- error=1;
+ if (nr != 0 || table->in_use->count_cuted_fields)
+ {
+ nr= 0;
+ error= 1;
+ }
}
store_type((ulonglong) (uint) nr);
return error;
diff --git a/sql/filesort.cc b/sql/filesort.cc
index f8868ed6927..db73ede99b0 100644
--- a/sql/filesort.cc
+++ b/sql/filesort.cc
@@ -1430,7 +1430,8 @@ get_addon_fields(THD *thd, Field **ptabfield, uint sortlength, uint *plength)
doesn't work for alter table
*/
if (thd->lex->sql_command != SQLCOM_SELECT &&
- thd->lex->sql_command != SQLCOM_INSERT_SELECT)
+ thd->lex->sql_command != SQLCOM_INSERT_SELECT &&
+ thd->lex->sql_command != SQLCOM_CREATE_TABLE)
return 0;
for (pfield= ptabfield; (field= *pfield) ; pfield++)
{
diff --git a/sql/ha_berkeley.cc b/sql/ha_berkeley.cc
index 2a5fe775ca6..fbfd5031656 100644
--- a/sql/ha_berkeley.cc
+++ b/sql/ha_berkeley.cc
@@ -2646,7 +2646,11 @@ ha_rows ha_berkeley::estimate_rows_upper_bound()
int ha_berkeley::cmp_ref(const byte *ref1, const byte *ref2)
{
if (hidden_primary_key)
- return memcmp(ref1, ref2, BDB_HIDDEN_PRIMARY_KEY_LENGTH);
+ {
+ ulonglong a=uint5korr((char*) ref1);
+ ulonglong b=uint5korr((char*) ref2);
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
int result;
Field *field;
diff --git a/sql/ha_federated.cc b/sql/ha_federated.cc
index 3cf9c2a8b99..d8ffd6c55f8 100644
--- a/sql/ha_federated.cc
+++ b/sql/ha_federated.cc
@@ -497,105 +497,6 @@ err:
}
-/*
- Check (in create) whether the tables exists, and that it can be connected to
-
- SYNOPSIS
- check_foreign_data_source()
- share pointer to FEDERATED share
- table_create_flag tells us that ::create is the caller,
- therefore, return CANT_CREATE_FEDERATED_TABLE
-
- DESCRIPTION
- This method first checks that the connection information that parse url
- has populated into the share will be sufficient to connect to the foreign
- table, and if so, does the foreign table exist.
-*/
-
-static int check_foreign_data_source(FEDERATED_SHARE *share,
- bool table_create_flag)
-{
- char query_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char error_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- uint error_code;
- String query(query_buffer, sizeof(query_buffer), &my_charset_bin);
- MYSQL *mysql;
- DBUG_ENTER("ha_federated::check_foreign_data_source");
-
- /* Zero the length, otherwise the string will have misc chars */
- query.length(0);
-
- /* error out if we can't alloc memory for mysql_init(NULL) (per Georg) */
- if (!(mysql= mysql_init(NULL)))
- DBUG_RETURN(HA_ERR_OUT_OF_MEM);
- /* check if we can connect */
- if (!mysql_real_connect(mysql,
- share->hostname,
- share->username,
- share->password,
- share->database,
- share->port,
- share->socket, 0))
- {
- /*
- we want the correct error message, but it to return
- ER_CANT_CREATE_FEDERATED_TABLE if called by ::create
- */
- error_code= (table_create_flag ?
- ER_CANT_CREATE_FEDERATED_TABLE :
- ER_CONNECT_TO_FOREIGN_DATA_SOURCE);
-
- my_sprintf(error_buffer,
- (error_buffer,
- "database: '%s' username: '%s' hostname: '%s'",
- share->database, share->username, share->hostname));
-
- my_error(ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), error_buffer);
- goto error;
- }
- else
- {
- /*
- Since we do not support transactions at this version, we can let the
- client API silently reconnect. For future versions, we will need more
- logic to deal with transactions
- */
- mysql->reconnect= 1;
- /*
- Note: I am not using INORMATION_SCHEMA because this needs to work with
- versions prior to 5.0
-
- if we can connect, then make sure the table exists
-
- the query will be: SELECT * FROM `tablename` WHERE 1=0
- */
- query.append(FEDERATED_SELECT);
- query.append(FEDERATED_STAR);
- query.append(FEDERATED_FROM);
- append_ident(&query, share->table_name, share->table_name_length,
- ident_quote_char);
- query.append(FEDERATED_WHERE);
- query.append(FEDERATED_FALSE);
-
- if (mysql_real_query(mysql, query.ptr(), query.length()))
- {
- error_code= table_create_flag ?
- ER_CANT_CREATE_FEDERATED_TABLE : ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST;
- my_sprintf(error_buffer, (error_buffer, "error: %d '%s'",
- mysql_errno(mysql), mysql_error(mysql)));
-
- my_error(error_code, MYF(0), error_buffer);
- goto error;
- }
- }
- error_code=0;
-
-error:
- mysql_close(mysql);
- DBUG_RETURN(error_code);
-}
-
-
static int parse_url_error(FEDERATED_SHARE *share, TABLE *table, int error_num)
{
char buf[FEDERATED_QUERY_BUFFER_SIZE];
@@ -1478,36 +1379,7 @@ int ha_federated::open(const char *name, int mode, uint test_if_locked)
DBUG_RETURN(1);
thr_lock_data_init(&share->lock, &lock, NULL);
- /* Connect to foreign database mysql_real_connect() */
- mysql= mysql_init(0);
-
- /*
- BUG# 17044 Federated Storage Engine is not UTF8 clean
- Add set names to whatever charset the table is at open
- of table
- */
- /* this sets the csname like 'set names utf8' */
- mysql_options(mysql,MYSQL_SET_CHARSET_NAME,
- this->table->s->table_charset->csname);
-
- if (!mysql || !mysql_real_connect(mysql,
- share->hostname,
- share->username,
- share->password,
- share->database,
- share->port,
- share->socket, 0))
- {
- free_share(share);
- DBUG_RETURN(stash_remote_error());
- }
- /*
- Since we do not support transactions at this version, we can let the client
- API silently reconnect. For future versions, we will need more logic to
- deal with transactions
- */
-
- mysql->reconnect= 1;
+ DBUG_ASSERT(mysql == NULL);
ref_length= (table->s->primary_key != MAX_KEY ?
table->key_info[table->s->primary_key].key_length :
@@ -1543,8 +1415,8 @@ int ha_federated::close(void)
stored_result= 0;
}
/* Disconnect from mysql */
- if (mysql) // QQ is this really needed
- mysql_close(mysql);
+ mysql_close(mysql);
+ mysql= NULL;
retval= free_share(share);
DBUG_RETURN(retval);
@@ -1774,7 +1646,7 @@ int ha_federated::write_row(byte *buf)
if (bulk_insert.length + values_string.length() + bulk_padding >
mysql->net.max_packet_size && bulk_insert.length)
{
- error= mysql_real_query(mysql, bulk_insert.str, bulk_insert.length);
+ error= real_query(bulk_insert.str, bulk_insert.length);
bulk_insert.length= 0;
}
else
@@ -1798,8 +1670,7 @@ int ha_federated::write_row(byte *buf)
}
else
{
- error= mysql_real_query(mysql, values_string.ptr(),
- values_string.length());
+ error= real_query(values_string.ptr(), values_string.length());
}
if (error)
@@ -1811,8 +1682,13 @@ int ha_federated::write_row(byte *buf)
field, then store the last_insert_id() value from the foreign server
*/
if (auto_increment_update_required)
+ {
update_auto_increment();
+ /* mysql_insert() uses this for protocol return value */
+ table->next_number_field->store(auto_increment_value, 1);
+ }
+
DBUG_RETURN(0);
}
@@ -1842,6 +1718,13 @@ void ha_federated::start_bulk_insert(ha_rows rows)
if (rows == 1)
DBUG_VOID_RETURN;
+ /*
+ Make sure we have an open connection so that we know the
+ maximum packet size.
+ */
+ if (!mysql && real_connect())
+ DBUG_VOID_RETURN;
+
page_size= (uint) my_getpagesize();
if (init_dynamic_string(&bulk_insert, NULL, page_size, page_size))
@@ -1870,7 +1753,7 @@ int ha_federated::end_bulk_insert()
if (bulk_insert.str && bulk_insert.length)
{
- if (mysql_real_query(mysql, bulk_insert.str, bulk_insert.length))
+ if (real_query(bulk_insert.str, bulk_insert.length))
error= stash_remote_error();
else
if (table->next_number_field)
@@ -1896,7 +1779,8 @@ void ha_federated::update_auto_increment(void)
THD *thd= current_thd;
DBUG_ENTER("ha_federated::update_auto_increment");
- thd->insert_id(mysql->last_used_con->insert_id);
+ ha_federated::info(HA_STATUS_AUTO);
+ thd->insert_id(auto_increment_value);
DBUG_PRINT("info",("last_insert_id: %ld", (long) auto_increment_value));
DBUG_VOID_RETURN;
@@ -1915,7 +1799,7 @@ int ha_federated::optimize(THD* thd, HA_CHECK_OPT* check_opt)
append_ident(&query, share->table_name, share->table_name_length,
ident_quote_char);
- if (mysql_real_query(mysql, query.ptr(), query.length()))
+ if (real_query(query.ptr(), query.length()))
{
DBUG_RETURN(stash_remote_error());
}
@@ -1943,7 +1827,7 @@ int ha_federated::repair(THD* thd, HA_CHECK_OPT* check_opt)
if (check_opt->sql_flags & TT_USEFRM)
query.append(FEDERATED_USE_FRM);
- if (mysql_real_query(mysql, query.ptr(), query.length()))
+ if (real_query(query.ptr(), query.length()))
{
DBUG_RETURN(stash_remote_error());
}
@@ -2081,7 +1965,7 @@ int ha_federated::update_row(const byte *old_data, byte *new_data)
if (!has_a_primary_key)
update_string.append(FEDERATED_LIMIT1);
- if (mysql_real_query(mysql, update_string.ptr(), update_string.length()))
+ if (real_query(update_string.ptr(), update_string.length()))
{
DBUG_RETURN(stash_remote_error());
}
@@ -2146,7 +2030,7 @@ int ha_federated::delete_row(const byte *buf)
delete_string.append(FEDERATED_LIMIT1);
DBUG_PRINT("info",
("Delete sql: %s", delete_string.c_ptr_quick()));
- if (mysql_real_query(mysql, delete_string.ptr(), delete_string.length()))
+ if (real_query(delete_string.ptr(), delete_string.length()))
{
DBUG_RETURN(stash_remote_error());
}
@@ -2255,7 +2139,7 @@ int ha_federated::index_read_idx_with_result_set(byte *buf, uint index,
NULL, 0);
sql_query.append(index_string);
- if (mysql_real_query(mysql, sql_query.ptr(), sql_query.length()))
+ if (real_query(sql_query.ptr(), sql_query.length()))
{
my_sprintf(error_buffer, (error_buffer, "error: %d '%s'",
mysql_errno(mysql), mysql_error(mysql)));
@@ -2321,7 +2205,7 @@ int ha_federated::read_range_first(const key_range *start_key,
mysql_free_result(stored_result);
stored_result= 0;
}
- if (mysql_real_query(mysql, sql_query.ptr(), sql_query.length()))
+ if (real_query(sql_query.ptr(), sql_query.length()))
{
retval= ER_QUERY_ON_FOREIGN_DATA_SOURCE;
goto error;
@@ -2421,9 +2305,7 @@ int ha_federated::rnd_init(bool scan)
stored_result= 0;
}
- if (mysql_real_query(mysql,
- share->select_query,
- strlen(share->select_query)))
+ if (real_query(share->select_query, strlen(share->select_query)))
goto error;
stored_result= mysql_store_result(mysql);
@@ -2640,8 +2522,7 @@ int ha_federated::info(uint flag)
append_ident(&status_query_string, share->table_name,
share->table_name_length, value_quote_char);
- if (mysql_real_query(mysql, status_query_string.ptr(),
- status_query_string.length()))
+ if (real_query(status_query_string.ptr(), status_query_string.length()))
goto error;
status_query_string.length(0);
@@ -2688,18 +2569,27 @@ int ha_federated::info(uint flag)
block_size= 4096;
}
- if (result)
- mysql_free_result(result);
+ if (flag & HA_STATUS_AUTO)
+ auto_increment_value= mysql->last_used_con->insert_id;
+
+ mysql_free_result(result);
DBUG_RETURN(0);
error:
- if (result)
- mysql_free_result(result);
-
- my_sprintf(error_buffer, (error_buffer, ": %d : %s",
- mysql_errno(mysql), mysql_error(mysql)));
- my_error(error_code, MYF(0), error_buffer);
+ mysql_free_result(result);
+ if (mysql)
+ {
+ my_sprintf(error_buffer, (error_buffer, ": %d : %s",
+ mysql_errno(mysql), mysql_error(mysql)));
+ my_error(error_code, MYF(0), error_buffer);
+ }
+ else
+ if (remote_error_number != -1 /* error already reported */)
+ {
+ error_code= remote_error_number;
+ my_error(error_code, MYF(0), ER(error_code));
+ }
DBUG_RETURN(error_code);
}
@@ -2777,7 +2667,7 @@ int ha_federated::delete_all_rows()
/*
TRUNCATE won't return anything in mysql_affected_rows
*/
- if (mysql_real_query(mysql, query.ptr(), query.length()))
+ if (real_query(query.ptr(), query.length()))
{
DBUG_RETURN(stash_remote_error());
}
@@ -2866,8 +2756,7 @@ int ha_federated::create(const char *name, TABLE *table_arg,
FEDERATED_SHARE tmp_share; // Only a temporary share, to test the url
DBUG_ENTER("ha_federated::create");
- if (!(retval= parse_url(&tmp_share, table_arg, 1)))
- retval= check_foreign_data_source(&tmp_share, 1);
+ retval= parse_url(&tmp_share, table_arg, 1);
my_free((gptr) tmp_share.scheme, MYF(MY_ALLOW_ZERO_PTR));
DBUG_RETURN(retval);
@@ -2875,9 +2764,114 @@ int ha_federated::create(const char *name, TABLE *table_arg,
}
+int ha_federated::real_connect()
+{
+ char buffer[FEDERATED_QUERY_BUFFER_SIZE];
+ String sql_query(buffer, sizeof(buffer), &my_charset_bin);
+ DBUG_ENTER("ha_federated::real_connect");
+
+ /*
+ Bug#25679
+ Ensure that we do not hold the LOCK_open mutex while attempting
+ to establish Federated connection to guard against a trivial
+ Denial of Service scenerio.
+ */
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ DBUG_ASSERT(mysql == NULL);
+
+ if (!(mysql= mysql_init(NULL)))
+ {
+ remote_error_number= HA_ERR_OUT_OF_MEM;
+ DBUG_RETURN(-1);
+ }
+
+ /*
+ BUG# 17044 Federated Storage Engine is not UTF8 clean
+ Add set names to whatever charset the table is at open
+ of table
+ */
+ /* this sets the csname like 'set names utf8' */
+ mysql_options(mysql,MYSQL_SET_CHARSET_NAME,
+ this->table->s->table_charset->csname);
+
+ sql_query.length(0);
+
+ if (!mysql_real_connect(mysql,
+ share->hostname,
+ share->username,
+ share->password,
+ share->database,
+ share->port,
+ share->socket, 0))
+ {
+ stash_remote_error();
+ mysql_close(mysql);
+ mysql= NULL;
+ my_error(ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), remote_error_buf);
+ remote_error_number= -1;
+ DBUG_RETURN(-1);
+ }
+
+ /*
+ We have established a connection, lets try a simple dummy query just
+ to check that the table and expected columns are present.
+ */
+ sql_query.append(share->select_query);
+ sql_query.append(FEDERATED_WHERE);
+ sql_query.append(FEDERATED_FALSE);
+ if (mysql_real_query(mysql, sql_query.ptr(), sql_query.length()))
+ {
+ sql_query.length(0);
+ sql_query.append("error: ");
+ sql_query.qs_append(mysql_errno(mysql));
+ sql_query.append(" '");
+ sql_query.append(mysql_error(mysql));
+ sql_query.append("'");
+ mysql_close(mysql);
+ mysql= NULL;
+ my_error(ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST, MYF(0), sql_query.ptr());
+ remote_error_number= -1;
+ DBUG_RETURN(-1);
+ }
+
+ /* Just throw away the result, no rows anyways but need to keep in sync */
+ mysql_free_result(mysql_store_result(mysql));
+
+ /*
+ Since we do not support transactions at this version, we can let the client
+ API silently reconnect. For future versions, we will need more logic to
+ deal with transactions
+ */
+
+ mysql->reconnect= 1;
+ DBUG_RETURN(0);
+}
+
+
+int ha_federated::real_query(const char *query, uint length)
+{
+ int rc= 0;
+ DBUG_ENTER("ha_federated::real_query");
+
+ if (!mysql && (rc= real_connect()))
+ goto end;
+
+ if (!query || !length)
+ goto end;
+
+ rc= mysql_real_query(mysql, query, length);
+
+end:
+ DBUG_RETURN(rc);
+}
+
+
int ha_federated::stash_remote_error()
{
DBUG_ENTER("ha_federated::stash_remote_error()");
+ if (!mysql)
+ DBUG_RETURN(remote_error_number);
remote_error_number= mysql_errno(mysql);
strmake(remote_error_buf, mysql_error(mysql), sizeof(remote_error_buf)-1);
if (remote_error_number == ER_DUP_ENTRY ||
diff --git a/sql/ha_federated.h b/sql/ha_federated.h
index 2e510fa87da..dc4f976c578 100644
--- a/sql/ha_federated.h
+++ b/sql/ha_federated.h
@@ -183,6 +183,8 @@ private:
uint key_len,
ha_rkey_function find_flag,
MYSQL_RES **result);
+ int real_query(const char *query, uint length);
+ int real_connect();
public:
ha_federated(TABLE *table_arg);
~ha_federated()
diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc
index b03dc9bb986..f87d47174ac 100644
--- a/sql/ha_innodb.cc
+++ b/sql/ha_innodb.cc
@@ -455,9 +455,7 @@ convert_error_code_to_mysql(
tell it also to MySQL so that MySQL knows to empty the
cached binlog for this transaction */
- if (thd) {
- ha_rollback(thd);
- }
+ mark_transaction_to_rollback(thd, TRUE);
return(HA_ERR_LOCK_DEADLOCK);
@@ -467,9 +465,8 @@ convert_error_code_to_mysql(
latest SQL statement in a lock wait timeout. Previously, we
rolled back the whole transaction. */
- if (thd && row_rollback_on_timeout) {
- ha_rollback(thd);
- }
+ mark_transaction_to_rollback(thd,
+ (bool)row_rollback_on_timeout);
return(HA_ERR_LOCK_WAIT_TIMEOUT);
@@ -521,9 +518,7 @@ convert_error_code_to_mysql(
tell it also to MySQL so that MySQL knows to empty the
cached binlog for this transaction */
- if (thd) {
- ha_rollback(thd);
- }
+ mark_transaction_to_rollback(thd, TRUE);
return(HA_ERR_LOCK_TABLE_FULL);
} else {
@@ -6721,17 +6716,6 @@ ha_innobase::store_lock(
&& !thd->tablespace_op
&& thd->lex->sql_command != SQLCOM_TRUNCATE
&& thd->lex->sql_command != SQLCOM_OPTIMIZE
-
-#ifdef __WIN__
- /* For alter table on win32 for succesful operation
- completion it is used TL_WRITE(=10) lock instead of
- TL_WRITE_ALLOW_READ(=6), however here in innodb handler
- TL_WRITE is lifted to TL_WRITE_ALLOW_WRITE, which causes
- race condition when several clients do alter table
- simultaneously (bug #17264). This fix avoids the problem. */
- && thd->lex->sql_command != SQLCOM_ALTER_TABLE
-#endif
-
&& thd->lex->sql_command != SQLCOM_CREATE_TABLE) {
lock_type = TL_WRITE_ALLOW_WRITE;
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
index 357b797ec75..a00dd6c505c 100644
--- a/sql/ha_ndbcluster.cc
+++ b/sql/ha_ndbcluster.cc
@@ -3732,7 +3732,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type)
{
m_transaction_on= FALSE;
/* Would be simpler if has_transactions() didn't always say "yes" */
- thd->no_trans_update.all= thd->no_trans_update.stmt= TRUE;
+ thd->transaction.all.modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table= TRUE;
}
else if (!thd->transaction.on)
m_transaction_on= FALSE;
diff --git a/sql/handler.cc b/sql/handler.cc
index f8aec72ec90..8ef14ce8906 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -821,6 +821,9 @@ int ha_rollback_trans(THD *thd, bool all)
}
}
#endif /* USING_TRANSACTIONS */
+ if (all)
+ thd->transaction_rollback_request= FALSE;
+
/*
If a non-transactional table was updated, warn; don't warn if this is a
slave thread (because when a slave thread executes a ROLLBACK, it has
@@ -830,7 +833,7 @@ int ha_rollback_trans(THD *thd, bool all)
the error log; but we don't want users to wonder why they have this
message in the error log, so we don't send it.
*/
- if (is_real_trans && thd->no_trans_update.all &&
+ if (is_real_trans && thd->transaction.all.modified_non_trans_table &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
@@ -858,6 +861,8 @@ int ha_autocommit_or_rollback(THD *thd, int error)
if (ha_commit_stmt(thd))
error=1;
}
+ else if (thd->transaction_rollback_request && !thd->in_sub_stmt)
+ (void) ha_rollback(thd);
else
(void) ha_rollback_stmt(thd);
@@ -1997,7 +2002,13 @@ static bool update_frm_version(TABLE *table, bool needs_lock)
int result= 1;
DBUG_ENTER("update_frm_version");
- if (table->s->mysql_version != MYSQL_VERSION_ID)
+ /*
+ No need to update frm version in case table was created or checked
+ by server with the same version. This also ensures that we do not
+ update frm version for temporary tables as this code doesn't support
+ temporary tables.
+ */
+ if (table->s->mysql_version == MYSQL_VERSION_ID)
DBUG_RETURN(0);
strxnmov(path, sizeof(path)-1, mysql_data_home, "/", table->s->db, "/",
diff --git a/sql/handler.h b/sql/handler.h
index a3767573178..74c09d2d9ee 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -420,6 +420,35 @@ typedef struct st_thd_trans
bool no_2pc;
/* storage engines that registered themselves for this transaction */
handlerton *ht[MAX_HA];
+ /*
+ The purpose of this flag is to keep track of non-transactional
+ tables that were modified in scope of:
+ - transaction, when the variable is a member of
+ THD::transaction.all
+ - top-level statement or sub-statement, when the variable is a
+ member of THD::transaction.stmt
+ This member has the following life cycle:
+ * stmt.modified_non_trans_table is used to keep track of
+ modified non-transactional tables of top-level statements. At
+ the end of the previous statement and at the beginning of the session,
+ it is reset to FALSE. If such functions
+ as mysql_insert, mysql_update, mysql_delete etc modify a
+ non-transactional table, they set this flag to TRUE. At the
+ end of the statement, the value of stmt.modified_non_trans_table
+ is merged with all.modified_non_trans_table and gets reset.
+ * all.modified_non_trans_table is reset at the end of transaction
+
+ * Since we do not have a dedicated context for execution of a
+ sub-statement, to keep track of non-transactional changes in a
+ sub-statement, we re-use stmt.modified_non_trans_table.
+ At entrance into a sub-statement, a copy of the value of
+ stmt.modified_non_trans_table (containing the changes of the
+ outer statement) is saved on stack. Then
+ stmt.modified_non_trans_table is reset to FALSE and the
+ substatement is executed. Then the new value is merged with the
+ saved value.
+ */
+ bool modified_non_trans_table;
} THD_TRANS;
enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
@@ -508,6 +537,29 @@ class handler :public Sql_alloc
*/
virtual int rnd_init(bool scan) =0;
virtual int rnd_end() { return 0; }
+ /**
+ Is not invoked for non-transactional temporary tables.
+
+ Tells the storage engine that we intend to read or write data
+ from the table. This call is prefixed with a call to handler::store_lock()
+ and is invoked only for those handler instances that stored the lock.
+
+ Calls to rnd_init/index_init are prefixed with this call. When table
+ IO is complete, we call external_lock(F_UNLCK).
+ A storage engine writer should expect that each call to
+ ::external_lock(F_[RD|WR]LOCK is followed by a call to
+ ::external_lock(F_UNLCK). If it is not, it is a bug in MySQL.
+
+ The name and signature originate from the first implementation
+ in MyISAM, which would call fcntl to set/clear an advisory
+ lock on the data file in this method.
+
+ @param lock_type F_RDLCK, F_WRLCK, F_UNLCK
+
+ @return non-0 in case of failure, 0 in case of success.
+ When lock_type is F_UNLCK, the return value is ignored.
+ */
+ virtual int external_lock(THD *thd, int lock_type) { return 0; }
public:
const handlerton *ht; /* storage engine of this handler */
@@ -548,6 +600,7 @@ public:
uint raid_type,raid_chunks;
FT_INFO *ft_handler;
enum {NONE=0, INDEX, RND} inited;
+ bool locked;
bool auto_increment_column_changed;
bool implicit_emptied; /* Can be !=0 only if HEAP */
const COND *pushed_cond;
@@ -560,10 +613,11 @@ public:
create_time(0), check_time(0), update_time(0),
key_used_on_scan(MAX_KEY), active_index(MAX_KEY),
ref_length(sizeof(my_off_t)), block_size(0),
- raid_type(0), ft_handler(0), inited(NONE), implicit_emptied(0),
+ raid_type(0), ft_handler(0), inited(NONE),
+ locked(FALSE), implicit_emptied(0),
pushed_cond(NULL)
{}
- virtual ~handler(void) { /* TODO: DBUG_ASSERT(inited == NONE); */ }
+ virtual ~handler(void) { DBUG_ASSERT(locked == FALSE); /* TODO: DBUG_ASSERT(inited == NONE); */ }
virtual handler *clone(MEM_ROOT *mem_root);
int ha_open(const char *name, int mode, int test_if_locked);
void adjust_next_insert_id_after_explicit_value(ulonglong nr);
@@ -597,6 +651,12 @@ public:
virtual const char *index_type(uint key_number) { DBUG_ASSERT(0); return "";}
+ int ha_external_lock(THD *thd, int lock_type)
+ {
+ DBUG_ENTER("ha_external_lock");
+ locked= lock_type != F_UNLCK;
+ DBUG_RETURN(external_lock(thd, lock_type));
+ }
int ha_index_init(uint idx)
{
DBUG_ENTER("ha_index_init");
@@ -689,7 +749,6 @@ public:
virtual int extra_opt(enum ha_extra_function operation, ulong cache_size)
{ return extra(operation); }
virtual int reset() { return extra(HA_EXTRA_RESET); }
- virtual int external_lock(THD *thd, int lock_type) { return 0; }
virtual void unlock_row() {}
virtual int start_stmt(THD *thd, thr_lock_type lock_type) {return 0;}
/*
@@ -837,6 +896,9 @@ public:
/* lock_count() can be more than one if the table is a MERGE */
virtual uint lock_count(void) const { return 1; }
+ /**
+ Is not invoked for non-transactional temporary tables.
+ */
virtual THR_LOCK_DATA **store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type)=0;
diff --git a/sql/item.cc b/sql/item.cc
index 2fc58eebe75..9612fbc5243 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -336,6 +336,37 @@ int Item::save_date_in_field(Field *field)
}
+/*
+ Store the string value in field directly
+
+ SYNOPSIS
+ Item::save_str_value_in_field()
+ field a pointer to field where to store
+ result the pointer to the string value to be stored
+
+ DESCRIPTION
+ The method is used by Item_*::save_in_field implementations
+ when we don't need to calculate the value to store
+ See Item_string::save_in_field() implementation for example
+
+ IMPLEMENTATION
+ Check if the Item is null and stores the NULL or the
+ result value in the field accordingly.
+
+ RETURN
+ Nonzero value if error
+*/
+
+int Item::save_str_value_in_field(Field *field, String *result)
+{
+ if (null_value)
+ return set_field_to_null(field);
+ field->set_notnull();
+ return field->store(result->ptr(), result->length(),
+ collation.collation);
+}
+
+
Item::Item():
rsize(0), name(0), orig_name(0), name_length(0), fixed(0),
is_autogenerated_name(TRUE),
@@ -1022,9 +1053,9 @@ bool Item_sp_variable::is_null()
Item_splocal::Item_splocal(const LEX_STRING &sp_var_name,
uint sp_var_idx,
enum_field_types sp_var_type,
- uint pos_in_q)
+ uint pos_in_q, uint len_in_q)
:Item_sp_variable(sp_var_name.str, sp_var_name.length),
- m_var_idx(sp_var_idx), pos_in_query(pos_in_q)
+ m_var_idx(sp_var_idx), pos_in_query(pos_in_q), len_in_query(len_in_q)
{
maybe_null= TRUE;
@@ -3009,16 +3040,6 @@ my_decimal *Item_copy_string::val_decimal(my_decimal *decimal_value)
}
-
-int Item_copy_string::save_in_field(Field *field, bool no_conversions)
-{
- if (null_value)
- return set_field_to_null(field);
- field->set_notnull();
- return field->store(str_value.ptr(),str_value.length(),
- collation.collation);
-}
-
/*
Functions to convert item to field (for send_fields)
*/
@@ -4417,6 +4438,12 @@ int Item_null::save_safe_in_field(Field *field)
}
+/*
+ This implementation can lose str_value content, so if the
+ Item uses str_value to store something, it should
+ reimplement it's ::save_in_field() as Item_string, for example, does
+*/
+
int Item::save_in_field(Field *field, bool no_conversions)
{
int error;
@@ -4474,10 +4501,7 @@ int Item_string::save_in_field(Field *field, bool no_conversions)
{
String *result;
result=val_str(&str_value);
- if (null_value)
- return set_field_to_null(field);
- field->set_notnull();
- return field->store(result->ptr(),result->length(),collation.collation);
+ return save_str_value_in_field(field, result);
}
diff --git a/sql/item.h b/sql/item.h
index 5b1a80a5f03..23f6977a0f8 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -612,6 +612,7 @@ public:
int save_time_in_field(Field *field);
int save_date_in_field(Field *field);
+ int save_str_value_in_field(Field *field, String *result);
virtual Field *get_tmp_table_field() { return 0; }
/* This is also used to create fields in CREATE ... SELECT: */
@@ -959,9 +960,18 @@ public:
SP variable in query text.
*/
uint pos_in_query;
+ /*
+ Byte length of SP variable name in the statement (see pos_in_query).
+ The value of this field may differ from the name_length value because
+ name_length contains byte length of UTF8-encoded item name, but
+ the query string (see sp_instr_stmt::m_query) is currently stored with
+ a charset from the SET NAMES statement.
+ */
+ uint len_in_query;
Item_splocal(const LEX_STRING &sp_var_name, uint sp_var_idx,
- enum_field_types sp_var_type, uint pos_in_q= 0);
+ enum_field_types sp_var_type,
+ uint pos_in_q= 0, uint len_in_q= 0);
bool is_splocal() { return 1; } /* Needed for error checking */
@@ -2166,7 +2176,10 @@ public:
my_decimal *val_decimal(my_decimal *);
void make_field(Send_field *field) { item->make_field(field); }
void copy();
- int save_in_field(Field *field, bool no_conversions);
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return save_str_value_in_field(field, &str_value);
+ }
table_map used_tables() const { return (table_map) 1L; }
bool const_item() const { return 0; }
bool is_null() { return null_value; }
diff --git a/sql/item_create.cc b/sql/item_create.cc
index 50db1c37371..561613032bc 100644
--- a/sql/item_create.cc
+++ b/sql/item_create.cc
@@ -70,7 +70,8 @@ Item *create_func_ceiling(Item* a)
Item *create_func_connection_id(void)
{
- current_thd->lex->safe_to_cache_query= 0;
+ THD *thd= current_thd;
+ thd->lex->safe_to_cache_query= 0;
return new Item_func_connection_id();
}
diff --git a/sql/item_func.cc b/sql/item_func.cc
index b256ce4624a..c70cfa1ce2a 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -649,16 +649,8 @@ bool Item_func_connection_id::fix_fields(THD *thd, Item **ref)
{
if (Item_int_func::fix_fields(thd, ref))
return TRUE;
-
- /*
- To replicate CONNECTION_ID() properly we should use
- pseudo_thread_id on slave, which contains the value of thread_id
- on master.
- */
- value= ((thd->slave_thread) ?
- thd->variables.pseudo_thread_id :
- thd->thread_id);
-
+ thd->thread_specific_used= TRUE;
+ value= thd->variables.pseudo_thread_id;
return FALSE;
}
@@ -5591,5 +5583,15 @@ Item_func_sp::fix_fields(THD *thd, Item **ref)
#endif /* ! NO_EMBEDDED_ACCESS_CHECKS */
}
+ if (!m_sp->m_chistics->detistic)
+ used_tables_cache |= RAND_TABLE_BIT;
DBUG_RETURN(res);
}
+
+
+void Item_func_sp::update_used_tables()
+{
+ Item_func::update_used_tables();
+ if (!m_sp->m_chistics->detistic)
+ used_tables_cache |= RAND_TABLE_BIT;
+}
diff --git a/sql/item_func.h b/sql/item_func.h
index 9a0201cb28b..56b5e75652c 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -1467,7 +1467,7 @@ public:
virtual ~Item_func_sp()
{}
- table_map used_tables() const { return RAND_TABLE_BIT; }
+ void update_used_tables();
void cleanup();
diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
index d7c4a3eddef..6ca0b89a22b 100644
--- a/sql/item_strfunc.h
+++ b/sql/item_strfunc.h
@@ -434,6 +434,10 @@ public:
}
const char *func_name() const { return "user"; }
const char *fully_qualified_func_name() const { return "user()"; }
+ int save_in_field(Field *field, bool no_conversions)
+ {
+ return save_str_value_in_field(field, &str_value);
+ }
};
diff --git a/sql/lock.cc b/sql/lock.cc
index 93358e56701..f730ac56d35 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -151,7 +151,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
}
thd->proc_info="System lock";
- if (lock_external(thd, tables, count))
+ if (sql_lock->table_count && lock_external(thd, sql_lock->table,
+ sql_lock->table_count))
{
/* Clear the lock type of all lock data to avoid reusage. */
reset_lock_data(sql_lock);
@@ -246,12 +247,12 @@ static int lock_external(THD *thd, TABLE **tables, uint count)
(*tables)->reginfo.lock_type <= TL_READ_NO_INSERT))
lock_type=F_RDLCK;
- if ((error=(*tables)->file->external_lock(thd,lock_type)))
+ if ((error= (*tables)->file->ha_external_lock(thd,lock_type)))
{
print_lock_error(error, (*tables)->file->table_type());
for (; i-- ; tables--)
{
- (*tables)->file->external_lock(thd, F_UNLCK);
+ (*tables)->file->ha_external_lock(thd, F_UNLCK);
(*tables)->current_lock=F_UNLCK;
}
DBUG_RETURN(error);
@@ -353,10 +354,28 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock)
}
+/**
+ Try to find the table in the list of locked tables.
+ In case of success, unlock the table and remove it from this list.
-void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table)
+ @note This function has a legacy side effect: the table is
+ unlocked even if it is not found in the locked list.
+ It's not clear if this side effect is intentional or still
+ desirable. It might lead to unmatched calls to
+ unlock_external(). Moreover, a discrepancy can be left
+ unnoticed by the storage engine, because in
+ unlock_external() we call handler::external_lock(F_UNLCK) only
+ if table->current_lock is not F_UNLCK.
+
+ @param always_unlock specify explicitly if the legacy side
+ effect is desired.
+*/
+
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table,
+ bool always_unlock)
{
- mysql_unlock_some_tables(thd, &table,1);
+ if (always_unlock == TRUE)
+ mysql_unlock_some_tables(thd, &table, /* table count */ 1);
if (locked)
{
reg1 uint i;
@@ -370,6 +389,10 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table)
DBUG_ASSERT(table->lock_position == i);
+ /* Unlock if not yet unlocked */
+ if (always_unlock == FALSE)
+ mysql_unlock_some_tables(thd, &table, /* table count */ 1);
+
/* Decrement table_count in advance, making below expressions easier */
old_tables= --locked->table_count;
@@ -623,7 +646,7 @@ static int unlock_external(THD *thd, TABLE **table,uint count)
if ((*table)->current_lock != F_UNLCK)
{
(*table)->current_lock = F_UNLCK;
- if ((error=(*table)->file->external_lock(thd, F_UNLCK)))
+ if ((error= (*table)->file->ha_external_lock(thd, F_UNLCK)))
{
error_code=error;
print_lock_error(error_code, (*table)->file->table_type());
diff --git a/sql/log.cc b/sql/log.cc
index 744d2a3ca65..e9aa273676a 100644
--- a/sql/log.cc
+++ b/sql/log.cc
@@ -162,7 +162,7 @@ static int binlog_rollback(THD *thd, bool all)
table. Such cases should be rare (updating a
non-transactional table inside a transaction...)
*/
- if (unlikely(thd->no_trans_update.all))
+ if (unlikely(thd->transaction.all.modified_non_trans_table))
{
Query_log_event qev(thd, STRING_WITH_LEN("ROLLBACK"), TRUE, FALSE);
qev.error_code= 0; // see comment in MYSQL_LOG::write(THD, IO_CACHE)
@@ -217,7 +217,7 @@ static int binlog_savepoint_rollback(THD *thd, void *sv)
non-transactional table. Otherwise, truncate the binlog cache starting
from the SAVEPOINT command.
*/
- if (unlikely(thd->no_trans_update.all))
+ if (unlikely(thd->transaction.all.modified_non_trans_table))
{
Query_log_event qinfo(thd, thd->query, thd->query_length, TRUE, FALSE);
DBUG_RETURN(mysql_bin_log.write(&qinfo));
@@ -1833,6 +1833,7 @@ bool MYSQL_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event)
/* NULL would represent nothing to replicate after ROLLBACK */
DBUG_ASSERT(commit_event != NULL);
+ DBUG_ASSERT(is_open());
if (likely(is_open())) // Should always be true
{
uint length, group, carry, hdr_offs, val;
diff --git a/sql/log_event.cc b/sql/log_event.cc
index c37df31ae00..1ef765f607f 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -1303,8 +1303,9 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg,
ulong query_length, bool using_trans,
bool suppress_use, THD::killed_state killed_status_arg)
:Log_event(thd_arg,
- ((thd_arg->tmp_table_used ? LOG_EVENT_THREAD_SPECIFIC_F : 0)
- | (suppress_use ? LOG_EVENT_SUPPRESS_USE_F : 0)),
+ ((thd_arg->tmp_table_used || thd_arg->thread_specific_used) ?
+ LOG_EVENT_THREAD_SPECIFIC_F : 0) |
+ (suppress_use ? LOG_EVENT_SUPPRESS_USE_F : 0),
using_trans),
data_buf(0), query(query_arg), catalog(thd_arg->catalog),
db(thd_arg->db), q_len((uint32) query_length),
@@ -2689,8 +2690,10 @@ Load_log_event::Load_log_event(THD *thd_arg, sql_exchange *ex,
List<Item> &fields_arg,
enum enum_duplicates handle_dup,
bool ignore, bool using_trans)
- :Log_event(thd_arg, !thd_arg->tmp_table_used ?
- 0 : LOG_EVENT_THREAD_SPECIFIC_F, using_trans),
+ :Log_event(thd_arg,
+ (thd_arg->tmp_table_used || thd_arg->thread_specific_used) ?
+ LOG_EVENT_THREAD_SPECIFIC_F : 0,
+ using_trans),
thread_id(thd_arg->thread_id),
slave_proxy_id(thd_arg->variables.pseudo_thread_id),
num_fields(0),fields(0),
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index d14aab57489..ca6aa8ecab0 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -1452,7 +1452,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count);
-void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table);
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table,
+ bool always_unlock);
void mysql_lock_abort(THD *thd, TABLE *table);
bool mysql_lock_abort_for_thread(THD *thd, TABLE *table);
MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b);
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 9eb3d157dcf..57d1656736d 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -7241,11 +7241,16 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
#endif /* HAVE_INNOBASE_DB */
case OPT_MYISAM_RECOVER:
{
- if (!argument || !argument[0])
+ if (!argument)
{
myisam_recover_options= HA_RECOVER_DEFAULT;
myisam_recover_options_str= myisam_recover_typelib.type_names[0];
}
+ else if (!argument[0])
+ {
+ myisam_recover_options= HA_RECOVER_NONE;
+ myisam_recover_options_str= "OFF";
+ }
else
{
myisam_recover_options_str=argument;
diff --git a/sql/opt_range.cc b/sql/opt_range.cc
index 247f0eada49..d978c8882ac 100644
--- a/sql/opt_range.cc
+++ b/sql/opt_range.cc
@@ -972,7 +972,7 @@ QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT()
DBUG_PRINT("info", ("Freeing separate handler 0x%lx (free: %d)", (long) file,
free_file));
file->reset();
- file->external_lock(current_thd, F_UNLCK);
+ file->ha_external_lock(current_thd, F_UNLCK);
file->close();
}
}
@@ -1142,7 +1142,7 @@ int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler)
/* Caller will free the memory */
goto failure; /* purecov: inspected */
}
- if (file->external_lock(thd, F_RDLCK))
+ if (file->ha_external_lock(thd, F_RDLCK))
goto failure;
if (!head->no_keyread)
{
@@ -1152,7 +1152,7 @@ int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler)
if (file->extra(HA_EXTRA_RETRIEVE_PRIMARY_KEY) ||
init() || reset())
{
- file->external_lock(thd, F_UNLCK);
+ file->ha_external_lock(thd, F_UNLCK);
file->close();
goto failure;
}
diff --git a/sql/set_var.cc b/sql/set_var.cc
index b30aa008366..e1246617d84 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -2901,14 +2901,14 @@ static bool set_option_autocommit(THD *thd, set_var *var)
{
/* We changed to auto_commit mode */
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status|= SERVER_STATUS_AUTOCOMMIT;
if (ha_commit(thd))
return 1;
}
else
{
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_AUTOCOMMIT;
}
}
diff --git a/sql/sp.cc b/sql/sp.cc
index c0e7d5e2271..75d6fa4618f 100644
--- a/sql/sp.cc
+++ b/sql/sp.cc
@@ -273,7 +273,7 @@ db_find_routine(THD *thd, int type, sp_name *name, sp_head **sphp)
if ((ret= db_find_routine_aux(thd, type, name, table)) != SP_OK)
goto done;
- if (table->s->fields != MYSQL_PROC_FIELD_COUNT)
+ if (table->s->fields < MYSQL_PROC_FIELD_COUNT)
{
ret= SP_GET_FIELD_FAILED;
goto done;
@@ -523,7 +523,7 @@ db_create_routine(THD *thd, int type, sp_head *sp)
strxmov(definer, thd->lex->definer->user.str, "@",
thd->lex->definer->host.str, NullS);
- if (table->s->fields != MYSQL_PROC_FIELD_COUNT)
+ if (table->s->fields < MYSQL_PROC_FIELD_COUNT)
{
ret= SP_GET_FIELD_FAILED;
goto done;
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 0ac1db336d0..5ad6625efb8 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -337,13 +337,13 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr)
enum_check_fields save_count_cuted_fields= thd->count_cuted_fields;
bool save_abort_on_warning= thd->abort_on_warning;
- bool save_no_trans_update_stmt= thd->no_trans_update.stmt;
+ bool save_stmt_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table;
thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
thd->abort_on_warning=
thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES);
- thd->no_trans_update.stmt= FALSE;
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
/* Save the value in the field. Convert the value if needed. */
@@ -351,7 +351,7 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr)
thd->count_cuted_fields= save_count_cuted_fields;
thd->abort_on_warning= save_abort_on_warning;
- thd->no_trans_update.stmt= save_no_trans_update_stmt;
+ thd->transaction.stmt.modified_non_trans_table= save_stmt_modified_non_trans_table;
if (thd->net.report_error)
{
@@ -795,7 +795,8 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b)
/*
- Replace thd->query{_length} with a string that one can write to the binlog.
+ Replace thd->query{_length} with a string that one can write to the binlog
+ or the query cache.
SYNOPSIS
subst_spvars()
@@ -807,7 +808,9 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b)
DESCRIPTION
The binlog-suitable string is produced by replacing references to SP local
- variables with NAME_CONST('sp_var_name', value) calls.
+ variables with NAME_CONST('sp_var_name', value) calls. To make this string
+ suitable for the query cache this function allocates some additional space
+ for the query cache flags.
RETURN
FALSE on success
@@ -820,80 +823,89 @@ static bool
subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str)
{
DBUG_ENTER("subst_spvars");
- if (thd->prelocked_mode == NON_PRELOCKED && mysql_bin_log.is_open())
- {
- Dynamic_array<Item_splocal*> sp_vars_uses;
- char *pbuf, *cur, buffer[512];
- String qbuf(buffer, sizeof(buffer), &my_charset_bin);
- int prev_pos, res;
- /* Find all instances of Item_splocal used in this statement */
- for (Item *item= instr->free_list; item; item= item->next)
- {
- if (item->is_splocal())
- {
- Item_splocal *item_spl= (Item_splocal*)item;
- if (item_spl->pos_in_query)
- sp_vars_uses.append(item_spl);
- }
- }
- if (!sp_vars_uses.elements())
- DBUG_RETURN(FALSE);
-
- /* Sort SP var refs by their occurences in the query */
- sp_vars_uses.sort(cmp_splocal_locations);
+ Dynamic_array<Item_splocal*> sp_vars_uses;
+ char *pbuf, *cur, buffer[512];
+ String qbuf(buffer, sizeof(buffer), &my_charset_bin);
+ int prev_pos, res, buf_len;
- /*
- Construct a statement string where SP local var refs are replaced
- with "NAME_CONST(name, value)"
- */
- qbuf.length(0);
- cur= query_str->str;
- prev_pos= res= 0;
- for (Item_splocal **splocal= sp_vars_uses.front();
- splocal < sp_vars_uses.back(); splocal++)
+ /* Find all instances of Item_splocal used in this statement */
+ for (Item *item= instr->free_list; item; item= item->next)
+ {
+ if (item->is_splocal())
{
- Item *val;
+ Item_splocal *item_spl= (Item_splocal*)item;
+ if (item_spl->pos_in_query)
+ sp_vars_uses.append(item_spl);
+ }
+ }
+ if (!sp_vars_uses.elements())
+ DBUG_RETURN(FALSE);
+
+ /* Sort SP var refs by their occurences in the query */
+ sp_vars_uses.sort(cmp_splocal_locations);
- char str_buffer[STRING_BUFFER_USUAL_SIZE];
- String str_value_holder(str_buffer, sizeof(str_buffer),
- &my_charset_latin1);
- String *str_value;
-
- /* append the text between sp ref occurences */
- res|= qbuf.append(cur + prev_pos, (*splocal)->pos_in_query - prev_pos);
- prev_pos= (*splocal)->pos_in_query + (*splocal)->m_name.length;
-
- /* append the spvar substitute */
- res|= qbuf.append(STRING_WITH_LEN(" NAME_CONST('"));
- res|= qbuf.append((*splocal)->m_name.str, (*splocal)->m_name.length);
- res|= qbuf.append(STRING_WITH_LEN("',"));
- res|= (*splocal)->fix_fields(thd, (Item **) splocal);
+ /*
+ Construct a statement string where SP local var refs are replaced
+ with "NAME_CONST(name, value)"
+ */
+ qbuf.length(0);
+ cur= query_str->str;
+ prev_pos= res= 0;
+ for (Item_splocal **splocal= sp_vars_uses.front();
+ splocal < sp_vars_uses.back(); splocal++)
+ {
+ Item *val;
- if (res)
- break;
+ char str_buffer[STRING_BUFFER_USUAL_SIZE];
+ String str_value_holder(str_buffer, sizeof(str_buffer),
+ &my_charset_latin1);
+ String *str_value;
+
+ /* append the text between sp ref occurences */
+ res|= qbuf.append(cur + prev_pos, (*splocal)->pos_in_query - prev_pos);
+ prev_pos= (*splocal)->pos_in_query + (*splocal)->len_in_query;
+
+ /* append the spvar substitute */
+ res|= qbuf.append(STRING_WITH_LEN(" NAME_CONST('"));
+ res|= qbuf.append((*splocal)->m_name.str, (*splocal)->m_name.length);
+ res|= qbuf.append(STRING_WITH_LEN("',"));
+ res|= (*splocal)->fix_fields(thd, (Item **) splocal);
- val= (*splocal)->this_item();
- DBUG_PRINT("info", ("print %p", val));
- str_value= sp_get_item_value(thd, val, &str_value_holder);
- if (str_value)
- res|= qbuf.append(*str_value);
- else
- res|= qbuf.append(STRING_WITH_LEN("NULL"));
- res|= qbuf.append(')');
- if (res)
- break;
- }
- res|= qbuf.append(cur + prev_pos, query_str->length - prev_pos);
if (res)
- DBUG_RETURN(TRUE);
+ break;
- if (!(pbuf= thd->strmake(qbuf.ptr(), qbuf.length())))
- DBUG_RETURN(TRUE);
+ val= (*splocal)->this_item();
+ DBUG_PRINT("info", ("print %p", val));
+ str_value= sp_get_item_value(thd, val, &str_value_holder);
+ if (str_value)
+ res|= qbuf.append(*str_value);
+ else
+ res|= qbuf.append(STRING_WITH_LEN("NULL"));
+ res|= qbuf.append(')');
+ if (res)
+ break;
+ }
+ res|= qbuf.append(cur + prev_pos, query_str->length - prev_pos);
+ if (res)
+ DBUG_RETURN(TRUE);
- thd->query= pbuf;
- thd->query_length= qbuf.length();
+ /*
+ Allocate additional space at the end of the new query string for the
+ query_cache_send_result_to_client function.
+ */
+ buf_len= qbuf.length() + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE + 1;
+ if ((pbuf= alloc_root(thd->mem_root, buf_len)))
+ {
+ memcpy(pbuf, qbuf.ptr(), qbuf.length());
+ pbuf[qbuf.length()]= 0;
}
+ else
+ DBUG_RETURN(TRUE);
+
+ thd->query= pbuf;
+ thd->query_length= qbuf.length();
+
DBUG_RETURN(FALSE);
}
@@ -2388,7 +2400,13 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
bool open_tables, sp_instr* instr)
{
int res= 0;
-
+ /*
+ The flag is saved at the entry to the following substatement.
+ It's reset further in the common code part.
+ It's merged with the saved parent's value at the exit of this func.
+ */
+ bool parent_modified_non_trans_table= thd->transaction.stmt.modified_non_trans_table;
+ thd->transaction.stmt.modified_non_trans_table= FALSE;
DBUG_ASSERT(!thd->derived_tables);
DBUG_ASSERT(thd->change_list.is_empty());
/*
@@ -2455,7 +2473,11 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
/* Update the state of the active arena. */
thd->stmt_arena->state= Query_arena::EXECUTED;
-
+ /*
+ Merge here with the saved parent's values
+ what is needed from the substatement gained
+ */
+ thd->transaction.stmt.modified_non_trans_table |= parent_modified_non_trans_table;
/*
Unlike for PS we should not call Item's destructors for newly created
items after execution of each instruction in stored routine. This is
diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc
index e49c4eb1240..ac7c46c9fe5 100644
--- a/sql/sp_rcontext.cc
+++ b/sql/sp_rcontext.cc
@@ -37,6 +37,7 @@ sp_rcontext::sp_rcontext(sp_pcontext *root_parsing_ctx,
m_var_items(0),
m_return_value_fld(return_value_fld),
m_return_value_set(FALSE),
+ in_sub_stmt(FALSE),
m_hcount(0),
m_hsp(0),
m_ihsp(0),
@@ -67,6 +68,8 @@ sp_rcontext::~sp_rcontext()
bool sp_rcontext::init(THD *thd)
{
+ in_sub_stmt= thd->in_sub_stmt;
+
if (init_var_table(thd) || init_var_items())
return TRUE;
@@ -191,7 +194,7 @@ sp_rcontext::set_return_value(THD *thd, Item **return_value_item)
*/
bool
-sp_rcontext::find_handler(uint sql_errno,
+sp_rcontext::find_handler(THD *thd, uint sql_errno,
MYSQL_ERROR::enum_warning_level level)
{
if (m_hfound >= 0)
@@ -200,6 +203,15 @@ sp_rcontext::find_handler(uint sql_errno,
const char *sqlstate= mysql_errno_to_sqlstate(sql_errno);
int i= m_hcount, found= -1;
+ /*
+ If this is a fatal sub-statement error, and this runtime
+ context corresponds to a sub-statement, no CONTINUE/EXIT
+ handlers from this context are applicable: try to locate one
+ in the outer scope.
+ */
+ if (thd->is_fatal_sub_stmt_error && in_sub_stmt)
+ i= 0;
+
/* Search handlers from the latest (innermost) to the oldest (outermost) */
while (i--)
{
@@ -252,7 +264,7 @@ sp_rcontext::find_handler(uint sql_errno,
*/
if (m_prev_runtime_ctx && IS_EXCEPTION_CONDITION(sqlstate) &&
level == MYSQL_ERROR::WARN_LEVEL_ERROR)
- return m_prev_runtime_ctx->find_handler(sql_errno, level);
+ return m_prev_runtime_ctx->find_handler(thd, sql_errno, level);
return FALSE;
}
m_hfound= found;
@@ -298,7 +310,7 @@ sp_rcontext::handle_error(uint sql_errno,
elevated_level= MYSQL_ERROR::WARN_LEVEL_ERROR;
}
- if (find_handler(sql_errno, elevated_level))
+ if (find_handler(thd, sql_errno, elevated_level))
{
if (elevated_level == MYSQL_ERROR::WARN_LEVEL_ERROR)
{
diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h
index fbf479f52aa..0104b71a648 100644
--- a/sql/sp_rcontext.h
+++ b/sql/sp_rcontext.h
@@ -125,7 +125,7 @@ class sp_rcontext : public Sql_alloc
// Returns 1 if a handler was found, 0 otherwise.
bool
- find_handler(uint sql_errno,MYSQL_ERROR::enum_warning_level level);
+ find_handler(THD *thd, uint sql_errno,MYSQL_ERROR::enum_warning_level level);
// If there is an error handler for this error, handle it and return TRUE.
bool
@@ -236,6 +236,10 @@ private:
during execution.
*/
bool m_return_value_set;
+ /**
+ TRUE if the context is created for a sub-statement.
+ */
+ bool in_sub_stmt;
sp_handler_t *m_handler; // Visible handlers
uint m_hcount; // Stack pointer for m_handler
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index bf1c6d7cb0d..1c01248c283 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -62,11 +62,11 @@ Prelock_error_handler::handle_error(uint sql_errno,
if (sql_errno == ER_NO_SUCH_TABLE)
{
m_handled_errors++;
- return TRUE; // 'TRUE', as per coding style
+ return TRUE;
}
m_unhandled_errors++;
- return FALSE; // 'FALSE', as per coding style
+ return FALSE;
}
@@ -1037,6 +1037,31 @@ TABLE **find_temporary_table(THD *thd, const char *db, const char *table_name)
return 0; // Not a temporary table
}
+
+/**
+ Drop a temporary table.
+
+ Try to locate the table in the list of thd->temporary_tables.
+ If the table is found:
+ - if the table is in thd->locked_tables, unlock it and
+ remove it from the list of locked tables. Currently only transactional
+ temporary tables are present in the locked_tables list.
+ - Close the temporary table, remove its .FRM
+ - remove the table from the list of temporary tables
+
+ This function is used to drop user temporary tables, as well as
+ internal tables created in CREATE TEMPORARY TABLE ... SELECT
+ or ALTER TABLE. Even though part of the work done by this function
+ is redundant when the table is internal, as long as we
+ link both internal and user temporary tables into the same
+ thd->temporary_tables list, it's impossible to tell here whether
+ we're dealing with an internal or a user temporary table.
+
+ @retval TRUE the table was not found in the list of temporary tables
+ of this thread
+ @retval FALSE the table was found and dropped successfully.
+*/
+
bool close_temporary_table(THD *thd, const char *db, const char *table_name)
{
TABLE *table,**prev;
@@ -1045,6 +1070,11 @@ bool close_temporary_table(THD *thd, const char *db, const char *table_name)
return 1;
table= *prev;
*prev= table->next;
+ /*
+ If LOCK TABLES list is not empty and contains this table,
+ unlock the table and remove the table from this list.
+ */
+ mysql_lock_remove(thd, thd->locked_tables, table, FALSE);
close_temporary(table, 1);
if (thd->slave_thread)
--slave_open_temp_tables;
@@ -1120,7 +1150,7 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find)
!memcmp(list->s->table_cache_key, key, key_length))
{
if (thd->locked_tables)
- mysql_lock_remove(thd, thd->locked_tables,list);
+ mysql_lock_remove(thd, thd->locked_tables, list, TRUE);
VOID(hash_delete(&open_cache,(byte*) list)); // Close table
}
else
@@ -1151,6 +1181,8 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find)
dropped is already unlocked. In the former case it will
also remove lock on the table. But one should not rely on
this behaviour as it may change in future.
+ Currently, however, this function is never called for a
+ table that was locked with LOCK TABLES.
*/
void drop_open_table(THD *thd, TABLE *table, const char *db_name,
@@ -2099,7 +2131,7 @@ bool close_data_tables(THD *thd,const char *db, const char *table_name)
if (!strcmp(table->s->table_name, table_name) &&
!strcmp(table->s->db, db))
{
- mysql_lock_remove(thd, thd->locked_tables,table);
+ mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
table->file->close();
table->db_stat=0;
}
@@ -2239,7 +2271,7 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
instances of this table.
*/
mysql_lock_abort(thd, table);
- mysql_lock_remove(thd, thd->locked_tables, table);
+ mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
/*
We want to protect the table from concurrent DDL operations
(like RENAME TABLE) until we will re-open and re-lock it.
@@ -2343,7 +2375,7 @@ bool drop_locked_tables(THD *thd,const char *db, const char *table_name)
if (!strcmp(table->s->table_name, table_name) &&
!strcmp(table->s->db, db))
{
- mysql_lock_remove(thd, thd->locked_tables,table);
+ mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
VOID(hash_delete(&open_cache,(byte*) table));
found=1;
}
@@ -2674,7 +2706,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
for (tables= *start; tables ;tables= tables->next_global)
{
- safe_to_ignore_table= FALSE; // 'FALSE', as per coding style
+ safe_to_ignore_table= FALSE;
if (tables->lock_type == TL_WRITE_DEFAULT)
{
@@ -2938,13 +2970,6 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type)
if (table)
{
-#if defined( __WIN__) || defined(OS2)
- /* Win32 can't drop a file that is open */
- if (lock_type == TL_WRITE_ALLOW_READ)
- {
- lock_type= TL_WRITE;
- }
-#endif /* __WIN__ || OS2 */
table_list->lock_type= lock_type;
table_list->table= table;
table->grant= table_list->grant;
@@ -3435,7 +3460,7 @@ find_field_in_view(THD *thd, TABLE_LIST *table_list,
table_list->alias, name, item_name, (ulong) ref));
Field_iterator_view field_it;
field_it.set(table_list);
- Query_arena *arena, backup;
+ Query_arena *arena= 0, backup;
DBUG_ASSERT(table_list->schema_table_reformed ||
(ref != 0 && table_list->view != 0));
@@ -3444,14 +3469,14 @@ find_field_in_view(THD *thd, TABLE_LIST *table_list,
if (!my_strcasecmp(system_charset_info, field_it.name(), name))
{
// in PS use own arena or data will be freed after prepare
- if (register_tree_change)
+ if (register_tree_change && thd->stmt_arena->is_stmt_prepare_or_first_sp_execute())
arena= thd->activate_stmt_arena_if_needed(&backup);
/*
create_item() may, or may not create a new Item, depending on
the column reference. See create_view_field() for details.
*/
Item *item= field_it.create_item(thd);
- if (register_tree_change && arena)
+ if (arena)
thd->restore_active_arena(arena, &backup);
if (!item)
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index ee4e1ea149c..b67f63778dc 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -173,6 +173,7 @@ THD::THD()
Open_tables_state(refresh_version),
lock_id(&main_lock_id),
user_time(0), in_sub_stmt(0), global_read_lock(0), is_fatal_error(0),
+ transaction_rollback_request(0), is_fatal_sub_stmt_error(0),
rand_used(0), time_zone_used(0),
last_insert_id_used(0), last_insert_id_used_bin_log(0), insert_id_used(0),
clear_next_insert_id(0), in_lock_tables(0), bootstrap(0),
@@ -197,7 +198,7 @@ THD::THD()
count_cuted_fields= CHECK_FIELD_IGNORE;
killed= NOT_KILLED;
db_length= col_access=0;
- query_error= tmp_table_used= 0;
+ query_error= tmp_table_used= thread_specific_used= 0;
next_insert_id=last_insert_id=0;
hash_clear(&handler_tables_hash);
tmp_table=0;
@@ -339,7 +340,7 @@ void THD::init(void)
if (variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES)
server_status|= SERVER_STATUS_NO_BACKSLASH_ESCAPES;
options= thd_startup_options;
- no_trans_update.stmt= no_trans_update.all= FALSE;
+ transaction.all.modified_non_trans_table= transaction.stmt.modified_non_trans_table= FALSE;
open_options=ha_open_options;
update_lock_default= (variables.low_priority_updates ?
TL_WRITE_LOW_PRIORITY :
@@ -976,7 +977,7 @@ void select_send::abort()
{
DBUG_ENTER("select_send::abort");
if (status && thd->spcont &&
- thd->spcont->find_handler(thd->net.last_errno,
+ thd->spcont->find_handler(thd, thd->net.last_errno,
MYSQL_ERROR::WARN_LEVEL_ERROR))
{
/*
@@ -2211,6 +2212,13 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup)
limit_found_rows= backup->limit_found_rows;
sent_row_count= backup->sent_row_count;
client_capabilities= backup->client_capabilities;
+ /*
+ If we've left sub-statement mode, reset the fatal error flag.
+ Otherwise keep the current value, to propagate it up the sub-statement
+ stack.
+ */
+ if (!in_sub_stmt)
+ is_fatal_sub_stmt_error= FALSE;
if ((options & OPTION_BIN_LOG) && is_update_query(lex->sql_command))
mysql_bin_log.stop_union_events(this);
@@ -2224,6 +2232,18 @@ void THD::restore_sub_statement_state(Sub_statement_state *backup)
}
+/**
+ Mark transaction to rollback and mark error as fatal to a sub-statement.
+
+ @param thd Thread handle
+ @param all TRUE <=> rollback main transaction.
+*/
+
+void mark_transaction_to_rollback(THD *thd, bool all)
+{
+ thd->is_fatal_sub_stmt_error= TRUE;
+ thd->transaction_rollback_request= all;
+}
/***************************************************************************
Handling of XA id cacheing
***************************************************************************/
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 445a3ce437c..4fac86dc405 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -450,7 +450,7 @@ public:
Table_ident *table, List<key_part_spec> &ref_cols,
uint delete_opt_arg, uint update_opt_arg, uint match_opt_arg)
:Key(FOREIGN_KEY, name_arg, HA_KEY_ALG_UNDEF, 0, cols),
- ref_table(table), ref_columns(cols),
+ ref_table(table), ref_columns(ref_cols),
delete_opt(delete_opt_arg), update_opt(update_opt_arg),
match_opt(match_opt_arg)
{}
@@ -995,13 +995,25 @@ enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1,
class Open_tables_state
{
public:
- /*
- open_tables - list of regular tables in use by this thread
- temporary_tables - list of temp tables in use by this thread
- handler_tables - list of tables that were opened with HANDLER OPEN
- and are still in use by this thread
+ /**
+ List of regular tables in use by this thread. Contains temporary and
+ base tables that were opened with @see open_tables().
+ */
+ TABLE *open_tables;
+ /**
+ List of temporary tables used by this thread. Contains user-level
+ temporary tables, created with CREATE TEMPORARY TABLE, and
+ internal temporary tables, created, e.g., to resolve a SELECT,
+ or for an intermediate table used in ALTER.
+ XXX Why are internal temporary tables added to this list?
*/
- TABLE *open_tables, *temporary_tables, *handler_tables, *derived_tables;
+ TABLE *temporary_tables;
+ /**
+ List of tables that were opened with HANDLER OPEN and are
+ still in use by this thread.
+ */
+ TABLE *handler_tables;
+ TABLE *derived_tables;
/*
During a MySQL session, one can lock tables in two modes: automatic
or manual. In automatic mode all necessary tables are locked just before
@@ -1421,7 +1433,33 @@ public:
bool slave_thread, one_shot_set;
bool locked, some_tables_deleted;
bool last_cuted_field;
- bool no_errors, password, is_fatal_error;
+ bool no_errors, password;
+ /**
+ Set to TRUE if execution of the current compound statement
+ can not continue. In particular, disables activation of
+ CONTINUE or EXIT handlers of stored routines.
+ Reset in the end of processing of the current user request, in
+ @see mysql_reset_thd_for_next_command().
+ */
+ bool is_fatal_error;
+ /**
+ Set by a storage engine to request the entire
+ transaction (that possibly spans multiple engines) to
+ rollback. Reset in ha_rollback.
+ */
+ bool transaction_rollback_request;
+ /**
+ TRUE if we are in a sub-statement and the current error can
+ not be safely recovered until we left the sub-statement mode.
+ In particular, disables activation of CONTINUE and EXIT
+ handlers inside sub-statements. E.g. if it is a deadlock
+ error and requires a transaction-wide rollback, this flag is
+ raised (traditionally, MySQL first has to close all the reads
+ via @see handler::ha_index_or_rnd_end() and only then perform
+ the rollback).
+ Reset to FALSE when we leave the sub-statement mode.
+ */
+ bool is_fatal_sub_stmt_error;
bool query_start_used, rand_used, time_zone_used;
/*
@@ -1457,13 +1495,12 @@ public:
bool in_lock_tables;
bool query_error, bootstrap, cleanup_done;
bool tmp_table_used;
+
+ /** is set if some thread specific value(s) used in a statement. */
+ bool thread_specific_used;
bool charset_is_system_charset, charset_is_collation_connection;
bool charset_is_character_set_filesystem;
bool enable_slow_log; /* enable slow log for current statement */
- struct {
- bool all:1;
- bool stmt:1;
- } no_trans_update;
bool abort_on_warning;
bool got_warning; /* Set on call to push_warning() */
bool no_warnings_for_error; /* no warnings on call to my_error() */
@@ -1714,7 +1751,7 @@ public:
inline bool really_abort_on_warning()
{
return (abort_on_warning &&
- (!no_trans_update.stmt ||
+ (!transaction.stmt.modified_non_trans_table ||
(variables.sql_mode & MODE_STRICT_ALL_TABLES)));
}
void set_status_var_init();
@@ -2397,3 +2434,5 @@ public:
/* Functions in sql_class.cc */
void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var);
+void mark_transaction_to_rollback(THD *thd, bool all);
+
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index bccd4d4cafe..56edfa6c5b2 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -315,6 +315,9 @@ cleanup:
delete select;
transactional_table= table->file->has_transactions();
+ if (!transactional_table && deleted > 0)
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+
/* See similar binlogging code in sql_update.cc, for comments */
if ((error < 0) || (deleted && !transactional_table))
{
@@ -327,9 +330,10 @@ cleanup:
if (mysql_bin_log.write(&qinfo) && transactional_table)
error=1;
}
- if (!transactional_table)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
+ DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table);
free_underlaid_joins(thd, select_lex);
if (transactional_table)
{
@@ -642,20 +646,22 @@ bool multi_delete::send_data(List<Item> &values)
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, FALSE))
- DBUG_RETURN(1);
+ DBUG_RETURN(1);
table->status|= STATUS_DELETED;
if (!(error=table->file->delete_row(table->record[0])))
{
- deleted++;
+ deleted++;
+ if (!table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, FALSE))
- DBUG_RETURN(1);
+ DBUG_RETURN(1);
}
else
{
- table->file->print_error(error,MYF(0));
- DBUG_RETURN(1);
+ table->file->print_error(error,MYF(0));
+ DBUG_RETURN(1);
}
}
else
@@ -705,6 +711,7 @@ void multi_delete::send_error(uint errcode,const char *err)
error= 1;
send_eof();
}
+ DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table);
DBUG_VOID_RETURN;
}
@@ -732,6 +739,7 @@ int multi_delete::do_deletes()
for (; table_being_deleted;
table_being_deleted= table_being_deleted->next_local, counter++)
{
+ ha_rows last_deleted= deleted;
TABLE *table = table_being_deleted->table;
if (tempfiles[counter]->get(table))
{
@@ -769,6 +777,8 @@ int multi_delete::do_deletes()
break;
}
}
+ if (last_deleted != deleted && !table->file->has_transactions())
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
end_read_record(&info);
if (thd->killed && !local_error)
local_error= 1;
@@ -807,7 +817,6 @@ bool multi_delete::send_eof()
{
query_cache_invalidate3(thd, delete_tables, 1);
}
-
if ((local_error == 0) || (deleted && normal_tables))
{
if (mysql_bin_log.is_open())
@@ -819,9 +828,11 @@ bool multi_delete::send_eof()
if (mysql_bin_log.write(&qinfo) && !normal_tables)
local_error=1; // Log write failed: roll back the SQL statement
}
- if (!transactional_tables)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
+ DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table);
+
/* Commit or rollback the current SQL statement */
if (transactional_tables)
if (ha_autocommit_or_rollback(thd,local_error > 0))
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 73f8c5e4418..5edce08e481 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -560,6 +560,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
int error, res;
bool transactional_table, joins_freed= FALSE;
bool changed;
+ bool was_insert_delayed= (table_list->lock_type == TL_WRITE_DELAYED);
uint value_count;
ulong counter = 1;
ulonglong id;
@@ -732,7 +733,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if (lock_type != TL_WRITE_DELAYED && !thd->prelocked_mode)
table->file->start_bulk_insert(values_list.elements);
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!ignore && (thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES)));
@@ -859,14 +859,16 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
transactional_table= table->file->has_transactions();
- if ((changed= (info.copied || info.deleted || info.updated)))
+ if ((changed= (info.copied || info.deleted || info.updated)) ||
+ was_insert_delayed)
{
/*
Invalidate the table in the query cache if something changed.
For the transactional algorithm to work the invalidation must be
before binlog writing and ha_autocommit_or_rollback
*/
- query_cache_invalidate3(thd, table_list, 1);
+ if (changed)
+ query_cache_invalidate3(thd, table_list, 1);
if (error <= 0 || !transactional_table)
{
if (mysql_bin_log.is_open())
@@ -904,10 +906,12 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
if (mysql_bin_log.write(&qinfo) && transactional_table)
error=1;
}
- if (!transactional_table)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
}
+ DBUG_ASSERT(transactional_table || !changed ||
+ thd->transaction.stmt.modified_non_trans_table);
if (transactional_table)
error=ha_autocommit_or_rollback(thd,error);
@@ -1308,7 +1312,7 @@ static int last_uniq_key(TABLE *table,uint keynr)
then both on update triggers will work instead. Similarly both on
delete triggers will be invoked if we will delete conflicting records.
- Sets thd->no_trans_update.stmt to TRUE if table which is updated didn't have
+ Sets thd->transaction.stmt.modified_non_trans_table to TRUE if table which is updated didn't have
transactions.
RETURN VALUE
@@ -1475,7 +1479,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info)
goto err;
info->deleted++;
if (!table->file->has_transactions())
- thd->no_trans_update.stmt= TRUE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, TRUE))
@@ -1507,7 +1511,7 @@ ok_or_after_trg_err:
if (key)
my_safe_afree(key,table->s->max_unique_length,MAX_KEY_LENGTH);
if (!table->file->has_transactions())
- thd->no_trans_update.stmt= TRUE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
DBUG_RETURN(trg_error);
err:
@@ -1702,18 +1706,18 @@ 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;
- while ((tmp=it++))
+ Delayed_insert *di;
+ while ((di= it++))
{
- if (!strcmp(tmp->thd.db,table_list->db) &&
- !strcmp(table_list->table_name,tmp->table->s->table_name))
+ if (!strcmp(table_list->db, di->table_list.db) &&
+ !strcmp(table_list->table_name, di->table_list.table_name))
{
- tmp->lock();
+ di->lock();
break;
}
}
pthread_mutex_unlock(&LOCK_delayed_insert); // For unlink from list
- return tmp;
+ return di;
}
@@ -1739,21 +1743,41 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
Two latter cases indicate a request for lock upgrade.
XXX: why do we regard INSERT DELAYED into a view as an error and
- do not simply a lock upgrade?
+ do not simply perform a lock upgrade?
+
+ TODO: The approach with using two mutexes to work with the
+ delayed thread list -- LOCK_delayed_insert and
+ LOCK_delayed_create -- is redundant, and we only need one of
+ them to protect the list. The reason we have two locks is that
+ we do not want to block look-ups in the list while we're waiting
+ for the newly created thread to open the delayed table. However,
+ this wait itself is redundant -- we always call get_local_table
+ later on, and there wait again until the created thread acquires
+ a table lock.
+
+ As is redundant the concept of locks_in_memory, since we already
+ have another counter with similar semantics - tables_in_use,
+ both of them are devoted to counting the number of producers for
+ a given consumer (delayed insert thread), only at different
+ stages of producer-consumer relationship.
+
+ 'dead' and 'status' variables in Delayed_insert are redundant
+ too, since there is already 'di->thd.killed' and
+ di->stacked_inserts.
*/
static
bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
{
int error;
- Delayed_insert *tmp;
+ Delayed_insert *di;
DBUG_ENTER("delayed_get_table");
/* Must be set in the parser */
DBUG_ASSERT(table_list->db);
/* Find the thread which handles this table. */
- if (!(tmp=find_handler(thd,table_list)))
+ if (!(di= find_handler(thd, table_list)))
{
/*
No match. Create a new thread to handle the table, but
@@ -1767,9 +1791,9 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
The first search above was done without LOCK_delayed_create.
Another thread might have created the handler in between. Search again.
*/
- if (! (tmp= find_handler(thd, table_list)))
+ if (! (di= find_handler(thd, table_list)))
{
- if (!(tmp=new Delayed_insert()))
+ if (!(di= new Delayed_insert()))
{
my_error(ER_OUTOFMEMORY,MYF(0),sizeof(Delayed_insert));
thd->fatal_error();
@@ -1778,28 +1802,30 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
pthread_mutex_lock(&LOCK_thread_count);
thread_count++;
pthread_mutex_unlock(&LOCK_thread_count);
- tmp->thd.set_db(table_list->db, strlen(table_list->db));
- tmp->thd.query= my_strdup(table_list->table_name,MYF(MY_WME));
- if (tmp->thd.db == NULL || tmp->thd.query == NULL)
+ di->thd.set_db(table_list->db, strlen(table_list->db));
+ di->thd.query= my_strdup(table_list->table_name, MYF(MY_WME));
+ if (di->thd.db == NULL || di->thd.query == NULL)
{
/* The error is reported */
- delete tmp;
+ delete di;
thd->fatal_error();
goto end_create;
}
- tmp->table_list= *table_list; // Needed to open table
- tmp->table_list.alias= tmp->table_list.table_name= tmp->thd.query;
- tmp->lock();
- pthread_mutex_lock(&tmp->mutex);
- if ((error=pthread_create(&tmp->thd.real_id,&connection_attrib,
- handle_delayed_insert,(void*) tmp)))
+ di->table_list= *table_list; // Needed to open table
+ /* Replace volatile strings with local copies */
+ di->table_list.alias= di->table_list.table_name= di->thd.query;
+ di->table_list.db= di->thd.db;
+ di->lock();
+ pthread_mutex_lock(&di->mutex);
+ if ((error= pthread_create(&di->thd.real_id, &connection_attrib,
+ handle_delayed_insert, (void*) di)))
{
DBUG_PRINT("error",
("Can't create thread to handle delayed insert (error %d)",
error));
- pthread_mutex_unlock(&tmp->mutex);
- tmp->unlock();
- delete tmp;
+ pthread_mutex_unlock(&di->mutex);
+ di->unlock();
+ delete di;
my_error(ER_CANT_CREATE_THREAD, MYF(0), error);
thd->fatal_error();
goto end_create;
@@ -1807,15 +1833,15 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
/* Wait until table is open */
thd->proc_info="waiting for handler open";
- while (!tmp->thd.killed && !tmp->table && !thd->killed)
+ while (!di->thd.killed && !di->table && !thd->killed)
{
- pthread_cond_wait(&tmp->cond_client,&tmp->mutex);
+ pthread_cond_wait(&di->cond_client, &di->mutex);
}
- pthread_mutex_unlock(&tmp->mutex);
+ pthread_mutex_unlock(&di->mutex);
thd->proc_info="got old table";
- if (tmp->thd.killed)
+ if (di->thd.killed)
{
- if (tmp->thd.net.report_error)
+ if (di->thd.net.report_error)
{
/*
Copy the error message. Note that we don't treat fatal
@@ -1823,31 +1849,34 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
main thread. Use of my_message will enable stored
procedures continue handlers.
*/
- my_message(tmp->thd.net.last_errno, tmp->thd.net.last_error,
+ my_message(di->thd.net.last_errno, di->thd.net.last_error,
MYF(0));
}
- tmp->unlock();
+ di->unlock();
goto end_create;
}
if (thd->killed)
{
- tmp->unlock();
+ di->unlock();
goto end_create;
}
+ pthread_mutex_lock(&LOCK_delayed_insert);
+ delayed_threads.append(di);
+ pthread_mutex_unlock(&LOCK_delayed_insert);
}
pthread_mutex_unlock(&LOCK_delayed_create);
}
- pthread_mutex_lock(&tmp->mutex);
- table_list->table= tmp->get_local_table(thd);
- pthread_mutex_unlock(&tmp->mutex);
+ pthread_mutex_lock(&di->mutex);
+ table_list->table= di->get_local_table(thd);
+ pthread_mutex_unlock(&di->mutex);
if (table_list->table)
{
DBUG_ASSERT(thd->net.report_error == 0);
- thd->di=tmp;
+ thd->di= di;
}
/* Unlock the delayed insert object after its last access. */
- tmp->unlock();
+ di->unlock();
DBUG_RETURN(table_list->table == NULL);
end_create:
@@ -2077,26 +2106,26 @@ 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;
- while ((tmp=it++))
+ Delayed_insert *di;
+ while ((di= it++))
{
- tmp->thd.killed= THD::KILL_CONNECTION;
- if (tmp->thd.mysys_var)
+ di->thd.killed= THD::KILL_CONNECTION;
+ if (di->thd.mysys_var)
{
- pthread_mutex_lock(&tmp->thd.mysys_var->mutex);
- if (tmp->thd.mysys_var->current_cond)
+ pthread_mutex_lock(&di->thd.mysys_var->mutex);
+ if (di->thd.mysys_var->current_cond)
{
/*
We need the following test because the main mutex may be locked
in handle_delayed_insert()
*/
- if (&tmp->mutex != tmp->thd.mysys_var->current_mutex)
- pthread_mutex_lock(tmp->thd.mysys_var->current_mutex);
- pthread_cond_broadcast(tmp->thd.mysys_var->current_cond);
- if (&tmp->mutex != tmp->thd.mysys_var->current_mutex)
- pthread_mutex_unlock(tmp->thd.mysys_var->current_mutex);
+ if (&di->mutex != di->thd.mysys_var->current_mutex)
+ pthread_mutex_lock(di->thd.mysys_var->current_mutex);
+ pthread_cond_broadcast(di->thd.mysys_var->current_cond);
+ if (&di->mutex != di->thd.mysys_var->current_mutex)
+ pthread_mutex_unlock(di->thd.mysys_var->current_mutex);
}
- pthread_mutex_unlock(&tmp->thd.mysys_var->mutex);
+ pthread_mutex_unlock(&di->thd.mysys_var->mutex);
}
}
VOID(pthread_mutex_unlock(&LOCK_delayed_insert)); // For unlink from list
@@ -2176,11 +2205,6 @@ pthread_handler_t handle_delayed_insert(void *arg)
}
di->table->copy_blobs=1;
- /* One can now use this */
- pthread_mutex_lock(&LOCK_delayed_insert);
- delayed_threads.append(di);
- pthread_mutex_unlock(&LOCK_delayed_insert);
-
/* Tell client that the thread is initialized */
pthread_cond_signal(&di->cond_client);
@@ -2767,7 +2791,6 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
}
if (info.handle_duplicates == DUP_UPDATE)
table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!info.ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
@@ -2904,7 +2927,8 @@ void select_insert::send_error(uint errcode,const char *err)
bool select_insert::send_eof()
{
- int error,error2;
+ int error, error2;
+ bool changed, transactional_table= table->file->has_transactions();
DBUG_ENTER("select_insert::send_eof");
error= (!thd->prelocked_mode) ? table->file->end_bulk_insert():0;
@@ -2916,12 +2940,14 @@ bool select_insert::send_eof()
and ha_autocommit_or_rollback
*/
- if (info.copied || info.deleted || info.updated)
+ if (changed= (info.copied || info.deleted || info.updated))
{
query_cache_invalidate3(thd, table, 1);
- if (!(table->file->has_transactions() || table->s->tmp_table))
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
+ DBUG_ASSERT(transactional_table || !changed ||
+ thd->transaction.stmt.modified_non_trans_table);
if (last_insert_id)
thd->insert_id(info.copied ? last_insert_id : 0); // For binary log
@@ -2931,7 +2957,7 @@ bool select_insert::send_eof()
if (!error)
thd->clear_error();
Query_log_event qinfo(thd, thd->query, thd->query_length,
- table->file->has_transactions(), FALSE);
+ transactional_table, FALSE);
mysql_bin_log.write(&qinfo);
}
if ((error2=ha_autocommit_or_rollback(thd,error)) && ! error)
@@ -2957,6 +2983,7 @@ bool select_insert::send_eof()
void select_insert::abort()
{
+ bool changed, transactional_table;
DBUG_ENTER("select_insert::abort");
if (!table)
@@ -2967,6 +2994,7 @@ void select_insert::abort()
*/
DBUG_VOID_RETURN;
}
+ transactional_table= table->file->has_transactions();
if (!thd->prelocked_mode)
table->file->end_bulk_insert();
/*
@@ -2975,21 +3003,22 @@ void select_insert::abort()
error while inserting into a MyISAM table) we must write to the binlog (and
the error code will make the slave stop).
*/
- if ((info.copied || info.deleted || info.updated) &&
- !table->file->has_transactions())
+ if ((changed= info.copied || info.deleted || info.updated) &&
+ !transactional_table)
{
if (last_insert_id)
thd->insert_id(last_insert_id); // For binary log
if (mysql_bin_log.is_open())
{
Query_log_event qinfo(thd, thd->query, thd->query_length,
- table->file->has_transactions(), FALSE);
+ transactional_table, FALSE);
mysql_bin_log.write(&qinfo);
}
- if (!table->s->tmp_table)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
- if (info.copied || info.deleted || info.updated)
+ DBUG_ASSERT(transactional_table || !changed || thd->transaction.stmt.modified_non_trans_table);
+ if (changed)
{
query_cache_invalidate3(thd, table, 1);
}
@@ -3236,7 +3265,6 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE);
if (!thd->prelocked_mode)
table->file->start_bulk_insert((ha_rows) 0);
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!info.ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index c37d77345b6..a62d8b383a5 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -2035,12 +2035,129 @@ void st_select_lex_unit::set_limit(SELECT_LEX *sl)
/**
- Update the parsed tree with information about triggers that
- may be fired when executing this statement.
+ @brief Set the initial purpose of this TABLE_LIST object in the list of used
+ tables.
+
+ We need to track this information on table-by-table basis, since when this
+ table becomes an element of the pre-locked list, it's impossible to identify
+ which SQL sub-statement it has been originally used in.
+
+ E.g.:
+
+ User request: SELECT * FROM t1 WHERE f1();
+ FUNCTION f1(): DELETE FROM t2; RETURN 1;
+ BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a);
+
+ For this user request, the pre-locked list will contain t1, t2, t3
+ table elements, each needed for different DML.
+
+ The trigger event map is updated to reflect INSERT, UPDATE, DELETE,
+ REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE ..
+ REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE
+ clause.
*/
void st_lex::set_trg_event_type_for_tables()
{
+ uint8 new_trg_event_map= 0;
+
+ /*
+ Some auxiliary operations
+ (e.g. GRANT processing) create TABLE_LIST instances outside
+ the parser. Additionally, some commands (e.g. OPTIMIZE) change
+ the lock type for a table only after parsing is done. Luckily,
+ these do not fire triggers and do not need to pre-load them.
+ For these TABLE_LISTs set_trg_event_type is never called, and
+ trg_event_map is always empty. That means that the pre-locking
+ algorithm will ignore triggers defined on these tables, if
+ any, and the execution will either fail with an assert in
+ sql_trigger.cc or with an error that a used table was not
+ pre-locked, in case of a production build.
+
+ TODO: this usage pattern creates unnecessary module dependencies
+ and should be rewritten to go through the parser.
+ Table list instances created outside the parser in most cases
+ refer to mysql.* system tables. It is not allowed to have
+ a trigger on a system table, but keeping track of
+ initialization provides extra safety in case this limitation
+ is circumvented.
+ */
+
+ switch (sql_command) {
+ case SQLCOM_LOCK_TABLES:
+ /*
+ On a LOCK TABLE, all triggers must be pre-loaded for this TABLE_LIST
+ when opening an associated TABLE.
+ */
+ new_trg_event_map= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_INSERT)) |
+ static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE)) |
+ static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ /*
+ Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE
+ clause, it will be handled later in this method.
+ */
+ case SQLCOM_INSERT: /* fall through */
+ case SQLCOM_INSERT_SELECT:
+ /*
+ LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT
+ triggers.
+ If the statement also has REPLACE clause, it will be
+ handled later in this method.
+ */
+ case SQLCOM_LOAD: /* fall through */
+ /*
+ REPLACE is semantically equivalent to INSERT. In case
+ of a primary or unique key conflict, it deletes the old
+ record and inserts a new one. So we also may need to
+ fire ON DELETE triggers. This functionality is handled
+ later in this method.
+ */
+ case SQLCOM_REPLACE: /* fall through */
+ case SQLCOM_REPLACE_SELECT:
+ /*
+ CREATE TABLE ... SELECT defaults to INSERT if the table or
+ view already exists. REPLACE option of CREATE TABLE ...
+ REPLACE SELECT is handled later in this method.
+ */
+ case SQLCOM_CREATE_TABLE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_INSERT));
+ break;
+ /* Basic update and multi-update */
+ case SQLCOM_UPDATE: /* fall through */
+ case SQLCOM_UPDATE_MULTI:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE));
+ break;
+ /* Basic delete and multi-delete */
+ case SQLCOM_DELETE: /* fall through */
+ case SQLCOM_DELETE_MULTI:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ default:
+ break;
+ }
+
+ switch (duplicates) {
+ case DUP_UPDATE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_UPDATE));
+ break;
+ case DUP_REPLACE:
+ new_trg_event_map|= static_cast<uint8>
+ (1 << static_cast<int>(TRG_EVENT_DELETE));
+ break;
+ case DUP_ERROR:
+ default:
+ break;
+ }
+
+
/*
Do not iterate over sub-selects, only the tables in the outermost
SELECT_LEX can be modified, if any.
@@ -2049,7 +2166,17 @@ void st_lex::set_trg_event_type_for_tables()
while (tables)
{
- tables->set_trg_event_type(this);
+ /*
+ This is a fast check to filter out statements that do
+ not change data, or tables on the right side, in case of
+ INSERT .. SELECT, CREATE TABLE .. SELECT and so on.
+ Here we also filter out OPTIMIZE statement and non-updateable
+ views, for which lock_type is TL_UNLOCK or TL_READ after
+ parsing.
+ */
+ if (static_cast<int>(tables->lock_type) >=
+ static_cast<int>(TL_WRITE_ALLOW_WRITE))
+ tables->trg_event_map= new_trg_event_map;
tables= tables->next_local;
}
}
diff --git a/sql/sql_load.cc b/sql/sql_load.cc
index 7b1799baaad..55cbbf1c540 100644
--- a/sql/sql_load.cc
+++ b/sql/sql_load.cc
@@ -377,7 +377,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
table->file->start_bulk_insert((ha_rows) 0);
table->copy_blobs=1;
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= (!ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
@@ -411,7 +410,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
ha_autocommit_...
*/
query_cache_invalidate3(thd, table_list, 0);
-
if (error)
{
if (read_file_from_client)
@@ -466,8 +464,8 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
sprintf(name, ER(ER_LOAD_INFO), (ulong) info.records, (ulong) info.deleted,
(ulong) (info.records - info.copied), (ulong) thd->cuted_fields);
- if (!transactional_table)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
#ifndef EMBEDDED_LIBRARY
if (mysql_bin_log.is_open())
{
@@ -488,6 +486,8 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
/* ok to client sent only after binlog write and engine commit */
send_ok(thd, info.copied + info.deleted, 0L, name);
err:
+ DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) ||
+ thd->transaction.stmt.modified_non_trans_table);
if (thd->lock)
{
mysql_unlock_tables(thd, thd->lock);
@@ -532,7 +532,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
Item_field *sql_field;
TABLE *table= table_list->table;
ulonglong id;
- bool no_trans_update_stmt, err;
+ bool err;
DBUG_ENTER("read_fixed_length");
id= 0;
@@ -560,7 +560,6 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
#ifdef HAVE_purify
read_info.row_end[0]=0;
#endif
- no_trans_update_stmt= !table->file->has_transactions();
restore_record(table, s->default_values);
/*
@@ -630,7 +629,6 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
table->auto_increment_field_not_null= FALSE;
if (err)
DBUG_RETURN(1);
- thd->no_trans_update.stmt= no_trans_update_stmt;
/*
If auto_increment values are used, save the first one for
@@ -673,12 +671,11 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
TABLE *table= table_list->table;
uint enclosed_length;
ulonglong id;
- bool no_trans_update_stmt, err;
+ bool err;
DBUG_ENTER("read_sep_field");
enclosed_length=enclosed.length();
id= 0;
- no_trans_update_stmt= !table->file->has_transactions();
for (;;it.rewind())
{
@@ -821,7 +818,6 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
We don't need to reset auto-increment field since we are restoring
its default value at the beginning of each loop iteration.
*/
- thd->no_trans_update.stmt= no_trans_update_stmt;
if (read_info.next_line()) // Skip to next line
break;
if (read_info.line_cuted)
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index ae3bc0f5597..25ead88ac53 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -149,7 +149,7 @@ static bool end_active_trans(THD *thd)
if (ha_commit(thd))
error=1;
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
}
DBUG_RETURN(error);
}
@@ -173,7 +173,7 @@ static bool begin_trans(THD *thd)
else
{
LEX *lex= thd->lex;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->options|= (ulong) OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT)
@@ -1471,7 +1471,7 @@ int end_trans(THD *thd, enum enum_mysql_completiontype completion)
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
res= ha_commit(thd);
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
break;
case COMMIT_RELEASE:
do_release= 1; /* fall through */
@@ -1489,7 +1489,7 @@ int end_trans(THD *thd, enum enum_mysql_completiontype completion)
if (ha_rollback(thd))
res= -1;
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
if (!res && (completion == ROLLBACK_AND_CHAIN))
res= begin_trans(thd);
break;
@@ -2600,6 +2600,8 @@ mysql_execute_command(THD *thd)
statistic_increment(thd->status_var.com_stat[lex->sql_command],
&LOCK_status);
+ DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE);
+
switch (lex->sql_command) {
case SQLCOM_SELECT:
{
@@ -2937,7 +2939,7 @@ mysql_execute_command(THD *thd)
else
{
/* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */
- thd->no_trans_update.all= TRUE;
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
DBUG_ASSERT(first_table == all_tables && first_table != 0);
bool link_to_local;
@@ -3720,7 +3722,7 @@ end_with_restore_list:
lex->drop_if_exists= 1;
/* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */
- thd->no_trans_update.all= TRUE;
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
/* DDL and binlog write order protected by LOCK_open */
res= mysql_rm_table(thd, first_table, lex->drop_if_exists,
@@ -4322,7 +4324,7 @@ end_with_restore_list:
res= TRUE; // cannot happen
else
{
- if (thd->no_trans_update.all &&
+ if (thd->transaction.all.modified_non_trans_table &&
!thd->slave_thread)
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARNING_NOT_COMPLETE_ROLLBACK,
@@ -4969,7 +4971,7 @@ create_sp_error:
thd->transaction.xid_state.xa_state=XA_ACTIVE;
thd->transaction.xid_state.xid.set(thd->lex->xid);
xid_cache_insert(&thd->transaction.xid_state);
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->options|= (ulong) OPTION_BEGIN;
thd->server_status|= SERVER_STATUS_IN_TRANS;
send_ok(thd);
@@ -5064,7 +5066,7 @@ create_sp_error:
break;
}
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
@@ -5095,7 +5097,7 @@ create_sp_error:
else
send_ok(thd);
thd->options&= ~(ulong) OPTION_BEGIN;
- thd->no_trans_update.all= FALSE;
+ thd->transaction.all.modified_non_trans_table= FALSE;
thd->server_status&= ~SERVER_STATUS_IN_TRANS;
xid_cache_delete(&thd->transaction.xid_state);
thd->transaction.xid_state.xa_state=XA_NOTR;
@@ -5845,6 +5847,7 @@ void mysql_reset_thd_for_next_command(THD *thd)
SERVER_QUERY_NO_GOOD_INDEX_USED);
DBUG_ASSERT(thd->security_ctx== &thd->main_security_ctx);
thd->tmp_table_used= 0;
+ thd->thread_specific_used= FALSE;
if (!thd->in_sub_stmt)
{
if (opt_bin_log)
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index 89bdd22a3de..b852039b93f 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -1121,6 +1121,7 @@ JOIN::optimize()
order=0; // The output has only one row
simple_order=1;
select_distinct= 0; // No need in distinct for 1 row
+ group_optimized_away= 1;
}
calc_group_buffer(this, group_list);
@@ -2416,7 +2417,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds,
if ((table->s->system || table->file->records <= 1) && ! s->dependent &&
!(table->file->table_flags() & HA_NOT_EXACT_COUNT) &&
- !table->fulltext_searched)
+ !table->fulltext_searched && !join->no_const_tables)
{
set_position(join,const_count++,s,(KEYUSE*) 0);
}
@@ -11461,7 +11462,8 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (!join->first_record || end_of_records ||
(idx=test_if_group_changed(join->group_fields)) >= 0)
{
- if (join->first_record || (end_of_records && !join->group))
+ if (join->first_record ||
+ (end_of_records && !join->group && !join->group_optimized_away))
{
if (join->procedure)
join->procedure->end_group();
@@ -12009,6 +12011,7 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx,
key_part_end=key_part+table->key_info[idx].key_parts;
key_part_map const_key_parts=table->const_key_parts[idx];
int reverse=0;
+ my_bool on_primary_key= FALSE;
DBUG_ENTER("test_if_order_by_key");
for (; order ; order=order->next, const_key_parts>>=1)
@@ -12023,7 +12026,30 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx,
for (; const_key_parts & 1 ; const_key_parts>>= 1)
key_part++;
- if (key_part == key_part_end || key_part->field != field)
+ if (key_part == key_part_end)
+ {
+ /*
+ We are at the end of the key. Check if the engine has the primary
+ key as a suffix to the secondary keys. If it has continue to check
+ the primary key as a suffix.
+ */
+ if (!on_primary_key &&
+ (table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
+ table->s->primary_key != MAX_KEY)
+ {
+ on_primary_key= TRUE;
+ key_part= table->key_info[table->s->primary_key].key_part;
+ key_part_end=key_part+table->key_info[table->s->primary_key].key_parts;
+ const_key_parts=table->const_key_parts[table->s->primary_key];
+
+ for (; const_key_parts & 1 ; const_key_parts>>= 1)
+ key_part++;
+ }
+ else
+ DBUG_RETURN(0);
+ }
+
+ if (key_part->field != field)
DBUG_RETURN(0);
/* set flag to 1 if we can use read-next on key, else to -1 */
@@ -12034,7 +12060,8 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx,
reverse=flag; // Remember if reverse
key_part++;
}
- *used_key_parts= (uint) (key_part - table->key_info[idx].key_part);
+ *used_key_parts= on_primary_key ? table->key_info[idx].key_parts :
+ (uint) (key_part - table->key_info[idx].key_part);
if (reverse == -1 && !(table->file->index_flags(idx, *used_key_parts-1, 1) &
HA_READ_PREV))
reverse= 0; // Index can't be used
@@ -12738,7 +12765,7 @@ remove_duplicates(JOIN *join, TABLE *entry,List<Item> &fields, Item *having)
field_count++;
}
- if (!field_count && !(join->select_options & OPTION_FOUND_ROWS))
+ if (!field_count && !(join->select_options & OPTION_FOUND_ROWS) && !having)
{ // only const items with no OPTION_FOUND_ROWS
join->unit->select_limit_cnt= 1; // Only send first row
DBUG_RETURN(0);
diff --git a/sql/sql_select.h b/sql/sql_select.h
index 4f9f6e9ed48..d84fbcb8c2d 100644
--- a/sql/sql_select.h
+++ b/sql/sql_select.h
@@ -277,11 +277,27 @@ public:
SELECT_LEX_UNIT *unit;
// select that processed
SELECT_LEX *select_lex;
+ /*
+ TRUE <=> optimizer must not mark any table as a constant table.
+ This is needed for subqueries in form "a IN (SELECT .. UNION SELECT ..):
+ when we optimize the select that reads the results of the union from a
+ temporary table, we must not mark the temp. table as constant because
+ the number of rows in it may vary from one subquery execution to another.
+ */
+ bool no_const_tables;
JOIN *tmp_join; // copy of this JOIN to be used with temporary tables
ROLLUP rollup; // Used with rollup
bool select_distinct; // Set if SELECT DISTINCT
+ /*
+ If we have the GROUP BY statement in the query,
+ but the group_list was emptied by optimizer, this
+ flag is TRUE.
+ It happens when fields in the GROUP BY are from
+ constant table
+ */
+ bool group_optimized_away;
/*
simple_xxxxx is set if ORDER/GROUP BY doesn't include any references
@@ -390,6 +406,7 @@ public:
zero_result_cause= 0;
optimized= 0;
cond_equal= 0;
+ group_optimized_away= 0;
all_fields= fields_arg;
fields_list= fields_arg;
@@ -397,6 +414,8 @@ public:
tmp_table_param.init();
tmp_table_param.end_write_records= HA_POS_ERROR;
rollup.state= ROLLUP::STATE_NONE;
+
+ no_const_tables= FALSE;
}
int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num,
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index b91412390bc..05a847b3830 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -3211,7 +3211,7 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables,
Item *item;
Item_field *field;
/*
- chech that at least one coulmn in view is updatable
+ check that at least one column in view is updatable
*/
while ((item= it++))
{
@@ -3222,6 +3222,8 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables,
break;
}
}
+ if (updatable_view && !tables->view->can_be_merged())
+ updatable_view= 0;
}
if (updatable_view)
table->field[5]->store(STRING_WITH_LEN("YES"), cs);
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 6adb1872c17..18c58d3a36a 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -3793,11 +3793,9 @@ view_err:
{
VOID(pthread_mutex_lock(&LOCK_open));
wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- table->file->external_lock(thd, F_WRLCK);
+ VOID(pthread_mutex_unlock(&LOCK_open));
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
alter_info->keys_onoff);
- table->file->external_lock(thd, F_UNLCK);
- VOID(pthread_mutex_unlock(&LOCK_open));
error= ha_commit_stmt(thd);
if (ha_commit(thd))
error= 1;
@@ -3815,7 +3813,7 @@ view_err:
The following function call will free the new_table pointer,
in close_temporary_table(), so we can safely directly jump to err
*/
- close_temporary_table(thd,new_db,tmp_name);
+ close_temporary_table(thd, new_db, tmp_name);
goto err;
}
/* Close lock if this is a transactional table */
@@ -4088,14 +4086,13 @@ copy_data_between_tables(TABLE *from,TABLE *to,
if (!(copy= new Copy_field[to->s->fields]))
DBUG_RETURN(-1); /* purecov: inspected */
- if (to->file->external_lock(thd, F_WRLCK))
+ if (to->file->ha_external_lock(thd, F_WRLCK))
DBUG_RETURN(-1);
/* We need external lock before we can disable/enable keys */
alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff);
/* We can abort alter table for any table type */
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= !ignore && test(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES));
@@ -4240,7 +4237,7 @@ copy_data_between_tables(TABLE *from,TABLE *to,
free_io_cache(from);
*copied= found_count;
*deleted=delete_count;
- if (to->file->external_lock(thd,F_UNLCK))
+ if (to->file->ha_external_lock(thd,F_UNLCK))
error=1;
DBUG_RETURN(error > 0 ? -1 : 0);
}
diff --git a/sql/sql_union.cc b/sql/sql_union.cc
index 373b03d45e6..25a0540e4dd 100644
--- a/sql/sql_union.cc
+++ b/sql/sql_union.cc
@@ -545,6 +545,10 @@ bool st_select_lex_unit::exec()
/*
allocate JOIN for fake select only once (prevent
mysql_select automatic allocation)
+ TODO: The above is nonsense. mysql_select() will not allocate the
+ join if one already exists. There must be some other reason why we
+ don't let it allocate the join. Perhaps this is because we need
+ some special parameter values passed to join constructor?
*/
if (!(fake_select_lex->join= new JOIN(thd, item_list,
fake_select_lex->options, result)))
@@ -552,33 +556,52 @@ bool st_select_lex_unit::exec()
fake_select_lex->table_list.empty();
DBUG_RETURN(TRUE);
}
+ fake_select_lex->join->no_const_tables= TRUE;
/*
Fake st_select_lex should have item list for correctref_array
allocation.
*/
fake_select_lex->item_list= item_list;
+ saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array,
+ &result_table_list,
+ 0, item_list, NULL,
+ global_parameters->order_list.elements,
+ (ORDER*)global_parameters->order_list.first,
+ (ORDER*) NULL, NULL, (ORDER*) NULL,
+ fake_select_lex->options | SELECT_NO_UNLOCK,
+ result, this, fake_select_lex);
}
else
{
- JOIN_TAB *tab,*end;
- for (tab=join->join_tab, end=tab+join->tables ;
- tab && tab != end ;
- tab++)
- {
- delete tab->select;
- delete tab->quick;
- }
- join->init(thd, item_list, fake_select_lex->options, result);
+ if (describe)
+ {
+ /*
+ In EXPLAIN command, constant subqueries that do not use any
+ tables are executed two times:
+ - 1st time is a real evaluation to get the subquery value
+ - 2nd time is to produce EXPLAIN output rows.
+ 1st execution sets certain members (e.g. select_result) to perform
+ subquery execution rather than EXPLAIN line production. In order
+ to reset them back, we re-do all of the actions (yes it is ugly):
+ */
+ join->init(thd, item_list, fake_select_lex->options, result);
+ saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array,
+ &result_table_list,
+ 0, item_list, NULL,
+ global_parameters->order_list.elements,
+ (ORDER*)global_parameters->order_list.first,
+ (ORDER*) NULL, NULL, (ORDER*) NULL,
+ fake_select_lex->options | SELECT_NO_UNLOCK,
+ result, this, fake_select_lex);
+ }
+ else
+ {
+ join->examined_rows= 0;
+ saved_error= join->reinit();
+ join->exec();
+ }
}
- saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array,
- &result_table_list,
- 0, item_list, NULL,
- global_parameters->order_list.elements,
- (ORDER*)global_parameters->order_list.first,
- (ORDER*) NULL, NULL, (ORDER*) NULL,
- fake_select_lex->options | SELECT_NO_UNLOCK,
- result, this, fake_select_lex);
fake_select_lex->table_list.empty();
if (!saved_error)
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index f4239afc4cd..c78e246f518 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -430,7 +430,6 @@ int mysql_update(THD *thd,
query_id=thd->query_id;
transactional_table= table->file->has_transactions();
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= test(!ignore &&
(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
@@ -487,7 +486,6 @@ int mysql_update(THD *thd,
(byte*) table->record[0])))
{
updated++;
- thd->no_trans_update.stmt= !transactional_table;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
@@ -522,6 +520,10 @@ int mysql_update(THD *thd,
thd->row_count++;
}
+ if (!transactional_table && updated > 0)
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
+
+
/*
todo bug#27571: to avoid asynchronization of `error' and
`error_code' of binlog event constructor
@@ -589,9 +591,10 @@ int mysql_update(THD *thd,
if (mysql_bin_log.write(&qinfo) && transactional_table)
error=1; // Rollback update
}
- if (!transactional_table)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
+ DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table);
free_underlaid_joins(thd, select_lex);
if (transactional_table)
{
@@ -955,7 +958,6 @@ bool mysql_multi_update(THD *thd,
handle_duplicates, ignore)))
DBUG_RETURN(TRUE);
- thd->no_trans_update.stmt= FALSE;
thd->abort_on_warning= test(thd->variables.sql_mode &
(MODE_STRICT_TRANS_TABLES |
MODE_STRICT_ALL_TABLES));
@@ -1331,9 +1333,8 @@ multi_update::~multi_update()
if (copy_field)
delete [] copy_field;
thd->count_cuted_fields= CHECK_FIELD_IGNORE; // Restore this setting
- if (!trans_safe) // todo: remove since redundant
- thd->no_trans_update.all= TRUE;
- DBUG_ASSERT(trans_safe || thd->no_trans_update.all);
+ DBUG_ASSERT(trans_safe || !updated ||
+ thd->transaction.all.modified_non_trans_table);
}
@@ -1426,7 +1427,7 @@ bool multi_update::send_data(List<Item> &not_used_values)
else
{
trans_safe= 0;
- thd->no_trans_update.stmt= TRUE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
}
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
@@ -1489,7 +1490,6 @@ void multi_update::send_error(uint errcode,const char *err)
/* Something already updated so we have to invalidate cache */
query_cache_invalidate3(thd, update_tables, 1);
-
/*
If all tables that has been updated are trans safe then just do rollback.
If not attempt to do remaining updates.
@@ -1502,7 +1502,7 @@ void multi_update::send_error(uint errcode,const char *err)
}
else
{
- DBUG_ASSERT(thd->no_trans_update.stmt);
+ DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table);
if (do_update && table_count > 1)
{
/* Add warning here */
@@ -1513,7 +1513,7 @@ void multi_update::send_error(uint errcode,const char *err)
VOID(do_updates(0));
}
}
- if (thd->no_trans_update.stmt)
+ if (thd->transaction.stmt.modified_non_trans_table)
{
/*
The query has to binlog because there's a modified non-transactional table
@@ -1526,9 +1526,9 @@ void multi_update::send_error(uint errcode,const char *err)
mysql_bin_log.write(&qinfo);
}
if (!trans_safe)
- thd->no_trans_update.all= TRUE;
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
- DBUG_ASSERT(trans_safe || !updated || thd->no_trans_update.stmt);
+ DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table);
if (transactional_tables)
{
@@ -1664,7 +1664,7 @@ int multi_update::do_updates(bool from_send_error)
else
{
trans_safe= 0; // Can't do safe rollback
- thd->no_trans_update.stmt= TRUE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
}
}
(void) table->file->ha_rnd_end();
@@ -1697,7 +1697,7 @@ err2:
else
{
trans_safe= 0;
- thd->no_trans_update.stmt= TRUE;
+ thd->transaction.stmt.modified_non_trans_table= TRUE;
}
}
DBUG_RETURN(1);
@@ -1722,7 +1722,6 @@ bool multi_update::send_eof()
{
query_cache_invalidate3(thd, update_tables, 1);
}
-
/*
Write the SQL statement to the binlog if we updated
rows and we succeeded or if we updated some non
@@ -1732,8 +1731,9 @@ bool multi_update::send_eof()
either from the query's list or via a stored routine: bug#13270,23333
*/
- DBUG_ASSERT(trans_safe || !updated || thd->no_trans_update.stmt);
- if (local_error == 0 || thd->no_trans_update.stmt)
+ DBUG_ASSERT(trans_safe || !updated ||
+ thd->transaction.stmt.modified_non_trans_table);
+ if (local_error == 0 || thd->transaction.stmt.modified_non_trans_table)
{
if (mysql_bin_log.is_open())
{
@@ -1746,8 +1746,8 @@ bool multi_update::send_eof()
if (mysql_bin_log.write(&qinfo) && trans_safe)
local_error= 1; // Rollback update
}
- if (!trans_safe)
- thd->no_trans_update.all= TRUE;
+ if (thd->transaction.stmt.modified_non_trans_table)
+ thd->transaction.all.modified_non_trans_table= TRUE;
}
if (transactional_tables)
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 6fbd521e302..638da3b1bb0 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -5567,7 +5567,7 @@ join_table:
so that [INNER | CROSS] JOIN is properly nested as other
left-associative joins.
*/
- table_ref %prec TABLE_REF_PRIORITY normal_join table_ref
+ table_ref normal_join table_ref %prec TABLE_REF_PRIORITY
{ MYSQL_YYABORT_UNLESS($1 && ($$=$3)); }
| table_ref STRAIGHT_JOIN table_factor
{ MYSQL_YYABORT_UNLESS($1 && ($$=$3)); $3->straight=1; }
@@ -7708,7 +7708,8 @@ simple_ident:
Item_splocal *splocal;
splocal= new Item_splocal($1, spv->offset, spv->type,
lip->tok_start_prev -
- lex->sphead->m_tmp_query);
+ lex->sphead->m_tmp_query,
+ lip->tok_end - lip->tok_start_prev);
#ifndef DBUG_OFF
if (splocal)
splocal->m_sp= lex->sphead;
diff --git a/sql/table.cc b/sql/table.cc
index f24f5c6fbcc..a393f1a676b 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -780,7 +780,11 @@ int openfrm(THD *thd, const char *name, const char *alias, uint db_stat,
the primary key, then we can use any key to find this column
*/
if (ha_option & HA_PRIMARY_KEY_IN_READ_INDEX)
+ {
field->part_of_key= share->keys_in_use;
+ if (field->part_of_sortkey.is_set(key))
+ field->part_of_sortkey= share->keys_in_use;
+ }
}
if (field->key_length() != key_part->length)
{
@@ -1776,135 +1780,6 @@ void st_table::reset_item_list(List<Item> *item_list) const
}
}
-
-/**
- Set the initial purpose of this TABLE_LIST object in the list of
- used tables. We need to track this information on table-by-
- table basis, since when this table becomes an element of the
- pre-locked list, it's impossible to identify which SQL
- sub-statement it has been originally used in.
-
- E.g.:
-
- User request: SELECT * FROM t1 WHERE f1();
- FUNCTION f1(): DELETE FROM t2; RETURN 1;
- BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a);
-
- For this user request, the pre-locked list will contain t1, t2, t3
- table elements, each needed for different DML.
-
- This method is called immediately after parsing for tables
- of the table list of the top-level select lex.
-
- The trigger event map is updated to reflect INSERT, UPDATE, DELETE,
- REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE ..
- REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE
- clause.
-*/
-
-void
-TABLE_LIST::set_trg_event_type(const st_lex *lex)
-{
- enum trg_event_type trg_event;
-
- /*
- Some auxiliary operations
- (e.g. GRANT processing) create TABLE_LIST instances outside
- the parser. Additionally, some commands (e.g. OPTIMIZE) change
- the lock type for a table only after parsing is done. Luckily,
- these do not fire triggers and do not need to pre-load them.
- For these TABLE_LISTs set_trg_event_type is never called, and
- trg_event_map is always empty. That means that the pre-locking
- algorithm will ignore triggers defined on these tables, if
- any, and the execution will either fail with an assert in
- sql_trigger.cc or with an error that a used table was not
- pre-locked, in case of a production build.
-
- TODO: this usage pattern creates unnecessary module dependencies
- and should be rewritten to go through the parser.
- Table list instances created outside the parser in most cases
- refer to mysql.* system tables. It is not allowed to have
- a trigger on a system table, but keeping track of
- initialization provides extra safety in case this limitation
- is circumvented.
- */
-
- /*
- This is a fast check to filter out statements that do
- not change data, or tables on the right side, in case of
- INSERT .. SELECT, CREATE TABLE .. SELECT and so on.
- Here we also filter out OPTIMIZE statement and non-updateable
- views, for which lock_type is TL_UNLOCK or TL_READ after
- parsing.
- */
- if (static_cast<int>(lock_type) < static_cast<int>(TL_WRITE_ALLOW_WRITE))
- return;
-
- switch (lex->sql_command) {
- /*
- Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE
- clause, it will be handled later in this method.
- */
- case SQLCOM_INSERT: /* fall through */
- case SQLCOM_INSERT_SELECT:
- /*
- LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT
- triggers.
- If the statement also has REPLACE clause, it will be
- handled later in this method.
- */
- case SQLCOM_LOAD: /* fall through */
- /*
- REPLACE is semantically equivalent to INSERT. In case
- of a primary or unique key conflict, it deletes the old
- record and inserts a new one. So we also may need to
- fire ON DELETE triggers. This functionality is handled
- later in this method.
- */
- case SQLCOM_REPLACE: /* fall through */
- case SQLCOM_REPLACE_SELECT:
- /*
- CREATE TABLE ... SELECT defaults to INSERT if the table or
- view already exists. REPLACE option of CREATE TABLE ...
- REPLACE SELECT is handled later in this method.
- */
- case SQLCOM_CREATE_TABLE:
- trg_event= TRG_EVENT_INSERT;
- break;
- /* Basic update and multi-update */
- case SQLCOM_UPDATE: /* fall through */
- case SQLCOM_UPDATE_MULTI:
- trg_event= TRG_EVENT_UPDATE;
- break;
- /* Basic delete and multi-delete */
- case SQLCOM_DELETE: /* fall through */
- case SQLCOM_DELETE_MULTI:
- trg_event= TRG_EVENT_DELETE;
- break;
- default:
- /*
- OK to return, since value of 'duplicates' is irrelevant
- for non-updating commands.
- */
- return;
- }
- trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event));
-
- switch (lex->duplicates) {
- case DUP_UPDATE:
- trg_event= TRG_EVENT_UPDATE;
- break;
- case DUP_REPLACE:
- trg_event= TRG_EVENT_DELETE;
- break;
- case DUP_ERROR:
- default:
- return;
- }
- trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event));
-}
-
-
/*
calculate md5 of query
diff --git a/sql/table.h b/sql/table.h
index f8f7d7f06b7..f411ce489c4 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -770,7 +770,6 @@ struct TABLE_LIST
void reinit_before_use(THD *thd);
Item_subselect *containing_subselect();
- void set_trg_event_type(const st_lex *lex);
private:
bool prep_check_option(THD *thd, uint8 check_opt_type);
bool prep_where(THD *thd, Item **conds, bool no_where_clause);