diff options
Diffstat (limited to 'sql')
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> ¬_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); |