diff --git a/.gitignore b/.gitignore index 00157d50d69..485b2d186cf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,11 @@ .ninja_* *.mri *.mri.tpl +/.cproject +/.project .gdb_history .vs/ +/.settings/ errmsg.sys typescript _CPack_Packages diff --git a/CMakeLists.txt b/CMakeLists.txt index 00007f92f57..75fcb311ddd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,10 @@ IF(DISABLE_SHARED) SET(WITHOUT_DYNAMIC_PLUGINS 1) ENDIF() OPTION(ENABLED_PROFILING "Enable profiling" ON) +OPTION(ENABLED_JSON_WRITER_CONSISTENCY_CHECKS "Enable Json_writer_object / Json_writer_array checking to produce consistent JSON output" OFF) +IF(ENABLED_JSON_WRITER_CONSISTENCY_CHECKS) + ADD_DEFINITIONS(-DENABLED_JSON_WRITER_CONSISTENCY_CHECKS) +ENDIF() OPTION(WITHOUT_SERVER "Build only the client library and clients" OFF) IF(UNIX) OPTION(WITH_VALGRIND "Valgrind instrumentation" OFF) diff --git a/cmake/pcre.cmake b/cmake/pcre.cmake index 3d4f163fab9..5ea81f53828 100644 --- a/cmake/pcre.cmake +++ b/cmake/pcre.cmake @@ -11,21 +11,22 @@ MACRO(BUNDLE_PCRE2) FOREACH(lib pcre2-posix pcre2-8) ADD_LIBRARY(${lib} STATIC IMPORTED GLOBAL) ADD_DEPENDENCIES(${lib} pcre2) + + GET_PROPERTY(MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + IF(MULTICONFIG) + SET(intdir "${CMAKE_CFG_INTDIR}/") + ELSE() + SET(intdir) + ENDIF() + + SET(file ${dir}/src/pcre2-build/${intdir}${CMAKE_STATIC_LIBRARY_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX}) + IF(WIN32) # Debug libary name. # Same condition as in pcre2 CMakeLists.txt that adds "d" - GET_PROPERTY(MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - IF(MULTICONFIG) - SET(intdir "${CMAKE_CFG_INTDIR}/") - ELSE() - SET(intdir) - ENDIF() - - SET(file ${dir}/src/pcre2-build/${intdir}${CMAKE_STATIC_LIBRARY_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX}) SET(file_d ${dir}/src/pcre2-build/${intdir}${CMAKE_STATIC_LIBRARY_PREFIX}${lib}d${CMAKE_STATIC_LIBRARY_SUFFIX}) SET_TARGET_PROPERTIES(${lib} PROPERTIES IMPORTED_LOCATION_DEBUG ${file_d}) ELSE() - SET(file ${dir}/src/pcre2-build/${CMAKE_STATIC_LIBRARY_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX}) SET(file_d) ENDIF() SET(byproducts ${byproducts} BUILD_BYPRODUCTS ${file} ${file_d}) diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl index 6dcd25a9c86..330c54c8e62 100755 --- a/mysql-test/mariadb-test-run.pl +++ b/mysql-test/mariadb-test-run.pl @@ -1271,6 +1271,7 @@ sub command_line_setup { { $path_client_bindir= mtr_path_exists("$bindir/client_release", "$bindir/client_debug", + "$bindir/client/debug", "$bindir/client$opt_vs_config", "$bindir/client", "$bindir/bin"); diff --git a/mysql-test/suite/galera_3nodes_sr/r/MDEV-26707.result b/mysql-test/suite/galera_3nodes_sr/r/MDEV-26707.result index a041274162f..8de724c1576 100644 --- a/mysql-test/suite/galera_3nodes_sr/r/MDEV-26707.result +++ b/mysql-test/suite/galera_3nodes_sr/r/MDEV-26707.result @@ -71,35 +71,16 @@ connection node_3a; SET SESSION wsrep_sync_wait = 0; SET SESSION wsrep_sync_wait = DEFAULT; connection node_1a; -Timeout in wait_condition.inc for SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log -Id User Host db Command Time State Info Progress -1 system user NULL Sleep 66 wsrep aborter idle NULL 0.000 -2 system user NULL Sleep 66 closing tables NULL 0.000 -10 root localhost test Sleep 58 NULL 0.000 -11 root localhost:52722 test Sleep 56 NULL 0.000 -12 root localhost:52724 test Query 0 starting show full processlist 0.000 SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; EXPECT_0 -1 +0 connection node_2a; -Timeout in wait_condition.inc for SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log -Id User Host db Command Time State Info Progress -1 system user NULL Sleep 96 wsrep aborter idle NULL 0.000 -2 system user NULL Sleep 87 closing tables NULL 0.000 -10 root localhost:37222 test Sleep 64 NULL 0.000 -11 root localhost:37228 test Query 0 starting show full processlist 0.000 SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; EXPECT_0 -1 +0 connection node_3a; -Timeout in wait_condition.inc for SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log -Id User Host db Command Time State Info Progress -1 system user NULL Sleep 122 wsrep aborter idle NULL 0.000 -2 system user NULL Sleep 117 closing tables NULL 0.000 -10 root localhost:60992 test Sleep 117 NULL 0.000 -11 root localhost:60994 test Query 0 starting show full processlist 0.000 SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; EXPECT_0 -1 +0 connection node_1; DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/rpl_xa.result b/mysql-test/suite/rpl/r/rpl_xa.result index a90e6e0b996..061c7b360d0 100644 --- a/mysql-test/suite/rpl/r/rpl_xa.result +++ b/mysql-test/suite/rpl/r/rpl_xa.result @@ -219,4 +219,65 @@ include/sync_with_master_gtid.inc connection master; drop database test_ign; drop table t1, t2, t3, tm; +# +# MDEV-26682 slave lock timeout with XA and gap locks +# +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +connection slave; +set session tx_isolation='repeatable-read'; +start transaction; +select * from t1; +a b +1 1 +3 3 +5 5 +connect m2, localhost, root; +delete from t1 where a=3; +xa start 'x1'; +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; +connect m3, localhost, root; +insert t1 values (2, 2); +-->slave +connection slave; +commit; +select * from t1; +a b +1 1 +2 2 +5 5 +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; +connection master; +drop table t1; +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; +connection master; +xa end '1'; +xa prepare '1'; +->slave +connection slave; +connection slave; +include/sync_with_master_gtid.inc +connection con1; +xa commit '2'; +disconnect con1; +connection master; +xa commit '1'; +drop table t2, t1; include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result index ffd0426ab0d..35625cc7026 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result +++ b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result @@ -228,6 +228,67 @@ include/sync_with_master_gtid.inc connection master; drop database test_ign; drop table t1, t2, t3, tm; +# +# MDEV-26682 slave lock timeout with XA and gap locks +# +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +connection slave; +set session tx_isolation='repeatable-read'; +start transaction; +select * from t1; +a b +1 1 +3 3 +5 5 +connect m2, localhost, root; +delete from t1 where a=3; +xa start 'x1'; +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; +connect m3, localhost, root; +insert t1 values (2, 2); +-->slave +connection slave; +commit; +select * from t1; +a b +1 1 +2 2 +5 5 +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; +connection master; +drop table t1; +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; +connection master; +xa end '1'; +xa prepare '1'; +->slave +connection slave; +connection slave; +include/sync_with_master_gtid.inc +connection con1; +xa commit '2'; +disconnect con1; +connection master; +xa commit '1'; +drop table t2, t1; connection slave; include/stop_slave.inc SET @@global.gtid_pos_auto_engines=""; diff --git a/mysql-test/suite/rpl/t/rpl_xa.inc b/mysql-test/suite/rpl/t/rpl_xa.inc index 38344da5e66..d22d2d2ef3d 100644 --- a/mysql-test/suite/rpl/t/rpl_xa.inc +++ b/mysql-test/suite/rpl/t/rpl_xa.inc @@ -1,6 +1,6 @@ # # This "body" file checks general properties of XA transaction replication -# as of MDEV-7974. +# as of MDEV-742. # Parameters: # --let rpl_xa_check= SELECT ... # @@ -353,3 +353,81 @@ source include/sync_with_master_gtid.inc; connection master; --eval drop database test_ign drop table t1, t2, t3, tm; + +--echo # +--echo # MDEV-26682 slave lock timeout with XA and gap locks +--echo # +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +sync_slave_with_master; + +# set a strong isolation level to keep the read view below. +# alternatively a long-running select can do that too even in read-committed +set session tx_isolation='repeatable-read'; +start transaction; +# opens a read view to disable purge on the slave +select * from t1; + +connect m2, localhost, root; +# now, delete a value, purge it on the master, but not on the slave +delete from t1 where a=3; +xa start 'x1'; +# this sets a gap lock on <3>, when it exists (so, on the slave) +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; + +connect m3, localhost, root; +# and this tries to insert straight into the locked gap +insert t1 values (2, 2); + +echo -->slave; +sync_slave_with_master; +commit; +select * from t1; + +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; + +connection master; + +drop table t1; + +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; + +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; + +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); + +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); + +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; + +connection master; +xa end '1'; +xa prepare '1'; + +echo ->slave; + +sync_slave_with_master; + +connection slave; +source include/sync_with_master_gtid.inc; + +connection con1; +xa commit '2'; +disconnect con1; + +connection master; +xa commit '1'; +drop table t2, t1; diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.result b/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.result index 5f7063b8f4b..a6911751747 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.result +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.result @@ -88,5 +88,32 @@ Warnings: Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = INET6'::ff' DROP TABLE t1; # +# MDEV-26742 Assertion `field->type_handler() == this' failed in FixedBinTypeBundle::Type_handler_fbt::stored_field_cmp_to_item +# +CREATE TABLE t1 (pk inet6, c text) engine=myisam; +INSERT INTO t1 VALUES ('::',1); +CREATE TABLE t2 (d text, KEY (d)) engine=innodb ; +Warnings: +Note 1071 Specified key was too long; max key length is 3072 bytes +INSERT INTO t2 VALUES (2); +SELECT * FROM t2 JOIN t1 ON ( t1.pk > t2.d); +d pk c +Warnings: +Warning 1292 Incorrect inet6 value: '2' +UPDATE t2 JOIN t1 ON ( t1.pk > t2.d) SET t1.c = 1; +ERROR 22007: Incorrect inet6 value: '2' +SET sql_mode=''; +UPDATE t2 JOIN t1 ON ( t1.pk > t2.d) SET t1.c = 1; +Warnings: +Warning 1292 Incorrect inet6 value: '2' +SET sql_mode=DEFAULT; +SELECT * FROM t1; +pk c +:: 1 +SELECT * FROM t2; +d +2 +DROP TABLE t1, t2; +# # End of 10.5 tests # diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.test b/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.test index dd6049abbf3..55826cc3e3f 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.test +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_innodb.test @@ -12,6 +12,24 @@ SET default_storage_engine=InnoDB; --source type_inet6_engines.inc +--echo # +--echo # MDEV-26742 Assertion `field->type_handler() == this' failed in FixedBinTypeBundle::Type_handler_fbt::stored_field_cmp_to_item +--echo # + +CREATE TABLE t1 (pk inet6, c text) engine=myisam; +INSERT INTO t1 VALUES ('::',1); +CREATE TABLE t2 (d text, KEY (d)) engine=innodb ; +INSERT INTO t2 VALUES (2); +SELECT * FROM t2 JOIN t1 ON ( t1.pk > t2.d); +--error ER_TRUNCATED_WRONG_VALUE +UPDATE t2 JOIN t1 ON ( t1.pk > t2.d) SET t1.c = 1; +SET sql_mode=''; +UPDATE t2 JOIN t1 ON ( t1.pk > t2.d) SET t1.c = 1; +SET sql_mode=DEFAULT; +SELECT * FROM t1; +SELECT * FROM t2; +DROP TABLE t1, t2; + --echo # --echo # End of 10.5 tests diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.result b/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.result index c8dba6ff959..ba65d61cb08 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.result +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.result @@ -88,5 +88,24 @@ Warnings: Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where `test`.`t1`.`a` = INET6'::ff' DROP TABLE t1; # +# MDEV-26742 Assertion `field->type_handler() == this' failed in FixedBinTypeBundle::Type_handler_fbt::stored_field_cmp_to_item +# +CREATE TABLE t1 (c varchar(64), key(c)) engine=myisam; +INSERT INTO t1 VALUES ('0::1'),('::1'),('::2'); +SELECT * FROM t1 WHERE c>CAST('::1' AS INET6); +c +::2 +EXPLAIN SELECT * FROM t1 WHERE c>CAST('::1' AS INET6); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index c c 67 NULL 3 Using where; Using index +SELECT * FROM t1 WHERE c=CAST('::1' AS INET6); +c +0::1 +::1 +EXPLAIN SELECT * FROM t1 WHERE c=CAST('::1' AS INET6); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index c c 67 NULL 3 Using where; Using index +DROP TABLE t1; +# # End of 10.5 tests # diff --git a/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.test b/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.test index c5183f01cf0..0ba8369ac95 100644 --- a/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.test +++ b/plugin/type_inet/mysql-test/type_inet/type_inet6_myisam.test @@ -10,6 +10,18 @@ SET default_storage_engine=MyISAM; --source type_inet6_engines.inc +--echo # +--echo # MDEV-26742 Assertion `field->type_handler() == this' failed in FixedBinTypeBundle::Type_handler_fbt::stored_field_cmp_to_item +--echo # + +CREATE TABLE t1 (c varchar(64), key(c)) engine=myisam; +INSERT INTO t1 VALUES ('0::1'),('::1'),('::2'); +SELECT * FROM t1 WHERE c>CAST('::1' AS INET6); +EXPLAIN SELECT * FROM t1 WHERE c>CAST('::1' AS INET6); +SELECT * FROM t1 WHERE c=CAST('::1' AS INET6); +EXPLAIN SELECT * FROM t1 WHERE c=CAST('::1' AS INET6); +DROP TABLE t1; + --echo # --echo # End of 10.5 tests diff --git a/sql/field.cc b/sql/field.cc index a5cd595f05b..63d39c77d95 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1276,6 +1276,21 @@ bool Field::can_be_substituted_to_equal_item(const Context &ctx, } +bool Field::cmp_is_done_using_type_handler_of_this(const Item_bool_func *cond, + const Item *item) const +{ + /* + We could eventually take comparison_type_handler() from cond, + instead of calculating it again. But only some descendants of + Item_bool_func has this method. So this needs some hierarchy changes. + Another option is to pass "class Context" to this method. + */ + Type_handler_hybrid_field_type cmp(type_handler_for_comparison()); + return !cmp.aggregate_for_comparison(item->type_handler_for_comparison()) && + cmp.type_handler() == type_handler_for_comparison(); +} + + /* This handles all numeric and BIT data types. */ @@ -7368,7 +7383,7 @@ bool Field_longstr::cmp_to_string_with_same_collation(const Item_bool_func *cond, const Item *item) const { - return item->cmp_type() == STRING_RESULT && + return cmp_is_done_using_type_handler_of_this(cond, item) && charset() == cond->compare_collation(); } @@ -7377,7 +7392,7 @@ bool Field_longstr::cmp_to_string_with_stricter_collation(const Item_bool_func *cond, const Item *item) const { - return item->cmp_type() == STRING_RESULT && + return cmp_is_done_using_type_handler_of_this(cond, item) && (charset() == cond->compare_collation() || cond->compare_collation()->state & MY_CS_BINSORT); } diff --git a/sql/field.h b/sql/field.h index 0408ae90b3f..0b8f317b5b8 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1651,6 +1651,8 @@ protected: } int warn_if_overflow(int op_result); Copy_func *get_identical_copy_func() const; + bool cmp_is_done_using_type_handler_of_this(const Item_bool_func *cond, + const Item *item) const; bool can_optimize_scalar_range(const RANGE_OPT_PARAM *param, const KEY_PART *key_part, const Item_bool_func *cond, diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc index a70ae164f18..55e71bc1b52 100644 --- a/sql/my_json_writer.cc +++ b/sql/my_json_writer.cc @@ -260,6 +260,10 @@ void Json_writer::add_str(const String &str) add_str(str.ptr(), str.length()); } +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS +thread_local std::vector Json_writer_struct::named_items_expectation; +#endif + Json_writer_temp_disable::Json_writer_temp_disable(THD *thd_arg) { thd= thd_arg; diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h index 27aec74d08d..d82313f996f 100644 --- a/sql/my_json_writer.h +++ b/sql/my_json_writer.h @@ -15,8 +15,12 @@ #ifndef JSON_WRITER_INCLUDED #define JSON_WRITER_INCLUDED + #include "my_base.h" #include "sql_select.h" + +#include + class Opt_trace_stmt; class Opt_trace_context; class Json_writer; @@ -308,6 +312,9 @@ public: /* A common base for Json_writer_object and Json_writer_array */ class Json_writer_struct { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + static thread_local std::vector named_items_expectation; +#endif protected: Json_writer* my_writer; Json_value_helper context; @@ -317,16 +324,35 @@ protected: bool closed; public: - explicit Json_writer_struct(THD *thd) + explicit Json_writer_struct(THD *thd, bool expect_named_children) { my_writer= thd->opt_trace.get_current_json(); context.init(my_writer); closed= false; +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + named_items_expectation.push_back(expect_named_children); +#endif } - bool trace_started() + + virtual ~Json_writer_struct() + { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + named_items_expectation.pop_back(); +#endif + } + + bool trace_started() const { return my_writer != 0; } + +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + bool named_item_expected() const + { + return named_items_expectation.size() > 1 + && *(named_items_expectation.rbegin() + 1); + } +#endif }; @@ -347,15 +373,21 @@ private: } public: explicit Json_writer_object(THD *thd) - : Json_writer_struct(thd) + : Json_writer_struct(thd, true) { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + DBUG_ASSERT(!named_item_expected()); +#endif if (unlikely(my_writer)) my_writer->start_object(); } explicit Json_writer_object(THD* thd, const char *str) - : Json_writer_struct(thd) + : Json_writer_struct(thd, true) { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + DBUG_ASSERT(named_item_expected()); +#endif if (unlikely(my_writer)) my_writer->add_member(str).start_object(); } @@ -519,14 +551,22 @@ public: class Json_writer_array : public Json_writer_struct { public: - Json_writer_array(THD *thd): Json_writer_struct(thd) + Json_writer_array(THD *thd) + : Json_writer_struct(thd, false) { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + DBUG_ASSERT(!named_item_expected()); +#endif if (unlikely(my_writer)) my_writer->start_array(); } - Json_writer_array(THD *thd, const char *str) : Json_writer_struct(thd) + Json_writer_array(THD *thd, const char *str) + : Json_writer_struct(thd, false) { +#ifdef ENABLED_JSON_WRITER_CONSISTENCY_CHECKS + DBUG_ASSERT(named_item_expected()); +#endif if (unlikely(my_writer)) my_writer->add_member(str).start_array(); } diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index c2fef3baaac..5f051b8ffbe 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -427,6 +427,10 @@ lock_rec_unlock( and release possible other transactions waiting because of these locks. */ void lock_release(trx_t* trx); +/** Release non-exclusive locks on XA PREPARE, +and release possible other transactions waiting because of these locks. */ +void lock_release_on_prepare(trx_t *trx); + /** Release locks on a table whose creation is being rolled back */ ATTRIBUTE_COLD void lock_release_on_rollback(trx_t *trx, dict_table_t *table); diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 47e30e075a0..dcef069f049 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -3883,6 +3883,124 @@ released: #endif } +/** Release non-exclusive locks on XA PREPARE, +and wake up possible other transactions waiting because of these locks. +@param trx transaction in XA PREPARE state +@return whether all locks were released */ +static bool lock_release_on_prepare_try(trx_t *trx) +{ + /* At this point, trx->lock.trx_locks can still be modified by other + threads to convert implicit exclusive locks into explicit ones. + + The function lock_table_create() should never be invoked on behalf + of a transaction that is running in another thread. Also there, we + will assert that the current transaction be active. */ + DBUG_ASSERT(trx->state == TRX_STATE_PREPARED); + + bool all_released= true; + lock_sys.rd_lock(SRW_LOCK_CALL); + trx->mutex_lock(); + + /* Note: Normally, trx->mutex is not held while acquiring + a lock table latch, but here we are following the opposite order. + To avoid deadlocks, we only try to acquire the lock table latches + but not keep waiting for them. */ + + for (lock_t *prev, *lock= UT_LIST_GET_LAST(trx->lock.trx_locks); lock; + lock= prev) + { + ut_ad(lock->trx == trx); + prev= UT_LIST_GET_PREV(trx_locks, lock); + if (!lock->is_table()) + { + ut_ad(!lock->index->table->is_temporary()); + if (lock->mode() == LOCK_X && !lock->is_gap()) + continue; + auto &lock_hash= lock_sys.hash_get(lock->type_mode); + auto cell= lock_hash.cell_get(lock->un_member.rec_lock.page_id.fold()); + auto latch= lock_sys_t::hash_table::latch(cell); + if (latch->try_acquire()) + { + lock_rec_dequeue_from_page(lock, false); + latch->release(); + } + else + all_released= false; + } + else + { + dict_table_t *table= lock->un_member.tab_lock.table; + ut_ad(!table->is_temporary()); + switch (lock->mode()) { + case LOCK_IS: + case LOCK_S: + if (table->lock_mutex_trylock()) + { + lock_table_dequeue(lock, false); + table->lock_mutex_unlock(); + } + else + all_released= false; + break; + case LOCK_IX: + case LOCK_X: + ut_ad(table->id >= DICT_HDR_FIRST_ID || trx->dict_operation); + /* fall through */ + default: + break; + } + } + } + + lock_sys.rd_unlock(); + trx->mutex_unlock(); + return all_released; +} + +/** Release non-exclusive locks on XA PREPARE, +and release possible other transactions waiting because of these locks. */ +void lock_release_on_prepare(trx_t *trx) +{ + for (ulint count= 5; count--; ) + if (lock_release_on_prepare_try(trx)) + return; + + LockMutexGuard g{SRW_LOCK_CALL}; + trx->mutex_lock(); + + for (lock_t *prev, *lock= UT_LIST_GET_LAST(trx->lock.trx_locks); lock; + lock= prev) + { + ut_ad(lock->trx == trx); + prev= UT_LIST_GET_PREV(trx_locks, lock); + if (!lock->is_table()) + { + ut_ad(!lock->index->table->is_temporary()); + if (lock->mode() != LOCK_X || lock->is_gap()) + lock_rec_dequeue_from_page(lock, false); + } + else + { + dict_table_t *table= lock->un_member.tab_lock.table; + ut_ad(!table->is_temporary()); + switch (lock->mode()) { + case LOCK_IS: + case LOCK_S: + lock_table_dequeue(lock, false); + break; + case LOCK_IX: + case LOCK_X: + ut_ad(table->id >= DICT_HDR_FIRST_ID || trx->dict_operation); + /* fall through */ + default: + break; + } + } + } + + trx->mutex_unlock(); +} + /** Release locks on a table whose creation is being rolled back */ ATTRIBUTE_COLD void lock_release_on_rollback(trx_t *trx, dict_table_t *table) { diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index ad49d3e9c8e..43ef05f4f2b 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -1868,6 +1868,20 @@ trx_prepare( We must not be holding any mutexes or latches here. */ trx_flush_log_if_needed(lsn, trx); + + if (!UT_LIST_GET_LEN(trx->lock.trx_locks) + || trx->isolation_level == TRX_ISO_SERIALIZABLE) { + /* Do not release any locks at the + SERIALIZABLE isolation level. */ + } else if (!trx->mysql_thd + || thd_sql_command(trx->mysql_thd) + != SQLCOM_XA_PREPARE) { + /* Do not release locks for XA COMMIT ONE PHASE + or for internal distributed transactions + (XID::get_my_xid() would be nonzero). */ + } else { + lock_release_on_prepare(trx); + } } } diff --git a/storage/spider/mysql-test/spider/bugfix/r/mdev_26539.result b/storage/spider/mysql-test/spider/bugfix/r/mdev_26539.result new file mode 100644 index 00000000000..4e195fddfad --- /dev/null +++ b/storage/spider/mysql-test/spider/bugfix/r/mdev_26539.result @@ -0,0 +1,36 @@ +for master_1 +for child2 +child2_1 +child2_2 +child2_3 +for child3 +# +# MDEV-26539 SIGSEGV in spider_check_and_set_trx_isolation and I_P_List_iterator from THD::drop_temporary_table (10.5.3 opt only) on ALTER +# +connection child2_1; +CREATE DATABASE auto_test_remote; +USE auto_test_remote; +CREATE TABLE tbl_a ( +c INT +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +connection master_1; +CREATE DATABASE auto_test_local; +USE auto_test_local; +CREATE TABLE tbl_a ( +c INT +) ENGINE=Spider DEFAULT CHARSET=utf8 COMMENT='table "tbl_a"' PARTITION BY LIST COLUMNS (c) ( +PARTITION pt1 DEFAULT COMMENT = 'srv "s_2_1"' +); +INSERT INTO tbl_a VALUES (1); +ALTER TABLE tbl_a CHECK PARTITION ALL; +Table Op Msg_type Msg_text +auto_test_local.tbl_a check status OK +DROP DATABASE auto_test_local; +connection child2_1; +DROP DATABASE auto_test_remote; +for master_1 +for child2 +child2_1 +child2_2 +child2_3 +for child3 diff --git a/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.cnf b/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.cnf new file mode 100644 index 00000000000..05dfd8a0bce --- /dev/null +++ b/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.cnf @@ -0,0 +1,3 @@ +!include include/default_mysqld.cnf +!include ../my_1_1.cnf +!include ../my_2_1.cnf diff --git a/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.test b/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.test new file mode 100644 index 00000000000..f2561f8c9a5 --- /dev/null +++ b/storage/spider/mysql-test/spider/bugfix/t/mdev_26539.test @@ -0,0 +1,40 @@ +--disable_query_log +--disable_result_log +--source ../../t/test_init.inc +--enable_result_log +--enable_query_log + +--echo # +--echo # MDEV-26539 SIGSEGV in spider_check_and_set_trx_isolation and I_P_List_iterator from THD::drop_temporary_table (10.5.3 opt only) on ALTER +--echo # + +--connection child2_1 +CREATE DATABASE auto_test_remote; +USE auto_test_remote; +eval CREATE TABLE tbl_a ( + c INT +) $CHILD2_1_ENGINE $CHILD2_1_CHARSET; + +--connection master_1 +CREATE DATABASE auto_test_local; +USE auto_test_local; + +eval CREATE TABLE tbl_a ( + c INT +) $MASTER_1_ENGINE $MASTER_1_CHARSET COMMENT='table "tbl_a"' PARTITION BY LIST COLUMNS (c) ( + PARTITION pt1 DEFAULT COMMENT = 'srv "s_2_1"' +); + +INSERT INTO tbl_a VALUES (1); +ALTER TABLE tbl_a CHECK PARTITION ALL; + +DROP DATABASE auto_test_local; + +--connection child2_1 +DROP DATABASE auto_test_remote; + +--disable_query_log +--disable_result_log +--source ../../t/test_deinit.inc +--enable_result_log +--enable_query_log diff --git a/storage/spider/spd_trx.cc b/storage/spider/spd_trx.cc index defa9be3f8c..c5095e7ff55 100644 --- a/storage/spider/spd_trx.cc +++ b/storage/spider/spd_trx.cc @@ -3802,10 +3802,8 @@ int spider_check_trx_and_get_conn( } spider->wide_handler->trx = trx; spider->set_error_mode(); - if ( - spider->wide_handler->sql_command != SQLCOM_DROP_TABLE && - spider->wide_handler->sql_command != SQLCOM_ALTER_TABLE - ) { + if (spider->wide_handler->sql_command != SQLCOM_DROP_TABLE) + { SPIDER_TRX_HA *trx_ha = spider_check_trx_ha(trx, spider); if (!trx_ha || trx_ha->wait_for_reusing) spider_trx_set_link_idx_for_all(spider);