diff --git a/include/ssl_compat.h b/include/ssl_compat.h index 6bec4f48285..200c4cbcf3d 100644 --- a/include/ssl_compat.h +++ b/include/ssl_compat.h @@ -24,12 +24,6 @@ #define HAVE_OPENSSL11 1 #define SSL_LIBRARY OpenSSL_version(OPENSSL_VERSION) #define ERR_remove_state(X) ERR_clear_error() -#define EVP_CIPHER_CTX_SIZE 200 -#define EVP_MD_CTX_SIZE 80 -#undef EVP_MD_CTX_init -#define EVP_MD_CTX_init(X) do { memset((X), 0, EVP_MD_CTX_SIZE); EVP_MD_CTX_reset(X); } while(0) -#undef EVP_CIPHER_CTX_init -#define EVP_CIPHER_CTX_init(X) do { memset((X), 0, EVP_CIPHER_CTX_SIZE); EVP_CIPHER_CTX_reset(X); } while(0) /* Macros below are deprecated. OpenSSL 1.1 may define them or not, @@ -66,24 +60,16 @@ #endif /* HAVE_OPENSSL11 */ #endif -#ifdef HAVE_WOLFSSL -#define EVP_MD_CTX_SIZE sizeof(wc_Md5) -#endif - #ifndef HAVE_OPENSSL11 #ifndef ASN1_STRING_get0_data #define ASN1_STRING_get0_data(X) ASN1_STRING_data(X) #endif -#ifndef EVP_MD_CTX_SIZE -#define EVP_MD_CTX_SIZE sizeof(EVP_MD_CTX) -#endif #ifndef DH_set0_pqg #define DH_set0_pqg(D,P,Q,G) ((D)->p= (P), (D)->g= (G)) #endif #define EVP_CIPHER_CTX_encrypting(ctx) ((ctx)->encrypt) -#define EVP_CIPHER_CTX_SIZE sizeof(EVP_CIPHER_CTX) #ifndef HAVE_WOLFSSL #define OPENSSL_init_ssl(X,Y) SSL_library_init() @@ -97,13 +83,3 @@ #ifndef TLS1_3_VERSION #define SSL_CTX_set_ciphersuites(X,Y) 0 #endif - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -int check_openssl_compatibility(); - -#ifdef __cplusplus -} -#endif diff --git a/mysql-test/main/events_debug.result b/mysql-test/main/events_debug.result new file mode 100644 index 00000000000..0d8c3a4e6dc --- /dev/null +++ b/mysql-test/main/events_debug.result @@ -0,0 +1,47 @@ +# +# MDEV-39146 Fix stack-use-after-return in PROCESSLIST for event threads +# +CREATE USER definer@localhost; +GRANT EVENT ON *.* TO definer@localhost; +GRANT SUPER ON *.* TO definer@localhost; +SET DEBUG_SYNC= 'RESET'; +# Per-session DEBUG_SYNC: must arm from inside the event body (its own +# thd), not from another connection. +CREATE DEFINER=definer@localhost EVENT ev +ON SCHEDULE AT CURRENT_TIMESTAMP +DO +BEGIN +SET DEBUG_SYNC= 'event_worker_thread_after_execute_returned SIGNAL event_execute_returned'; +SET DEBUG_SYNC= 'now SIGNAL definer_ctx_active WAIT_FOR event_may_finish'; +END| +Warnings: +Warning 1105 Event scheduler is switched off, use SET GLOBAL event_scheduler=ON to enable it. +SET GLOBAL event_scheduler= ON; +# Connect 'waiter' now: iterate() holds server_threads' rwlock for the +# whole scan; connecting after would deadlock (needs that lock's +# write side). +connect waiter, localhost, root,,; +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR definer_ctx_active'; +# Pause right after processlist_callback() captures the +# DEFINER-switched security_ctx, before USER/HOST are read. +SET @saved_dbug= @@SESSION.debug_dbug; +SET SESSION debug_dbug= '+d,processlist_callback_pause_after_sctx_capture'; +SET DEBUG_SYNC= 'processlist_callback_captured_sctx SIGNAL scanner_captured WAIT_FOR let_scanner_continue'; +# Filter by name: checks the value read through the captured pointer. +SELECT user, host FROM information_schema.processlist WHERE user = 'definer'; +connection waiter; +SET DEBUG_SYNC= 'now WAIT_FOR scanner_captured'; +# Let the event finish and execute() return. +SET DEBUG_SYNC= 'now SIGNAL event_may_finish WAIT_FOR event_execute_returned TIMEOUT 60'; +# Release the scanner to dereference the pointer it captured. +SET DEBUG_SYNC= 'now SIGNAL let_scanner_continue'; +disconnect waiter; +connection default; +user host +definer localhost +SET SESSION debug_dbug= @saved_dbug; +SET DEBUG_SYNC= 'RESET'; +SET GLOBAL event_scheduler= OFF; +DROP USER definer@localhost; +# End of 10.11 tests diff --git a/mysql-test/main/events_debug.test b/mysql-test/main/events_debug.test new file mode 100644 index 00000000000..66dfec1a293 --- /dev/null +++ b/mysql-test/main/events_debug.test @@ -0,0 +1,68 @@ +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/not_embedded.inc + +--echo # +--echo # MDEV-39146 Fix stack-use-after-return in PROCESSLIST for event threads +--echo # + +CREATE USER definer@localhost; +GRANT EVENT ON *.* TO definer@localhost; +GRANT SUPER ON *.* TO definer@localhost; + +SET DEBUG_SYNC= 'RESET'; + +--echo # Per-session DEBUG_SYNC: must arm from inside the event body (its own +--echo # thd), not from another connection. +--delimiter | +CREATE DEFINER=definer@localhost EVENT ev +ON SCHEDULE AT CURRENT_TIMESTAMP +DO +BEGIN + SET DEBUG_SYNC= 'event_worker_thread_after_execute_returned SIGNAL event_execute_returned'; + SET DEBUG_SYNC= 'now SIGNAL definer_ctx_active WAIT_FOR event_may_finish'; +END| +--delimiter ; + +SET GLOBAL event_scheduler= ON; + +--echo # Connect 'waiter' now: iterate() holds server_threads' rwlock for the +--echo # whole scan; connecting after would deadlock (needs that lock's +--echo # write side). +connect (waiter, localhost, root,,); +connection default; + +SET DEBUG_SYNC= 'now WAIT_FOR definer_ctx_active'; + +--echo # Pause right after processlist_callback() captures the +--echo # DEFINER-switched security_ctx, before USER/HOST are read. +SET @saved_dbug= @@SESSION.debug_dbug; +SET SESSION debug_dbug= '+d,processlist_callback_pause_after_sctx_capture'; +SET DEBUG_SYNC= 'processlist_callback_captured_sctx SIGNAL scanner_captured WAIT_FOR let_scanner_continue'; + +--echo # Filter by name: checks the value read through the captured pointer. +--send SELECT user, host FROM information_schema.processlist WHERE user = 'definer' + +connection waiter; +SET DEBUG_SYNC= 'now WAIT_FOR scanner_captured'; + +--echo # Let the event finish and execute() return. +SET DEBUG_SYNC= 'now SIGNAL event_may_finish WAIT_FOR event_execute_returned TIMEOUT 60'; + +--echo # Release the scanner to dereference the pointer it captured. +SET DEBUG_SYNC= 'now SIGNAL let_scanner_continue'; + +--disconnect waiter +--connection default +--reap + +SET SESSION debug_dbug= @saved_dbug; +SET DEBUG_SYNC= 'RESET'; + +SET GLOBAL event_scheduler= OFF; +--source include/check_events_off.inc +let $count_sessions= 1; +--source include/wait_until_count_sessions.inc +DROP USER definer@localhost; + +--echo # End of 10.11 tests diff --git a/mysql-test/main/grant2.result b/mysql-test/main/grant2.result index 7f6a057db27..2a0819c7c5f 100644 --- a/mysql-test/main/grant2.result +++ b/mysql-test/main/grant2.result @@ -1143,11 +1143,11 @@ connection con1; set time_zone= '+00:00'; select row_start, row_end from t; row_start row_end -2001-01-01 00:00:00.000000 2106-02-07 06:28:15.999999 +2001-01-01 00:00:00.000000 # handler t open; handler t read first; id a b row_start row_end DB_ROW_HASH_1 -1 10 x 2001-01-01 00:00:00.000000 2106-02-07 06:28:15.999999 # +1 10 x 2001-01-01 00:00:00.000000 # # handler t close; disconnect con1; connection default; diff --git a/mysql-test/main/grant2.test b/mysql-test/main/grant2.test index 30a67169b6b..70a1611a537 100644 --- a/mysql-test/main/grant2.test +++ b/mysql-test/main/grant2.test @@ -1179,9 +1179,10 @@ connection default; grant select (a) on db.t to u@localhost; connection con1; set time_zone= '+00:00'; +--replace_column 2 # select row_start, row_end from t; handler t open; ---replace_column 6 # +--replace_column 5 # 6 # handler t read first; handler t close; disconnect con1; diff --git a/mysql-test/main/group_by.result b/mysql-test/main/group_by.result index c9a937c14c8..b1bd1f900cb 100644 --- a/mysql-test/main/group_by.result +++ b/mysql-test/main/group_by.result @@ -3043,7 +3043,7 @@ EXPLAIN { "query_block": { "select_id": 1, - "pseudo_bits_condition": "if(uuid_short(),NULL,1)", + "outer_ref_condition": "if(uuid_short(),NULL,1)", "nested_loop": [ { "table": { @@ -3069,7 +3069,7 @@ EXPLAIN { "query_block": { "select_id": 1, - "pseudo_bits_condition": "10 = rand()", + "outer_ref_condition": "10 = rand()", "nested_loop": [ { "table": { diff --git a/mysql-test/main/olap.result b/mysql-test/main/olap.result index b8aea1c7c2b..6b143f9a067 100644 --- a/mysql-test/main/olap.result +++ b/mysql-test/main/olap.result @@ -921,3 +921,19 @@ MAX(c) DROP TABLE t1; # End of 10.11 tests ALTER DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci; +# +# MDEV-41146: WHERE is not checked when every table is const and +# the query needs a temporary table +# +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +# rand() < 0 is never true, so only the ROLLUP total row is returned. +select distinct a from t1 where a=1 and rand() < 0 group by a with rollup; +a +NULL +select distinct a from t1 where a=1 and rand() >= 0 group by a with rollup; +a +1 +NULL +drop table t1; +# End of 11.4 tests diff --git a/mysql-test/main/olap.test b/mysql-test/main/olap.test index 265303b452b..5b83d774246 100644 --- a/mysql-test/main/olap.test +++ b/mysql-test/main/olap.test @@ -546,4 +546,20 @@ SELECT MAX(c) FROM t1 GROUP BY c WITH ROLLUP limit 5; DROP TABLE t1; --echo # End of 10.11 tests ---source include/test_db_charset_restore.inc +ALTER DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci; + +--echo # +--echo # MDEV-41146: WHERE is not checked when every table is const and +--echo # the query needs a temporary table +--echo # + +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); + +--echo # rand() < 0 is never true, so only the ROLLUP total row is returned. +select distinct a from t1 where a=1 and rand() < 0 group by a with rollup; +select distinct a from t1 where a=1 and rand() >= 0 group by a with rollup; + +drop table t1; + +--echo # End of 11.4 tests diff --git a/mysql-test/main/partition_explicit_prune.result b/mysql-test/main/partition_explicit_prune.result index 11e0dc7c0bb..8bc7409ee7f 100644 --- a/mysql-test/main/partition_explicit_prune.result +++ b/mysql-test/main/partition_explicit_prune.result @@ -2007,6 +2007,29 @@ drop table t1; # End of 10.4 tests # # +# MDEV-17613 Read sole now-partition for non-historical versioned query +# +# INSERT ... SELECT from explicit PARTITION() without WHERE must not +# read partitions outside of it (read_partitions widened by auto-increment) +# +create table t1 (id int auto_increment primary key, x int) +partition by range (id) ( +partition p0 values less than (10), +partition p1 values less than maxvalue); +insert into t1 values (1, 1), (2, 2), (11, 11), (12, 12); +create table t2 (id int, x int); +# Fresh share: auto-increment is not initialized yet +flush tables; +insert into t2 select * from t1 partition (p0); +select * from t2; +id x +1 1 +2 2 +drop tables t1, t2; +# +# End of 10.11 tests +# +# # MDEV-33988 DELETE (single table) to support table aliases # CREATE TABLE `t1` ( diff --git a/mysql-test/main/partition_explicit_prune.test b/mysql-test/main/partition_explicit_prune.test index a3b74be58e0..aedbf4c42b9 100644 --- a/mysql-test/main/partition_explicit_prune.test +++ b/mysql-test/main/partition_explicit_prune.test @@ -942,6 +942,29 @@ drop table t1; --echo # End of 10.4 tests --echo # +--echo # +--echo # MDEV-17613 Read sole now-partition for non-historical versioned query +--echo # +--echo # INSERT ... SELECT from explicit PARTITION() without WHERE must not +--echo # read partitions outside of it (read_partitions widened by auto-increment) +--echo # +create table t1 (id int auto_increment primary key, x int) +partition by range (id) ( + partition p0 values less than (10), + partition p1 values less than maxvalue); +insert into t1 values (1, 1), (2, 2), (11, 11), (12, 12); +create table t2 (id int, x int); +--echo # Fresh share: auto-increment is not initialized yet +flush tables; +insert into t2 select * from t1 partition (p0); +--sorted_result +select * from t2; +drop tables t1, t2; + +--echo # +--echo # End of 10.11 tests +--echo # + --echo # --echo # MDEV-33988 DELETE (single table) to support table aliases --echo # diff --git a/mysql-test/main/ssl_crl.test b/mysql-test/main/ssl_crl.test index 6da116f4ebe..13958e33c1b 100644 --- a/mysql-test/main/ssl_crl.test +++ b/mysql-test/main/ssl_crl.test @@ -9,6 +9,6 @@ --echo # try logging in with a certificate in the server's --ssl-crl : should fail # OpenSSL 1.1.1a and later releases correctly rejects the certificate, but the error message is different ---replace_regex /(ERROR 2013 \(HY000\): Lost connection to server at '.*', system error: [0-9]+|ERROR 2026 \(HY000\): TLS\/SSL error: sslv3 alert certificate revoked)/ERROR 2026 (HY000): TLS\/SSL error: ssl\/tls alert certificate revoked/ +--replace_regex /(ERROR 2013 \(HY000\): Lost connection to server at '.*', system error: [0-9]+|ERROR 2026 \(HY000\): TLS\/SSL error: (sslv3|tls) alert certificate revoked)/ERROR 2026 (HY000): TLS\/SSL error: ssl\/tls alert certificate revoked/ --error 1 --exec $MYSQL --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem --ssl-key=$MYSQL_TEST_DIR/std_data/client-key.pem --ssl-cert=$MYSQL_TEST_DIR/std_data/client-cert.pem test -e "SHOW STATUS LIKE 'Ssl_version'" 2>&1 diff --git a/mysql-test/main/subselect_no_semijoin.result b/mysql-test/main/subselect_no_semijoin.result index 47dc85aea4b..d1ea2fba2c4 100644 --- a/mysql-test/main/subselect_no_semijoin.result +++ b/mysql-test/main/subselect_no_semijoin.result @@ -7671,7 +7671,7 @@ EXPLAIN { "query_block": { "select_id": 2, - "pseudo_bits_condition": "1 = t1.a or (1,(subquery#3))", + "outer_ref_condition": "1 = t1.a or (1,(subquery#3))", "nested_loop": [ { "table": { diff --git a/mysql-test/main/subselect_sj_mat_rand.opt b/mysql-test/main/subselect_sj_mat_rand.opt new file mode 100644 index 00000000000..1af9e30741b --- /dev/null +++ b/mysql-test/main/subselect_sj_mat_rand.opt @@ -0,0 +1 @@ +--memory.optimizer_row_copy_cost=0 diff --git a/mysql-test/main/subselect_sj_mat_rand.result b/mysql-test/main/subselect_sj_mat_rand.result new file mode 100644 index 00000000000..692d522e4c9 --- /dev/null +++ b/mysql-test/main/subselect_sj_mat_rand.result @@ -0,0 +1,421 @@ +# +# MDEV-41146: rand() in WHERE is not checked when every base table +# is const and a materialized semi-join terminates the join order +# +# A WHERE conjunct that calls rand() and names no field is checked on +# the last top level base table that is not const. When every base +# table is const, the conjunct is checked once in outer_ref_condition. +# +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +create table t2 (b int primary key); +insert into t2 values (1),(2),(3),(4); +create table t3 (c int, d int); +insert into t3 values (1,1),(2,2),(3,3),(4,4),(5,5), +(6,6),(7,7),(8,8),(9,9),(10,10); +insert into t3 select c+10, d+10 from t3; +insert into t3 select c+20, d+20 from t3; +insert into t3 select c+40, d+40 from t3; +create table t4 (e int, f int); +insert into t4 select c, d from t3; +create table t5 (g int); +insert into t5 select c+1000 from t3; +set @save_optimizer_switch= @@optimizer_switch; +set @save_optimizer_where_cost= @@optimizer_where_cost; +set optimizer_switch='firstmatch=off,loosescan=off'; +set optimizer_where_cost=0; +# One const table. rand() < 0 is never true, so the result is empty. +explain +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() < 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() < 0; +a +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() >= 0; +a +1 +# The IN-equality is still checked. t5 holds no value below 1001. +select t1.a from t1 where t1.a=1 and t1.a in (select g from t5) and rand() >= 0; +a +select t1.a from t1 where t1.a=1 and t1.a in (select g from t5) and rand() < 0; +a +# Two rand() conjuncts. outer_ref_condition shows both. +explain format=json +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() < 0 and rand() < 2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": "COST_REPLACED", + "outer_ref_condition": "rand() < 0 and rand() < 2", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "const", + "possible_keys": ["PRIMARY"], + "key": "PRIMARY", + "key_length": "4", + "used_key_parts": ["a"], + "ref": ["const"], + "rows": 1, + "filtered": 100, + "using_index": true + } + }, + { + "table": { + "table_name": "", + "access_type": "eq_ref", + "possible_keys": ["distinct_key"], + "key": "distinct_key", + "key_length": "4", + "used_key_parts": ["c"], + "ref": ["func"], + "rows": 1, + "filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "nested_loop": [ + { + "table": { + "table_name": "t3", + "access_type": "ALL", + "loops": 1, + "rows": 80, + "cost": "COST_REPLACED", + "filtered": 100, + "attached_condition": "t3.c = 1" + } + } + ] + } + } + } + } + ] + } +} +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() < 0 and rand() < 2; +a +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() >= 0 and rand() < 2; +a +1 +# Two const tables joined, as in the original report. +explain +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() < 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY t2 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() < 0; +x y +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() >= 0; +x y +1 1 +# Two materialized nests terminate the join order. +explain +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) +and rand() < 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +3 MATERIALIZED t4 ALL NULL NULL NULL NULL 80 Using where +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) +and rand() < 0; +a +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) +and rand() >= 0; +a +1 +# The const table column is bigint and the subquery column is int. +create table t6 (a bigint primary key); +insert into t6 values (1),(2),(3); +explain +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() < 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t6 const PRIMARY PRIMARY 8 const 1 Using index +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() < 0; +a +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() >= 0; +a +1 +# An outer table that is not const precedes the nest. The conjunct +# is checked on that table. +create table t7 (a int); +insert into t7 values (1),(2),(3),(100); +explain +select t7.a from t7 where t7.a in (select c from t3) and rand() < 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t7 ALL NULL NULL NULL NULL 4 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 +select t7.a from t7 where t7.a in (select c from t3) and rand() < 0; +a +select t7.a from t7 where t7.a in (select c from t3) and rand() >= 0; +a +1 +2 +3 +# The conjunct also reads a column of the enclosing query. It is true +# when t7.a is 4 or more, which holds only for the row where t7.a is +# 100. +explain +select t7.a, +(select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < t7.a - 3) x +from t7; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t7 ALL NULL NULL NULL NULL 4 +2 DEPENDENT SUBQUERY t1 const PRIMARY PRIMARY 4 const 1 Using index +2 DEPENDENT SUBQUERY eq_ref distinct_key distinct_key 4 func 1 +3 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +select t7.a, +(select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < t7.a - 3) x +from t7; +a x +1 NULL +100 1 +2 NULL +3 NULL +# rand(7) continues one sequence across the executions of the +# subquery. The conjunct takes one value for each row of t8, +# including the rows where t8.a is 100 and the lookup finds no match. +# A plan where t1 is not const checks the conjunct on t1 before the +# lookup and returns the same rows. +create table t8 (id int, a int); +insert into t8 values (1,1),(2,100),(3,2),(4,100),(5,3),(6,100),(7,4), +(8,100),(9,5),(10,100),(11,6),(12,7),(13,8),(14,9); +explain +select t8.id, +(select t1.a from t1 +where t1.a=1 and t8.a in (select c from t3) and rand(7) < 0.5) x +from t8; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t8 ALL NULL NULL NULL NULL 14 +2 DEPENDENT SUBQUERY t1 const PRIMARY PRIMARY 4 const 1 Using index +2 DEPENDENT SUBQUERY eq_ref distinct_key distinct_key 4 func 1 +3 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 +select t8.id, +(select t1.a from t1 +where t1.a=1 and t8.a in (select c from t3) and rand(7) < 0.5) x +from t8 order by t8.id; +id x +1 NULL +2 NULL +3 1 +4 NULL +5 NULL +6 NULL +7 1 +8 NULL +9 1 +10 NULL +11 NULL +12 1 +13 1 +14 NULL +# rand() in the WHERE of the subquery instead of the outer query. +# Every row of t9 has c=1. The first four values of rand(1) are not +# below 0.09 and the fifth is, so checking the condition on each row +# of t9 matches t1.a=1. A subquery that calls rand() is not converted +# to a semi-join, so the condition is not moved to the outer query and +# checked once there. Adding 0*t9.d makes the condition read a column +# of t9, which gives the same result in any plan. +create table t9 (c int, d int); +insert into t9 select 1, 1 from seq_1_to_80; +explain +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY const distinct_key distinct_key 4 const 1 +2 MATERIALIZED t9 ALL NULL NULL NULL NULL 80 Using where +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09); +count(*) +1 +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09 + 0*t9.d); +count(*) +1 +# The conjunct is checked once, in outer_ref_condition, only when +# the join returns at most one row. f1() counts its calls. With +# the const t1 and a lookup nest, f1() is called once. Adding t10, +# which holds two rows, makes the join return two rows, and f1() is +# called once for each. +create table t10 (b int); +insert into t10 values (1),(2); +create function f1() returns int not deterministic +begin +set @calls= @calls + 1; +return 1; +end| +explain +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +set @calls= 0; +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +a +1 +select @calls; +@calls +1 +explain +select t1.a, t10.b from t1, t10 +where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY t10 ALL NULL NULL NULL NULL 2 Using where +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 80 Using where +set @calls= 0; +select t1.a, t10.b from t1, t10 +where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +a b +1 1 +1 2 +select @calls; +@calls +2 +drop function f1; +# A materialized nest read by a scan precedes t11, which is not +# const, so the conjunct is checked on t11. The optimizer chooses +# the scan only with the statistics from ANALYZE, which give 200 +# rows of t11 for each value of a. +create table t11 (a int, key(a)); +insert into t11 select seq mod 10 from seq_1_to_2000; +create table t12 (b int); +insert into t12 select seq mod 10 from seq_1_to_20; +analyze table t11, t12 persistent for all; +Table Op Msg_type Msg_text +test.t11 analyze status Engine-independent statistics collected +test.t11 analyze status Table is already up to date +test.t12 analyze status Engine-independent statistics collected +test.t12 analyze status OK +explain format=json +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() < 0; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "cost": "COST_REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "const", + "possible_keys": ["PRIMARY"], + "key": "PRIMARY", + "key_length": "4", + "used_key_parts": ["a"], + "ref": ["const"], + "rows": 1, + "filtered": 100, + "using_index": true + } + }, + { + "table": { + "table_name": "", + "access_type": "ALL", + "possible_keys": ["distinct_key"], + "rows": 20, + "filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "nested_loop": [ + { + "table": { + "table_name": "t12", + "access_type": "ALL", + "loops": 1, + "rows": 20, + "cost": "COST_REPLACED", + "filtered": 100, + "attached_condition": "t12.b is not null" + } + } + ] + } + } + } + }, + { + "table": { + "table_name": "t11", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t12.b"], + "loops": 20, + "rows": 200, + "cost": "COST_REPLACED", + "filtered": 100, + "attached_condition": "rand() < 0", + "using_index": true + } + } + ] + } +} +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() < 0; +count(*) +0 +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() >= 0; +count(*) +2000 +# A prepared statement executed twice, with the bound for rand() +# passed as a parameter. The first execution returns no row and +# the second returns t1.a=1. +prepare stmt from "select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < ?"; +execute stmt using 0; +a +execute stmt using 2; +a +1 +deallocate prepare stmt; +# The same query in a stored procedure called twice. +create procedure p1(x int) +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < x; +call p1(0); +a +call p1(2); +a +1 +drop procedure p1; +set optimizer_switch= @save_optimizer_switch; +set optimizer_where_cost= @save_optimizer_where_cost; +drop table t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12; +# End of 11.4 tests diff --git a/mysql-test/main/subselect_sj_mat_rand.test b/mysql-test/main/subselect_sj_mat_rand.test new file mode 100644 index 00000000000..be6c85739b2 --- /dev/null +++ b/mysql-test/main/subselect_sj_mat_rand.test @@ -0,0 +1,214 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-41146: rand() in WHERE is not checked when every base table +--echo # is const and a materialized semi-join terminates the join order +--echo # +--echo # A WHERE conjunct that calls rand() and names no field is checked on +--echo # the last top level base table that is not const. When every base +--echo # table is const, the conjunct is checked once in outer_ref_condition. +--echo # + +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +create table t2 (b int primary key); +insert into t2 values (1),(2),(3),(4); +create table t3 (c int, d int); +insert into t3 values (1,1),(2,2),(3,3),(4,4),(5,5), +(6,6),(7,7),(8,8),(9,9),(10,10); +insert into t3 select c+10, d+10 from t3; +insert into t3 select c+20, d+20 from t3; +insert into t3 select c+40, d+40 from t3; +create table t4 (e int, f int); +insert into t4 select c, d from t3; +create table t5 (g int); +insert into t5 select c+1000 from t3; + +set @save_optimizer_switch= @@optimizer_switch; +set @save_optimizer_where_cost= @@optimizer_where_cost; +set optimizer_switch='firstmatch=off,loosescan=off'; +set optimizer_where_cost=0; + +--echo # One const table. rand() < 0 is never true, so the result is empty. +explain +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() < 0; +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() < 0; +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and rand() >= 0; + +--echo # The IN-equality is still checked. t5 holds no value below 1001. +select t1.a from t1 where t1.a=1 and t1.a in (select g from t5) and rand() >= 0; +select t1.a from t1 where t1.a=1 and t1.a in (select g from t5) and rand() < 0; + +--echo # Two rand() conjuncts. outer_ref_condition shows both. +--source include/explain-no-costs.inc +explain format=json +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() < 0 and rand() < 2; +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() < 0 and rand() < 2; +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) +and rand() >= 0 and rand() < 2; + +--echo # Two const tables joined, as in the original report. +explain +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() < 0; +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() < 0; +select t1.a x, t2.b y from t1 left join t2 on t1.a=t2.b +where t1.a=1 and t2.b=1 and t1.a in (select c from t3) and rand() >= 0; + +--echo # Two materialized nests terminate the join order. +explain +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) + and rand() < 0; +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) + and rand() < 0; +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and t1.a in (select e from t4) + and rand() >= 0; + +--echo # The const table column is bigint and the subquery column is int. +create table t6 (a bigint primary key); +insert into t6 values (1),(2),(3); +explain +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() < 0; +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() < 0; +select t6.a from t6 where t6.a=1 and t6.a in (select c from t3) and rand() >= 0; + +--echo # An outer table that is not const precedes the nest. The conjunct +--echo # is checked on that table. +create table t7 (a int); +insert into t7 values (1),(2),(3),(100); +explain +select t7.a from t7 where t7.a in (select c from t3) and rand() < 0; +select t7.a from t7 where t7.a in (select c from t3) and rand() < 0; +--sorted_result +select t7.a from t7 where t7.a in (select c from t3) and rand() >= 0; + +--echo # The conjunct also reads a column of the enclosing query. It is true +--echo # when t7.a is 4 or more, which holds only for the row where t7.a is +--echo # 100. +explain +select t7.a, + (select t1.a from t1 + where t1.a=1 and t1.a in (select c from t3) and rand() < t7.a - 3) x +from t7; +--sorted_result +select t7.a, + (select t1.a from t1 + where t1.a=1 and t1.a in (select c from t3) and rand() < t7.a - 3) x +from t7; + +--echo # rand(7) continues one sequence across the executions of the +--echo # subquery. The conjunct takes one value for each row of t8, +--echo # including the rows where t8.a is 100 and the lookup finds no match. +--echo # A plan where t1 is not const checks the conjunct on t1 before the +--echo # lookup and returns the same rows. +create table t8 (id int, a int); +insert into t8 values (1,1),(2,100),(3,2),(4,100),(5,3),(6,100),(7,4), +(8,100),(9,5),(10,100),(11,6),(12,7),(13,8),(14,9); +explain +select t8.id, + (select t1.a from t1 + where t1.a=1 and t8.a in (select c from t3) and rand(7) < 0.5) x +from t8; +select t8.id, + (select t1.a from t1 + where t1.a=1 and t8.a in (select c from t3) and rand(7) < 0.5) x +from t8 order by t8.id; + +--echo # rand() in the WHERE of the subquery instead of the outer query. +--echo # Every row of t9 has c=1. The first four values of rand(1) are not +--echo # below 0.09 and the fifth is, so checking the condition on each row +--echo # of t9 matches t1.a=1. A subquery that calls rand() is not converted +--echo # to a semi-join, so the condition is not moved to the outer query and +--echo # checked once there. Adding 0*t9.d makes the condition read a column +--echo # of t9, which gives the same result in any plan. +create table t9 (c int, d int); +insert into t9 select 1, 1 from seq_1_to_80; +explain +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09); +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09); +select count(*) from t1 +where t1.a=1 and t1.a in (select c from t9 where rand(1) < 0.09 + 0*t9.d); + +--echo # The conjunct is checked once, in outer_ref_condition, only when +--echo # the join returns at most one row. f1() counts its calls. With +--echo # the const t1 and a lookup nest, f1() is called once. Adding t10, +--echo # which holds two rows, makes the join return two rows, and f1() is +--echo # called once for each. +create table t10 (b int); +insert into t10 values (1),(2); +delimiter |; +create function f1() returns int not deterministic +begin + set @calls= @calls + 1; + return 1; +end| +delimiter ;| +explain +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +set @calls= 0; +# A second execution under --ps-protocol would double @calls. +--disable_ps2_protocol +select t1.a from t1 where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +--enable_ps2_protocol +select @calls; +explain +select t1.a, t10.b from t1, t10 +where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +set @calls= 0; +# A second execution under --ps-protocol would double @calls. +--disable_ps2_protocol +--sorted_result +select t1.a, t10.b from t1, t10 +where t1.a=1 and t1.a in (select c from t3) and f1() > 0; +--enable_ps2_protocol +select @calls; +drop function f1; + +--echo # A materialized nest read by a scan precedes t11, which is not +--echo # const, so the conjunct is checked on t11. The optimizer chooses +--echo # the scan only with the statistics from ANALYZE, which give 200 +--echo # rows of t11 for each value of a. +create table t11 (a int, key(a)); +insert into t11 select seq mod 10 from seq_1_to_2000; +create table t12 (b int); +insert into t12 select seq mod 10 from seq_1_to_20; +analyze table t11, t12 persistent for all; +--source include/explain-no-costs.inc +explain format=json +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() < 0; +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() < 0; +select count(*) from t1, t11 +where t1.a=1 and t11.a in (select b from t12) and rand() >= 0; + +--echo # A prepared statement executed twice, with the bound for rand() +--echo # passed as a parameter. The first execution returns no row and +--echo # the second returns t1.a=1. +prepare stmt from "select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < ?"; +execute stmt using 0; +execute stmt using 2; +deallocate prepare stmt; + +--echo # The same query in a stored procedure called twice. +create procedure p1(x int) +select t1.a from t1 +where t1.a=1 and t1.a in (select c from t3) and rand() < x; +call p1(0); +call p1(2); +drop procedure p1; + +set optimizer_switch= @save_optimizer_switch; +set optimizer_where_cost= @save_optimizer_where_cost; +drop table t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12; + +--echo # End of 11.4 tests diff --git a/mysql-test/main/tlsv13.test b/mysql-test/main/tlsv13.test index f90b6849a86..2c80b4c2089 100644 --- a/mysql-test/main/tlsv13.test +++ b/mysql-test/main/tlsv13.test @@ -19,11 +19,11 @@ source include/restart_mysqld.inc; --exec $MYSQL --host=localhost --ssl-cipher=ECDHE-RSA-AES256-GCM-SHA384 --tls-version=TLSv1.2 -e "SHOW STATUS LIKE 'Ssl_cipher%';" # Check that other ciphers are correctly not supported by the server ---replace_regex /sslv3 alert handshake failure/ssl\/tls alert handshake failure/ +--replace_regex /error: (sslv3|tls) alert/error: ssl\/tls alert/ --error 1 --exec $MYSQL --host=localhost --ssl-cipher=TLS_AES_256_GCM_SHA384 --tls-version=TLSv1.3 -e "SHOW STATUS LIKE 'Ssl_cipher';" 2>&1 ---replace_regex /sslv3 alert handshake failure/ssl\/tls alert handshake failure/ +--replace_regex /error: (sslv3|tls) alert/error: ssl\/tls alert/ --error 1 --exec $MYSQL --host=localhost --ssl-cipher=ECDHE-RSA-AES128-GCM-SHA256 --tls-version=TLSv1.2 -e "SHOW STATUS LIKE 'Ssl_cipher';" 2>&1 diff --git a/mysql-test/main/win.result b/mysql-test/main/win.result index b13d189a2b3..e56ae5b189f 100644 --- a/mysql-test/main/win.result +++ b/mysql-test/main/win.result @@ -4875,3 +4875,27 @@ DROP TABLE t1; # # End of 10.11 tests # +# +# MDEV-41146: WHERE is not checked when every table is const and +# the query needs a temporary table +# +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +create table t2 (b int); +insert into t2 values (1),(10); +# rand() < 0 is never true, so the result is empty. +select a, row_number() over () from t1 where a=1 and rand() < 0; +a row_number() over () +select a, row_number() over () from t1 where a=1 and rand() >= 0; +a row_number() over () +1 1 +# t2.b > 5 is true only for the row where t2.b is 10. +select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x +from t2; +b x +1 NULL +10 1 +drop table t1, t2; +# +# End of 11.4 tests +# diff --git a/mysql-test/main/win.test b/mysql-test/main/win.test index dd40e61d312..1da3dfa3598 100644 --- a/mysql-test/main/win.test +++ b/mysql-test/main/win.test @@ -3150,3 +3150,28 @@ DROP TABLE t1; --echo # --echo # End of 10.11 tests --echo # + +--echo # +--echo # MDEV-41146: WHERE is not checked when every table is const and +--echo # the query needs a temporary table +--echo # + +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +create table t2 (b int); +insert into t2 values (1),(10); + +--echo # rand() < 0 is never true, so the result is empty. +select a, row_number() over () from t1 where a=1 and rand() < 0; +select a, row_number() over () from t1 where a=1 and rand() >= 0; + +--echo # t2.b > 5 is true only for the row where t2.b is 10. +--sorted_result +select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x +from t2; + +drop table t1, t2; + +--echo # +--echo # End of 11.4 tests +--echo # diff --git a/mysql-test/suite/binlog/r/binlog_base64_flag.result b/mysql-test/suite/binlog/r/binlog_base64_flag.result index 74339e16d03..f4a2a53afc7 100644 --- a/mysql-test/suite/binlog/r/binlog_base64_flag.result +++ b/mysql-test/suite/binlog/r/binlog_base64_flag.result @@ -104,3 +104,74 @@ BINLOG '-2079193929'; ERROR HY000: Decoding of base64 string failed BINLOG 'xç↓%~∙D╒ƒ╡'; ERROR HY000: Decoding of base64 string failed +# +# MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. +# +create table t0 (a int); +create table t1 (a int); +## Case 1: Trigger (disabled) +create trigger trig_t0_insert after insert on t0 for each row binlog 'enabled'; +ERROR 0A000: BINLOG is not allowed in stored procedures +create trigger trig_t0_insert after insert on t0 for each row +binlog @a, @b; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 2: SP (disabled) +create procedure proc_t1_insert() binlog 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp'; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 3: SF (disabled) +create function func_t1_insert() returns int +begin +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +return 0; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +create function func_t1_insert() returns int +begin +binlog @a, @b; +return 0; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 4: Event (disabled) +create event ev_t1_insert on schedule every 1 day do +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 5: Anonymous block (disabled) +begin not atomic +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 6: Dynamic SQL in SP (disabled) +create procedure proc_t1_insert() +execute immediate "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +call proc_t1_insert(); +ERROR 0A000: BINLOG is not allowed in stored procedures +drop procedure proc_t1_insert; +prepare stmt from "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +create procedure proc_t1_insert() execute stmt; +call proc_t1_insert(); +ERROR 0A000: BINLOG is not allowed in stored procedures +drop procedure proc_t1_insert; +deallocate prepare stmt; +## Case 7: Row event in PS (works) +binlog 'ODdYRw8BAAAAZgAAAGoAAAABAAQANS4xLjIzLXJjLWRlYnVnLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4N1hHEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC'; +prepare stmt from "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; +a +3 +3 +## Case 8: Statement event in PS (works) +prepare stmt from "binlog 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp'"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; +a +3 +3 +99 +99 +drop table t0, t1; +# End of 10.11 tests diff --git a/mysql-test/suite/binlog/t/binlog_base64_flag.test b/mysql-test/suite/binlog/t/binlog_base64_flag.test index 75bb8fb4f7c..899f8605d8d 100644 --- a/mysql-test/suite/binlog/t/binlog_base64_flag.test +++ b/mysql-test/suite/binlog/t/binlog_base64_flag.test @@ -164,3 +164,101 @@ BINLOG '123'; BINLOG '-2079193929'; --error ER_BASE64_DECODE_ERROR BINLOG 'xç↓%~∙D╒ƒ╡'; + +--echo # +--echo # MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. +--echo # + +create table t0 (a int); +create table t1 (a int); + +# Format_description_log_event, same as in the FD test above +--let $FORMAT_DESCR_LOG_EVENT= 'ODdYRw8BAAAAZgAAAGoAAAABAAQANS4xLjIzLXJjLWRlYnVnLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4N1hHEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC' + +# table_map+write_rows_log_event corresponding to: insert into t1 values (3) +--let $ROW_INSERT_T1= 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA==' + +# Query_log_event (no checksum, matches the checksum-less FD event above) +# corresponding to: insert into t1 values (99) +--let $STMT_INSERT_T1= 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp' + +--echo ## Case 1: Trigger (disabled) + +--error ER_SP_BADSTATEMENT +create trigger trig_t0_insert after insert on t0 for each row binlog 'enabled'; +--error ER_SP_BADSTATEMENT +create trigger trig_t0_insert after insert on t0 for each row +binlog @a, @b; + +--echo ## Case 2: SP (disabled) + +--error ER_SP_BADSTATEMENT +eval create procedure proc_t1_insert() binlog $STMT_INSERT_T1; + +--echo ## Case 3: SF (disabled) + +delimiter |; +--error ER_SP_BADSTATEMENT +eval create function func_t1_insert() returns int +begin + binlog $ROW_INSERT_T1; + return 0; +end| +--error ER_SP_BADSTATEMENT +create function func_t1_insert() returns int +begin + binlog @a, @b; + return 0; +end| +delimiter ;| + +--echo ## Case 4: Event (disabled) + +--error ER_SP_BADSTATEMENT +eval create event ev_t1_insert on schedule every 1 day do +binlog $ROW_INSERT_T1; + +--echo ## Case 5: Anonymous block (disabled) + +delimiter |; +--error ER_SP_BADSTATEMENT +eval begin not atomic + binlog $ROW_INSERT_T1; +end| +delimiter ;| + +--echo ## Case 6: Dynamic SQL in SP (disabled) + +eval create procedure proc_t1_insert() +execute immediate "binlog $ROW_INSERT_T1"; +--error ER_SP_BADSTATEMENT +call proc_t1_insert(); +drop procedure proc_t1_insert; + +eval prepare stmt from "binlog $ROW_INSERT_T1"; +create procedure proc_t1_insert() execute stmt; +--error ER_SP_BADSTATEMENT +call proc_t1_insert(); +drop procedure proc_t1_insert; +deallocate prepare stmt; + +--echo ## Case 7: Row event in PS (works) + +eval binlog $FORMAT_DESCR_LOG_EVENT; +eval prepare stmt from "binlog $ROW_INSERT_T1"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; + +--echo ## Case 8: Statement event in PS (works) + +eval prepare stmt from "binlog $STMT_INSERT_T1"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; + +drop table t0, t1; + +--echo # End of 10.11 tests diff --git a/mysql-test/suite/encryption/r/tempfiles_encrypted.result b/mysql-test/suite/encryption/r/tempfiles_encrypted.result index 03e64738871..dedfdc15533 100644 --- a/mysql-test/suite/encryption/r/tempfiles_encrypted.result +++ b/mysql-test/suite/encryption/r/tempfiles_encrypted.result @@ -4882,6 +4882,30 @@ DROP TABLE t1; # End of 10.11 tests # # +# MDEV-41146: WHERE is not checked when every table is const and +# the query needs a temporary table +# +create table t1 (a int primary key); +insert into t1 values (1),(2),(3); +create table t2 (b int); +insert into t2 values (1),(10); +# rand() < 0 is never true, so the result is empty. +select a, row_number() over () from t1 where a=1 and rand() < 0; +a row_number() over () +select a, row_number() over () from t1 where a=1 and rand() >= 0; +a row_number() over () +1 1 +# t2.b > 5 is true only for the row where t2.b is 10. +select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x +from t2; +b x +1 NULL +10 1 +drop table t1, t2; +# +# End of 11.4 tests +# +# # MDEV-23867: select crash in compute_window_func # set @save_sort_buffer_size=@@sort_buffer_size; diff --git a/mysql-test/suite/encryption/t/innochecksum.test b/mysql-test/suite/encryption/t/innochecksum.test index e440e3d3344..d4a3d78a944 100644 --- a/mysql-test/suite/encryption/t/innochecksum.test +++ b/mysql-test/suite/encryption/t/innochecksum.test @@ -300,6 +300,16 @@ if (0 && $have_debug) { # these messages sometimes fail to appear --source include/start_mysqld.inc +# The check of pages_old needs old pages in the buffer pool LRU list. +# They exist only when the list holds at least BUF_LRU_OLD_MIN_LEN pages. +# Without the asynchronous load of the buffer pool dump, the list can be +# shorter than that. +let $wait_condition = + SELECT SUBSTR(variable_value, 1, 33) = 'Buffer pool(s) load completed at ' + FROM information_schema.global_status + WHERE LOWER(variable_name) = 'innodb_buffer_pool_load_status'; +--source include/wait_condition.inc + --echo # Trigger AHI and test pages_hashed, pages_old as non zero from sys view SELECT STRAIGHT_JOIN COUNT(t6a.a) diff --git a/mysql-test/suite/galera/r/galera_restart_on_unknown_option.result b/mysql-test/suite/galera/r/galera_restart_on_unknown_option.result index 221ccc76afd..42c0700220b 100644 --- a/mysql-test/suite/galera/r/galera_restart_on_unknown_option.result +++ b/mysql-test/suite/galera/r/galera_restart_on_unknown_option.result @@ -1,5 +1,6 @@ connection node_2; connection node_1; +# Correct Galera library found CALL mtr.add_suppression("Aborting"); CALL mtr.add_suppression("unknown option '--galera-unknown-option'"); connection node_1; @@ -22,7 +23,10 @@ f1 f2 2 b 3 c connection node_2; -Starting server ... +Starting server with an unknown option ... +FOUND 1 /unknown option '--galera-unknown-option'/ in galera_restart_on_unknown_option.err +FOUND 1 /State transfer interrupted, shutting down gracefully/ in galera_restart_on_unknown_option.err +NOT FOUND /Assertion .* failed/ in galera_restart_on_unknown_option.err Starting server ... SELECT * FROM t1; f1 f2 @@ -39,7 +43,10 @@ f1 f2 2 d 3 d connection node_2; -Starting server ... +Starting server with an unknown option ... +FOUND 1 /unknown option '--galera-unknown-option'/ in galera_restart_on_unknown_option.err +FOUND 1 /State transfer interrupted, shutting down gracefully/ in galera_restart_on_unknown_option.err +NOT FOUND /Assertion .* failed/ in galera_restart_on_unknown_option.err Starting server ... call mtr.add_suppression("WSREP: Failed to scan the last segment to the end\\. Last events may be missing\\. Last recovered event: "); SELECT * FROM t1; diff --git a/mysql-test/suite/galera/t/galera_restart_on_unknown_option.test b/mysql-test/suite/galera/t/galera_restart_on_unknown_option.test index 9a11a174dfe..647bfa83ef4 100644 --- a/mysql-test/suite/galera/t/galera_restart_on_unknown_option.test +++ b/mysql-test/suite/galera/t/galera_restart_on_unknown_option.test @@ -1,6 +1,13 @@ --source include/galera_cluster.inc --source include/have_innodb.inc +# +# Galera library version with the graceful shutdown on an interrupted state +# transfer (MDEV-31517), which this test expects +# +--let $galera_version=26.4.25 +source ../wsrep/include/check_galera_version.inc; + # Suppress expected warnings: CALL mtr.add_suppression("Aborting"); @@ -11,22 +18,6 @@ CALL mtr.add_suppression("unknown option '--galera-unknown-option'"); --let $node_2=node_2 --source include/auto_increment_offset_save.inc -# -# We should count the number of "Assertion failed" warnings -# in the log file before and after testing. To do this we need -# to save original log file before testing: -# ---let TEST_LOG=$MYSQLTEST_VARDIR/log/mysqld.2.err ---perl - use strict; - my $test_log=$ENV{'TEST_LOG'} or die "TEST_LOG not set"; - my $test_log_copy=$test_log . '.copy'; - if (-e $test_log_copy) { - unlink $test_log_copy; - } -EOF ---copy_file $TEST_LOG $TEST_LOG.copy - --connection node_2 CREATE TABLE t1 (f1 INTEGER PRIMARY KEY, f2 CHAR(1)); @@ -59,14 +50,26 @@ SELECT * FROM t1; --remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat ---let $start_mysqld_params=--galera-unknown-option +# The server must refuse to start, and exit without an assertion. +# Start it directly rather than through the expect file: mtr would treat +# a server that fails to start as a test failure. +# The unknown option is found only after the state transfer has started, +# which is then interrupted, and Galera aborts the process on purpose +# (SIGABRT, exit code 134). ---echo Starting server ... ---write_line "try:$start_mysqld_params" $_expect_file_name - -# Sleep to ensure that server exited... - ---sleep 30 +--echo Starting server with an unknown option ... +--let $unknown_option_log= $MYSQLTEST_VARDIR/tmp/galera_restart_on_unknown_option.err +--error 134 +--exec $MYSQLD --defaults-group-suffix=.2 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --log-error=$unknown_option_log --galera-unknown-option > $unknown_option_log.out 2>&1 +--let SEARCH_FILE= $unknown_option_log +--let SEARCH_PATTERN= unknown option '--galera-unknown-option' +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN= State transfer interrupted, shutting down gracefully +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN= Assertion .* failed +--source include/search_pattern_in_file.inc +--remove_file $unknown_option_log +--remove_file $unknown_option_log.out # Restart node 2 without unknown option: @@ -104,14 +107,26 @@ SELECT * FROM t1; --connection node_2 ---let $start_mysqld_params=--galera-unknown-option +# The server must refuse to start, and exit without an assertion. +# Start it directly rather than through the expect file: mtr would treat +# a server that fails to start as a test failure. +# The unknown option is found only after the state transfer has started, +# which is then interrupted, and Galera aborts the process on purpose +# (SIGABRT, exit code 134). ---echo Starting server ... ---write_line "try:$start_mysqld_params" $_expect_file_name - -# Sleep to ensure that server exited... - ---sleep 30 +--echo Starting server with an unknown option ... +--let $unknown_option_log= $MYSQLTEST_VARDIR/tmp/galera_restart_on_unknown_option.err +--error 134 +--exec $MYSQLD --defaults-group-suffix=.2 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --log-error=$unknown_option_log --galera-unknown-option > $unknown_option_log.out 2>&1 +--let SEARCH_FILE= $unknown_option_log +--let SEARCH_PATTERN= unknown option '--galera-unknown-option' +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN= State transfer interrupted, shutting down gracefully +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN= Assertion .* failed +--source include/search_pattern_in_file.inc +--remove_file $unknown_option_log +--remove_file $unknown_option_log.out # Restart node 2 without unknown option: @@ -133,27 +148,4 @@ SELECT * FROM t1; DROP TABLE t1; --source include/auto_increment_offset_restore.inc -# -# We should count the number of "Assertion failed" warnings -# in the log file during test phase - to print the error message -# if quantity of such warnings in log file increased at the end -# of the test: -# ---perl - use strict; - my $test_log=$ENV{'TEST_LOG'} or die "TEST_LOG not set"; - my $test_log_copy=$test_log . '.copy'; - open(FILE, $test_log_copy) or die("Unable to open $test_log_copy: $!\n"); - my $initial=grep(/Assertion * failed/gi,); - close(FILE); - open(FILE, $test_log) or die("Unable to open $test_log: $!\n"); - my $count_warnings=grep(/Assertion * failed/gi,); - close(FILE); - if ($count_warnings != $initial) { - my $diff=$count_warnings-$initial; - print "Assertion failed $diff times.\n"; - } -EOF ---remove_file $TEST_LOG.copy - --source include/auto_increment_offset_restore.inc diff --git a/mysql-test/suite/innodb/r/buf_pool_resize_pcur_restore.result b/mysql-test/suite/innodb/r/buf_pool_resize_pcur_restore.result new file mode 100644 index 00000000000..289f7504ed3 --- /dev/null +++ b/mysql-test/suite/innodb/r/buf_pool_resize_pcur_restore.result @@ -0,0 +1,122 @@ +create table t1 (pk int primary key, val varchar(255) not null) +engine=innodb stats_persistent=0 charset=latin1; +insert into t1 select seq, repeat('a', 100) from seq_1_to_20 order by seq desc; +create table t2 like t1; +insert into t2 select seq, repeat('a', 100) from seq_1_to_20 order by seq desc; +create table t3 (pk int primary key) engine=innodb stats_persistent=0 +partition by hash(pk) partitions 256; +insert into t3 select seq from seq_0_to_255; +create table t4 (a int primary key, b varchar(7000)) engine=innodb +stats_persistent=0; +insert into t4 select seq, repeat('x', 7000) from seq_1_to_3000; +create function hold(x int) returns int +begin +if x = 1 then +set debug_sync= 'now SIGNAL paused WAIT_FOR go'; +end if; +return 1; +end| +create procedure read_into_first_new_block(tbl varchar(64)) +begin +declare attempts int default 5; +declare blk bigint default -1; +declare p int; +set @read_tbl= concat('select count(*) into @n from ', tbl); +prepare read_tbl from @read_tbl; +while blk != @first_new do +if attempts = 0 then +signal sqlstate '45000' set message_text= 'Wrong placement'; +end if; +set attempts= attempts - 1; +if blk != -1 then +set global innodb_buffer_pool_size= @old_size; +end if; +# Evict all pages of t1, t2 and t3. +select sum(length(b)) into @n from t4; +if @first_new = 0 then +set @first_new= (select pool_size +from information_schema.innodb_buffer_pool_stats); +end if; +set global innodb_buffer_pool_size= @new_size; +# A grow appends the new blocks to the end of the free list. Read pages +# of t3 until only the new blocks are free. +set p= 0; +while (select free_buffers > pool_size - @first_new +from information_schema.innodb_buffer_pool_stats) do +if p = 256 then +signal sqlstate '45000' set message_text= 'Out of partitions'; +end if; +select pk into @n from t3 where pk= p; +set p= p + 1; +end while; +execute read_tbl; +select block_id into blk from information_schema.innodb_buffer_page +where table_name = concat('`test`.`', tbl, '`'); +end while; +deallocate prepare read_tbl; +end| +set global innodb_max_purge_lag_wait= 0; +set @old_size= @@global.innodb_buffer_pool_size; +set @new_size= 12582912; +set @first_new= 0; +set @save_scan_depth= @@global.innodb_lru_scan_depth; +set global innodb_lru_scan_depth= 100; +# +# A read that continues from a stale position +# +# Read the page of t1 into the first new block. +call read_into_first_new_block('t1'); +connect con1,localhost,root; +select group_concat(pk) from t1 where hold(pk); +connection default; +set debug_sync= 'now WAIT_FOR paused'; +# Reorganize the page while the read holds a stored position on pk=1. +set @save_dbug= @@debug_dbug; +set debug_dbug= '+d,do_page_reorganize'; +insert into t1 values (21, repeat('a', 100)); +set debug_dbug= @save_dbug; +# Shrink, which moves the page out of the block. +set global innodb_buffer_pool_size= @old_size; +# Read the page of t1 into the same block again. +call read_into_first_new_block('t1'); +# Resume the read, which must return pk=2 to pk=20. +set debug_sync= 'now SIGNAL go'; +connection con1; +group_concat(pk) +1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 +connection default; +set global innodb_buffer_pool_size= @old_size; +set global innodb_max_purge_lag_wait= 0; +# +# An UPDATE that continues from a stale position +# +# Read the page of t2 into the first new block. +call read_into_first_new_block('t2'); +connection con1; +update t2 set val= 'b' where hold(pk); +connection default; +set debug_sync= 'now WAIT_FOR paused'; +# Reorganize the page while the UPDATE holds a stored position on pk=1. +set debug_dbug= '+d,do_page_reorganize'; +insert into t2 values (21, repeat('a', 100)); +set debug_dbug= @save_dbug; +# Shrink, which moves the page out of the block. +set global innodb_buffer_pool_size= @old_size; +# Read the page of t2 into the same block again. +call read_into_first_new_block('t2'); +# Resume the UPDATE, which must change pk=1, not pk=20. +set debug_sync= 'now SIGNAL go'; +connection con1; +affected rows: 21 +info: Rows matched: 21 Changed: 21 Warnings: 0 +disconnect con1; +connection default; +select group_concat(pk) from t2 where val = 'b'; +group_concat(pk) +1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21 +set debug_sync= 'reset'; +drop procedure read_into_first_new_block; +drop function hold; +drop table t1, t2, t3, t4; +set global innodb_buffer_pool_size= @old_size; +set global innodb_lru_scan_depth= @save_scan_depth; diff --git a/mysql-test/suite/innodb/r/skip_locked_wait_granted.result b/mysql-test/suite/innodb/r/skip_locked_wait_granted.result new file mode 100644 index 00000000000..c4c5877e5ba --- /dev/null +++ b/mysql-test/suite/innodb/r/skip_locked_wait_granted.result @@ -0,0 +1,116 @@ +# +# MDEV-40123 Assertion failure after lock wait is unblocked in SKIP LOCKED +# +CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT, KEY(a)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,1,1),(2,2,2),(3,3,3); +connect con1,localhost,root; +connect con2,localhost,root; +# The lock on the clustered index record is granted after the wait +connection con1; +BEGIN; +SELECT b FROM t1 WHERE pk=1 FOR UPDATE; +b +1 +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; +connection con2; +b +1 +2 +3 +connection con1; +SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED; +pk +connection con2; +COMMIT; +# The granted lock covers a row whose non-key column was updated +connection con1; +BEGIN; +UPDATE t1 SET b=10 WHERE pk=1; +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; +connection con2; +b +10 +2 +3 +connection con1; +SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED; +pk +connection con2; +COMMIT; +# The lock on the delete-marked secondary index record is granted +connection con1; +BEGIN; +DELETE FROM t1 WHERE pk=1; +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; +connection con2; +b +2 +3 +COMMIT; +# The lock is still held: the record is skipped +connection con1; +BEGIN; +SELECT b FROM t1 WHERE pk=2 FOR UPDATE; +b +2 +connection con2; +BEGIN; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +b +3 +COMMIT; +connection con1; +COMMIT; +# A deadlock is reported while waiting for the lock +CREATE TABLE t2 (x INT) ENGINE=InnoDB; +connection con1; +BEGIN; +INSERT INTO t2 VALUES (1),(2),(3),(4),(5); +SELECT b FROM t1 WHERE pk=2 FOR UPDATE; +b +2 +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +SET DEBUG_SYNC='deadlock_report_before_lock_releasing SIGNAL deadlock'; +SELECT pk FROM t1 FORCE INDEX (a) WHERE a=2 FOR UPDATE; +connection default; +SET DEBUG_SYNC='now WAIT_FOR deadlock'; +SET DEBUG_SYNC='now SIGNAL go'; +connection con2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +connection con1; +pk +2 +disconnect con2; +connection con1; +COMMIT; +disconnect con1; +connection default; +SET DEBUG_SYNC='RESET'; +DROP TABLE t1, t2; +# End of 11.4 tests diff --git a/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.opt b/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.opt new file mode 100644 index 00000000000..57fa706fbd4 --- /dev/null +++ b/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.opt @@ -0,0 +1,3 @@ +--innodb-buffer-pool-size=8M +--innodb-buffer-pool-size-max=16M +--innodb-read-ahead-threshold=0 diff --git a/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.test b/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.test new file mode 100644 index 00000000000..3c18dedb28b --- /dev/null +++ b/mysql-test/suite/innodb/t/buf_pool_resize_pcur_restore.test @@ -0,0 +1,173 @@ +# +# A persistent cursor that is restored after the buffer pool shrank and +# grew again +# +# btr_pcur_t::restore_position() accepts the stored position without a +# search when the block descriptor that it remembers holds the same page +# id and the same modify_clock. The descriptors are at fixed addresses, +# and a grow initializes the descriptors that an earlier shrink withdrew. +# If that reset modify_clock, a page that is read into such a descriptor +# after the grow could have the modify_clock that a cursor stored before +# the shrink, when the page had been read into the same descriptor after +# an earlier grow. The page can have changed in between, and the cursor +# would continue from a stale record pointer. +# +--source include/have_innodb.inc +--source include/have_innodb_16k.inc +--source include/have_partition.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/not_embedded.inc +--source include/count_sessions.inc + +# One leaf page each, with the records in descending key order in the page. +# A reorganization writes them in key order, so pk=20 moves to the offset +# of pk=1. +create table t1 (pk int primary key, val varchar(255) not null) +engine=innodb stats_persistent=0 charset=latin1; +insert into t1 select seq, repeat('a', 100) from seq_1_to_20 order by seq desc; +create table t2 like t1; +insert into t2 select seq, repeat('a', 100) from seq_1_to_20 order by seq desc; + +# One page per partition. A lookup of an evicted partition reads one page. +# The page cleaner keeps about innodb_lru_scan_depth blocks free, and the +# partitions must outnumber them. +create table t3 (pk int primary key) engine=innodb stats_persistent=0 +partition by hash(pk) partitions 256; +insert into t3 select seq from seq_0_to_255; + +# Three times innodb_buffer_pool_size, to evict everything else. +create table t4 (a int primary key, b varchar(7000)) engine=innodb +stats_persistent=0; +insert into t4 select seq, repeat('x', 7000) from seq_1_to_3000; + +delimiter |; +# Stop the statement after it read pk=1 and stored its position. +create function hold(x int) returns int +begin + if x = 1 then + set debug_sync= 'now SIGNAL paused WAIT_FOR go'; + end if; + return 1; +end| + +# Grow the buffer pool from @old_size to @new_size and read the only page +# of the table tbl into the first block that the grow adds. A concurrent +# page read can take that block; then shrink and try again. The first call +# sets @first_new, the block id of the first new block. Later calls require +# the same @first_new. +create procedure read_into_first_new_block(tbl varchar(64)) +begin + declare attempts int default 5; + declare blk bigint default -1; + declare p int; + set @read_tbl= concat('select count(*) into @n from ', tbl); + prepare read_tbl from @read_tbl; + while blk != @first_new do + if attempts = 0 then + signal sqlstate '45000' set message_text= 'Wrong placement'; + end if; + set attempts= attempts - 1; + if blk != -1 then + set global innodb_buffer_pool_size= @old_size; + end if; + # Evict all pages of t1, t2 and t3. + select sum(length(b)) into @n from t4; + if @first_new = 0 then + set @first_new= (select pool_size + from information_schema.innodb_buffer_pool_stats); + end if; + set global innodb_buffer_pool_size= @new_size; + # A grow appends the new blocks to the end of the free list. Read pages + # of t3 until only the new blocks are free. + set p= 0; + while (select free_buffers > pool_size - @first_new + from information_schema.innodb_buffer_pool_stats) do + if p = 256 then + signal sqlstate '45000' set message_text= 'Out of partitions'; + end if; + select pk into @n from t3 where pk= p; + set p= p + 1; + end while; + execute read_tbl; + select block_id into blk from information_schema.innodb_buffer_page + where table_name = concat('`test`.`', tbl, '`'); + end while; + deallocate prepare read_tbl; +end| +delimiter ;| + +# Purge resets DB_TRX_ID in the inserted records. Let it finish, so that it +# reads no pages into the free blocks that the procedure counts. +set global innodb_max_purge_lag_wait= 0; + +set @old_size= @@global.innodb_buffer_pool_size; +set @new_size= 12582912; +set @first_new= 0; +# The minimum, which keeps fewer blocks free than t3 has partitions. +set @save_scan_depth= @@global.innodb_lru_scan_depth; +set global innodb_lru_scan_depth= 100; + +--echo # +--echo # A read that continues from a stale position +--echo # +--echo # Read the page of t1 into the first new block. +call read_into_first_new_block('t1'); +connect (con1,localhost,root); +--send select group_concat(pk) from t1 where hold(pk) +connection default; +set debug_sync= 'now WAIT_FOR paused'; +--echo # Reorganize the page while the read holds a stored position on pk=1. +set @save_dbug= @@debug_dbug; +set debug_dbug= '+d,do_page_reorganize'; +insert into t1 values (21, repeat('a', 100)); +set debug_dbug= @save_dbug; +--echo # Shrink, which moves the page out of the block. +set global innodb_buffer_pool_size= @old_size; +--echo # Read the page of t1 into the same block again. +call read_into_first_new_block('t1'); +--echo # Resume the read, which must return pk=2 to pk=20. +set debug_sync= 'now SIGNAL go'; +connection con1; +--reap +connection default; +set global innodb_buffer_pool_size= @old_size; +# Purge was blocked by the read view of the paused read. Let it finish, so +# that it reads no pages into the new blocks of the next case. +set global innodb_max_purge_lag_wait= 0; + +--echo # +--echo # An UPDATE that continues from a stale position +--echo # +--echo # Read the page of t2 into the first new block. +call read_into_first_new_block('t2'); +connection con1; +--send update t2 set val= 'b' where hold(pk) +connection default; +set debug_sync= 'now WAIT_FOR paused'; +--echo # Reorganize the page while the UPDATE holds a stored position on pk=1. +set debug_dbug= '+d,do_page_reorganize'; +insert into t2 values (21, repeat('a', 100)); +set debug_dbug= @save_dbug; +--echo # Shrink, which moves the page out of the block. +set global innodb_buffer_pool_size= @old_size; +--echo # Read the page of t2 into the same block again. +call read_into_first_new_block('t2'); +--echo # Resume the UPDATE, which must change pk=1, not pk=20. +set debug_sync= 'now SIGNAL go'; +connection con1; +--enable_info +--reap +--disable_info +disconnect con1; +connection default; +select group_concat(pk) from t2 where val = 'b'; + +set debug_sync= 'reset'; +drop procedure read_into_first_new_block; +drop function hold; +drop table t1, t2, t3, t4; +set global innodb_buffer_pool_size= @old_size; +set global innodb_lru_scan_depth= @save_scan_depth; +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test index 8ca34b294d1..7094697df4d 100644 --- a/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test +++ b/mysql-test/suite/innodb/t/innodb_buffer_pool_resize_temporary.test @@ -36,7 +36,9 @@ send SET GLOBAL innodb_buffer_pool_size=8388608; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; # IA-32: 504 blocks; IA-32 glibc SUX_LOCK_GENERIC: 500; macOS SUX_LOCK_GENERIC: 498 ---replace_regex /([45]..)\/\1/505\/505/ +# Other threads may already have withdrawn some blocks before +# buf_pool_shrink_before_wakeup. +--replace_regex /\([0-9]+\/[45][0-9][0-9]\)/(505\/505)/ SHOW STATUS LIKE 'innodb_buffer_pool_resize_status'; SET DEBUG_SYNC='now SIGNAL go'; connection con1; diff --git a/mysql-test/suite/innodb/t/skip_locked_wait_granted.test b/mysql-test/suite/innodb/t/skip_locked_wait_granted.test new file mode 100644 index 00000000000..ada4f5c2586 --- /dev/null +++ b/mysql-test/suite/innodb/t/skip_locked_wait_granted.test @@ -0,0 +1,128 @@ +--source include/have_innodb.inc +--source include/have_debug_sync.inc + +--echo # +--echo # MDEV-40123 Assertion failure after lock wait is unblocked in SKIP LOCKED +--echo # + +CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT, KEY(a)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1,1,1),(2,2,2),(3,3,3); + +connect con1,localhost,root; +connect con2,localhost,root; + +--echo # The lock on the clustered index record is granted after the wait +connection con1; +BEGIN; +SELECT b FROM t1 WHERE pk=1 FOR UPDATE; + +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; + +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; + +connection con2; +reap; +connection con1; +SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED; +connection con2; +COMMIT; + +--echo # The granted lock covers a row whose non-key column was updated +connection con1; +BEGIN; +UPDATE t1 SET b=10 WHERE pk=1; + +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; + +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; + +connection con2; +reap; +connection con1; +SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED; +connection con2; +COMMIT; + +--echo # The lock on the delete-marked secondary index record is granted +connection con1; +BEGIN; +DELETE FROM t1 WHERE pk=1; + +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; + +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +COMMIT; +SET DEBUG_SYNC='now SIGNAL go'; + +connection con2; +reap; +COMMIT; + +--echo # The lock is still held: the record is skipped +connection con1; +BEGIN; +SELECT b FROM t1 WHERE pk=2 FOR UPDATE; + +connection con2; +BEGIN; +SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; +COMMIT; + +connection con1; +COMMIT; + +--echo # A deadlock is reported while waiting for the lock +CREATE TABLE t2 (x INT) ENGINE=InnoDB; +connection con1; +BEGIN; +# Make con1 heavier than con2, so that con2 is chosen as the deadlock victim +INSERT INTO t2 VALUES (1),(2),(3),(4),(5); +SELECT b FROM t1 WHERE pk=2 FOR UPDATE; + +connection con2; +BEGIN; +SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go'; +send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED; + +connection con1; +SET DEBUG_SYNC='now WAIT_FOR waiting'; +SET DEBUG_SYNC='deadlock_report_before_lock_releasing SIGNAL deadlock'; +send SELECT pk FROM t1 FORCE INDEX (a) WHERE a=2 FOR UPDATE; + +connection default; +SET DEBUG_SYNC='now WAIT_FOR deadlock'; +SET DEBUG_SYNC='now SIGNAL go'; + +connection con2; +--error ER_LOCK_DEADLOCK +reap; + +connection con1; +reap; + +disconnect con2; +connection con1; +COMMIT; +disconnect con1; + +connection default; +SET DEBUG_SYNC='RESET'; +DROP TABLE t1, t2; + +--echo # End of 11.4 tests diff --git a/mysql-test/suite/multi_source/mix_semisync_async.inc b/mysql-test/suite/multi_source/mix_semisync_async.inc new file mode 100644 index 00000000000..c9fad636e11 --- /dev/null +++ b/mysql-test/suite/multi_source/mix_semisync_async.inc @@ -0,0 +1,84 @@ +# Test replicating from an additional connection of semi-sync mode `$mode2` +# does not interfere with the active connection of mode `$mode1`. +# +# Pre-Conditions: +# * On connection `slave`. +# * CHANGE MASTER `master1` and `master2` set up to +# replicate from the connection with the same name. +# * None of the replication threads are running. +# * User variables `$mode1` and `$mode2` contain +# corresponding `@@rpl_semi_sync_slave_enabled` values. +# +# Post-Conditions: +# * On connection `slave`. +# * None of the replication threads are running. +# * @@default_master_connection = 'master1', +# @@rpl_semi_sync_slave_enabled = $mode2 + +--let $status_var= Rpl_semi_sync_master_clients +--let $status_var_value= 1 + + +# Start `master1` +SET @@SESSION.default_master_connection= 'master1'; +--eval SET @@GLOBAL.rpl_semi_sync_slave_enabled= $mode1 +--source include/start_slave.inc + +# Start `master2` +SET @@SESSION.default_master_connection= 'master2'; +--eval SET @@GLOBAL.rpl_semi_sync_slave_enabled= $mode2 +--source include/start_slave.inc + + +# TODO MDEV-40941: +# `Rpl_semi_sync_slave_status` does not refer to `@@default_master_connection`, +# but rather the most recent START SLAVE (`$mode2` in this case). +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +SET @@SESSION.default_master_connection= 'master1'; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; + + +# Verify `master2` +--connection master2 + if ($mode2) + { + --source include/wait_for_status_var.inc + } + CREATE TABLE t2 (a INT); + DROP TABLE t2; + --save_master_pos +--connection slave +--sync_with_master 0, master2 + +# Verify `master1` +--connection master1 + if ($mode1) + { + --source include/wait_for_status_var.inc + } + CREATE TABLE t1 (a INT); + --save_master_pos +--connection slave +--sync_with_master 0, master1 +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; + + +# Stop `master2` +SET @@SESSION.default_master_connection= 'master2'; +--source include/stop_slave.inc +# TODO MDEV-40941: +# `Rpl_semi_sync_slave_status` shows OFF after STOP SLAVE on any connection. +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; + +# Verify `master1` +--connection master1 + DROP TABLE t1; + SHOW STATUS LIKE 'Rpl_semi_sync_master_clients'; + --save_master_pos +--connection slave +--sync_with_master 0, master1 +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; + +# Stop `master1` +SET @@SESSION.default_master_connection= 'master1'; +--source include/stop_slave.inc diff --git a/mysql-test/suite/multi_source/mix_semisync_async.result b/mysql-test/suite/multi_source/mix_semisync_async.result new file mode 100644 index 00000000000..29b33ea0331 --- /dev/null +++ b/mysql-test/suite/multi_source/mix_semisync_async.result @@ -0,0 +1,109 @@ +# Setup +connect master1, 127.0.0.1, root, , , $SERVER_MYPORT_1; +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 1; +SET @@SESSION.gtid_domain_id= 1; +RESET MASTER; +connect master2, 127.0.0.1, root, , , $SERVER_MYPORT_2; +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 1; +SET @@SESSION.gtid_domain_id= 2; +RESET MASTER; +connect slave, 127.0.0.1, root, , , $SERVER_MYPORT_3; +SET @@GLOBAL.gtid_slave_pos= ''; +CHANGE MASTER 'master1' TO +master_host='127.0.0.1', master_port=SERVER_MYPORT_1, master_user='root'; +CHANGE MASTER 'master2' TO +master_host='127.0.0.1', master_port=SERVER_MYPORT_2, master_user='root'; +# Test semi-sync replication during async replication +SET @@SESSION.default_master_connection= 'master1'; +SET @@GLOBAL.rpl_semi_sync_slave_enabled= 0; +include/start_slave.inc +SET @@SESSION.default_master_connection= 'master2'; +SET @@GLOBAL.rpl_semi_sync_slave_enabled= 1; +include/start_slave.inc +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status ON +SET @@SESSION.default_master_connection= 'master1'; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status ON +connection master2; +CREATE TABLE t2 (a INT); +DROP TABLE t2; +connection slave; +connection master1; +CREATE TABLE t1 (a INT); +connection slave; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; +Variable_name Value +Rpl_semi_sync_slave_send_ack 2 +SET @@SESSION.default_master_connection= 'master2'; +include/stop_slave.inc +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status OFF +connection master1; +DROP TABLE t1; +SHOW STATUS LIKE 'Rpl_semi_sync_master_clients'; +Variable_name Value +Rpl_semi_sync_master_clients 0 +connection slave; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; +Variable_name Value +Rpl_semi_sync_slave_send_ack 2 +SET @@SESSION.default_master_connection= 'master1'; +include/stop_slave.inc +# Test the other order +SET @@SESSION.default_master_connection= 'master1'; +SET @@GLOBAL.rpl_semi_sync_slave_enabled= 1; +include/start_slave.inc +SET @@SESSION.default_master_connection= 'master2'; +SET @@GLOBAL.rpl_semi_sync_slave_enabled= 0; +include/start_slave.inc +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status OFF +SET @@SESSION.default_master_connection= 'master1'; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status OFF +connection master2; +CREATE TABLE t2 (a INT); +DROP TABLE t2; +connection slave; +connection master1; +CREATE TABLE t1 (a INT); +connection slave; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; +Variable_name Value +Rpl_semi_sync_slave_send_ack 1 +SET @@SESSION.default_master_connection= 'master2'; +include/stop_slave.inc +SHOW STATUS LIKE 'Rpl_semi_sync_slave_status'; +Variable_name Value +Rpl_semi_sync_slave_status OFF +connection master1; +DROP TABLE t1; +SHOW STATUS LIKE 'Rpl_semi_sync_master_clients'; +Variable_name Value +Rpl_semi_sync_master_clients 1 +connection slave; +SHOW STATUS LIKE 'Rpl_semi_sync_slave_send_ack'; +Variable_name Value +Rpl_semi_sync_slave_send_ack 2 +SET @@SESSION.default_master_connection= 'master1'; +include/stop_slave.inc +# Cleanup +RESET SLAVE 'master1' ALL; +RESET SLAVE 'master2' ALL; +disconnect slave; +connection master1; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 0; +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 1; +disconnect master1; +connection master2; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 0; +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 1; +disconnect master2; diff --git a/mysql-test/suite/multi_source/mix_semisync_async.test b/mysql-test/suite/multi_source/mix_semisync_async.test new file mode 100644 index 00000000000..8e3b035e95f --- /dev/null +++ b/mysql-test/suite/multi_source/mix_semisync_async.test @@ -0,0 +1,63 @@ +# MDEV-32947 Async conflicts with semi-sync in multi-source +# +# With a async master and a semi-sync master, +# test replication does not mix async and semi-sync regardless of order + +--source include/have_binlog_format_mixed.inc # format-agnostic +--let $orig_wait_no_slave= `SELECT @@GLOBAL.rpl_semi_sync_master_wait_no_slave` + +--echo # Setup +# Enable semi-sync on both masters; +# the slave will disable semi-sync on one of its connections. + +--connect (master1, 127.0.0.1, root, , , $SERVER_MYPORT_1) +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 1; +SET @@SESSION.gtid_domain_id= 1; +RESET MASTER; + +--connect (master2, 127.0.0.1, root, , , $SERVER_MYPORT_2) +SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= 0; +SET @@GLOBAL.rpl_semi_sync_master_enabled= 1; +SET @@SESSION.gtid_domain_id= 2; +RESET MASTER; + +--connect (slave, 127.0.0.1, root, , , $SERVER_MYPORT_3) +--disable_warnings + SET @@GLOBAL.gtid_slave_pos= ''; +--enable_warnings + +--replace_result $SERVER_MYPORT_1 SERVER_MYPORT_1 +eval CHANGE MASTER 'master1' TO + master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root'; +--replace_result $SERVER_MYPORT_2 SERVER_MYPORT_2 +eval CHANGE MASTER 'master2' TO + master_host='127.0.0.1', master_port=$SERVER_MYPORT_2, master_user='root'; + + +--echo # Test semi-sync replication during async replication +--let $mode1= 0 +--let $mode2= 1 +--source mix_semisync_async.inc + +--echo # Test the other order +--let $mode1= 1 +--let $mode2= 0 +--source mix_semisync_async.inc + + +--echo # Cleanup + +RESET SLAVE 'master1' ALL; +RESET SLAVE 'master2' ALL; +--disconnect slave + +--connection master1 + SET @@GLOBAL.rpl_semi_sync_master_enabled= 0; + --eval SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= $orig_wait_no_slave +--disconnect master1 + +--connection master2 + SET @@GLOBAL.rpl_semi_sync_master_enabled= 0; + --eval SET @@GLOBAL.rpl_semi_sync_master_wait_no_slave= $orig_wait_no_slave +--disconnect master2 diff --git a/mysql-test/suite/multi_source/rpl_filter_default_master.result b/mysql-test/suite/multi_source/sys_vars_default_master.result similarity index 53% rename from mysql-test/suite/multi_source/rpl_filter_default_master.result rename to mysql-test/suite/multi_source/sys_vars_default_master.result index 044e8031c8c..78d9950b23a 100644 --- a/mysql-test/suite/multi_source/rpl_filter_default_master.result +++ b/mysql-test/suite/multi_source/sys_vars_default_master.result @@ -1,9 +1,10 @@ SET @saved_replicate_do_db= @@GLOBAL.replicate_do_db; +SET @saved_slave_parallel_mode= @@GLOBAL.slave_parallel_mode; SET @saved_default_master_connection= @@SESSION.default_master_connection; CHANGE MASTER 'name' TO master_host='127.0.0.1', master_user='root'; -SET @@GLOBAL.replicate_do_db= 'unnamed'; SET @@SESSION.default_master_connection= 'name'; SET @@GLOBAL.replicate_do_db= 'named'; +SET @@GLOBAL.``.replicate_do_db= 'unnamed'; SELECT @@GLOBAL.name.replicate_do_db; @@GLOBAL.name.replicate_do_db named @@ -13,7 +14,19 @@ named SELECT @@GLOBAL.``.replicate_do_db; @@GLOBAL.``.replicate_do_db unnamed -RESET SLAVE ALL; +SET @@GLOBAL.slave_parallel_mode= 'minimal'; +SET @@GLOBAL.``.slave_parallel_mode= 'aggressive'; +SELECT @@GLOBAL.name.slave_parallel_mode; +@@GLOBAL.name.slave_parallel_mode +minimal +SELECT @@GLOBAL.slave_parallel_mode; +@@GLOBAL.slave_parallel_mode +minimal +SELECT @@GLOBAL.``.slave_parallel_mode; +@@GLOBAL.``.slave_parallel_mode +aggressive +RESET SLAVE 'name' ALL; SET @@SESSION.default_master_connection= @saved_default_master_connection; +SET @@GLOBAL.slave_parallel_mode= @saved_slave_parallel_mode; SET @@GLOBAL.replicate_do_db= @saved_replicate_do_db; -# End of 10.11 tests +# End of 11.4 tests diff --git a/mysql-test/suite/multi_source/rpl_filter_default_master.test b/mysql-test/suite/multi_source/sys_vars_default_master.test similarity index 52% rename from mysql-test/suite/multi_source/rpl_filter_default_master.test rename to mysql-test/suite/multi_source/sys_vars_default_master.test index 3693b1fd8f1..7f3e56d5ca2 100644 --- a/mysql-test/suite/multi_source/rpl_filter_default_master.test +++ b/mysql-test/suite/multi_source/sys_vars_default_master.test @@ -1,25 +1,37 @@ --source include/not_embedded.inc # MDEV-40296: SELECT @@GLOBAL.replicate_do_db ignores default_master_connection +# MDEV-40297: SET changes @@default_master_connection's system variable if the explicit connection name is blank # Setup SET @saved_replicate_do_db= @@GLOBAL.replicate_do_db; +SET @saved_slave_parallel_mode= @@GLOBAL.slave_parallel_mode; SET @saved_default_master_connection= @@SESSION.default_master_connection; CHANGE MASTER 'name' TO master_host='127.0.0.1', master_user='root'; -SET @@GLOBAL.replicate_do_db= 'unnamed'; SET @@SESSION.default_master_connection= 'name'; + +# replicate_do_db SET @@GLOBAL.replicate_do_db= 'named'; +SET @@GLOBAL.``.replicate_do_db= 'unnamed'; SELECT @@GLOBAL.name.replicate_do_db; SELECT @@GLOBAL.replicate_do_db; -# MDEV-40297: Explicit empty connection name SELECT @@GLOBAL.``.replicate_do_db; +# slave_parallel_mode +SET @@GLOBAL.slave_parallel_mode= 'minimal'; +SET @@GLOBAL.``.slave_parallel_mode= 'aggressive'; + +SELECT @@GLOBAL.name.slave_parallel_mode; +SELECT @@GLOBAL.slave_parallel_mode; +SELECT @@GLOBAL.``.slave_parallel_mode; + # Cleanup -RESET SLAVE ALL; +RESET SLAVE 'name' ALL; SET @@SESSION.default_master_connection= @saved_default_master_connection; +SET @@GLOBAL.slave_parallel_mode= @saved_slave_parallel_mode; SET @@GLOBAL.replicate_do_db= @saved_replicate_do_db; ---echo # End of 10.11 tests +--echo # End of 11.4 tests diff --git a/mysql-test/suite/vcol/t/races.test b/mysql-test/suite/vcol/t/races.test index b6b42b1771d..9df6d6997e3 100644 --- a/mysql-test/suite/vcol/t/races.test +++ b/mysql-test/suite/vcol/t/races.test @@ -5,6 +5,10 @@ source include/have_innodb.inc; source include/have_debug_sync.inc; create table t1 (f text, vf tinytext as (f), key (vf(64))) engine=innodb; insert t1 (f) values ('foo'); +# Purge may open t1 into the table cache after FLUSH TABLES +--disable_query_log +set global innodb_max_purge_lag_wait= 0; +--enable_query_log flush tables; connect con1,localhost,root,,test; set debug_sync='TABLE_after_field_clone WAIT_FOR go'; diff --git a/mysql-test/suite/versioning/r/partition,heap.rdiff b/mysql-test/suite/versioning/r/partition,heap.rdiff index de35764d602..d137c248e39 100644 --- a/mysql-test/suite/versioning/r/partition,heap.rdiff +++ b/mysql-test/suite/versioning/r/partition,heap.rdiff @@ -221,3 +221,12 @@ # INSERT .. ON DUPLICATE KEY UPDATE (ODKU) set timestamp= unix_timestamp('2000-01-01 00:00:00'); create or replace table t1 (x int primary key) with system versioning +@@ -3663,7 +3456,7 @@ + # (except heap combination) + explain extended select max(id) from t1; + id select_type table type possible_keys key key_len ref rows filtered Extra +-1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Select tables optimized away ++1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 + Warnings: + Note 1003 select max(`test`.`t1`.`id`) AS `max(id)` from `test`.`t1` + drop tables t1; diff --git a/mysql-test/suite/versioning/r/partition.result b/mysql-test/suite/versioning/r/partition.result index 03f3610e9f7..dc95fd582e0 100644 --- a/mysql-test/suite/versioning/r/partition.result +++ b/mysql-test/suite/versioning/r/partition.result @@ -373,10 +373,10 @@ set timestamp=unix_timestamp('2001-02-04 10:30:10'); update t1 set i=6 where i=5; select subpartition_name, partition_description, table_rows from information_schema.partitions where table_schema='test' and table_name='t1'; subpartition_name partition_description table_rows -p1sp0 2001-02-04 00:00:00 1 -p1sp1 2001-02-04 00:00:00 0 p0sp0 2001-02-05 00:00:00 1 p0sp1 2001-02-05 00:00:00 1 +p1sp0 2001-02-04 00:00:00 1 +p1sp1 2001-02-04 00:00:00 0 p2sp0 2001-02-06 00:00:00 0 p2sp1 2001-02-06 00:00:00 0 pnsp0 CURRENT 0 @@ -386,8 +386,8 @@ i 1 select * from t1 partition (p0); i -5 2 +5 select * from t1 partition (p2); i alter table t1 rebuild partition p0, p1, p2; @@ -396,8 +396,8 @@ i 1 select * from t1 partition (p0); i -5 2 +5 select * from t1 partition (p2); i ## pruning check @@ -409,7 +409,7 @@ i 6 explain partitions select * from t1; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 pn_pnsp0,pn_pnsp1 ALL NULL NULL NULL NULL # Using where +1 SIMPLE t1 pn_pnsp0,pn_pnsp1 ALL NULL NULL NULL NULL # explain partitions select * from t1 for system_time as of '2001-02-04 10:20:30'; id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE t1 p0_p0sp0,p0_p0sp1,p2_p2sp0,p2_p2sp1,pn_pnsp0,pn_pnsp1 ALL NULL NULL NULL NULL # Using where @@ -1124,6 +1124,23 @@ update v1 set f= ''; ERROR HY000: Table 't1' was locked with a READ lock and can't be updated delete from v1; ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +# system_versioning_asof is ignored by DML, so it does not cause ER_TABLE_NOT_LOCKED_FOR_WRITE +create or replace view v1 as select * from t1; +set system_versioning_asof= '2000-01-01 00:00:00'; +select * from v1; +f +select * from v1 for update; +f +update v1 set f= ''; +affected rows: 1 +info: Rows matched: 1 Changed: 1 Inserted: 1 Warnings: 0 +Warnings: +Warning 4114 Versioned table `test`.`t1`: last HISTORY partition (`p2`) is out of LIMIT, need more HISTORY partitions +delete from v1; +affected rows: 1 +Warnings: +Warning 4114 Versioned table `test`.`t1`: last HISTORY partition (`p2`) is out of LIMIT, need more HISTORY partitions +set system_versioning_asof= default; drop view v1; drop table t1; # @@ -2200,9 +2217,9 @@ t1 CREATE TABLE `t1` ( PARTITION `pn` CURRENT ENGINE = X) select * from t1; x -3 13 23 +3 select * from t1 for system_time all order by x; x 3 @@ -3733,22 +3750,199 @@ alter table t1 add y int partition by system_time interval 1 hour starts '2000-01-01 00:00:00'; affected rows: 3 info: Records: 3 Duplicates: 0 Warnings: 0 -select x, y, row_end from t1 for system_time all order by x; +select x, y, if(current_row_ts(row_end), 'CURRENT', row_end) as row_end +from t1 for system_time all order by x; x y row_end 0 NULL 2000-01-01 01:30:00.000000 1 NULL 2000-01-01 02:30:00.000000 -2 NULL 2106-02-07 06:28:15.999999 +2 NULL CURRENT # LIMIT with DROP COLUMN: the table is rebuilt alter table t1 drop y partition by system_time limit 100; affected rows: 3 info: Records: 3 Duplicates: 0 Warnings: 0 -select x, row_end from t1 for system_time all order by x; +select x, if(current_row_ts(row_end), 'CURRENT', row_end) as row_end +from t1 for system_time all order by x; x row_end 0 2000-01-01 01:30:00.000000 1 2000-01-01 02:30:00.000000 -2 2106-02-07 06:28:15.999999 +2 CURRENT set timestamp= default; set time_zone= default; drop table t1; # End of 10.11 tests +# +# MDEV-17613 Read sole now-partition for non-historical versioned query +# +create table t1 ( +id int, +ts timestamp(6) generated always as row start invisible, +te timestamp(6) generated always as row end invisible, +primary key (id, te), +period for system_time (ts, te) +) with system versioning +partition by system_time interval 1 month ( +partition p0 history, partition p1 history, partition pcur current); +insert t1 values (1), (2), (0); +delete from t1 where id = 1; +select * from t1; +id +0 +2 +# Below there should be: Select tables optimized away +# (except heap combination) +explain extended select max(id) from t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +Warnings: +Note 1003 select max(`test`.`t1`.`id`) AS `max(id)` from `test`.`t1` +# Exact COUNT(*) (MyISAM, heap) must not count history rows +select count(*) from t1; +count(*) +2 +# UPDATE without WHERE changes only current rows +update t1 set id= id + 10; +select * from t1; +id +10 +12 +select count(*) from t1 for system_time all; +count(*) +5 +# sql_safe_updates rejects DELETE without WHERE even with LIMIT, +# as for a non-versioned table +set sql_safe_updates= 1; +delete from t1 limit 1; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +set sql_safe_updates= default; +# DELETE without WHERE removes only current rows +delete from t1; +select count(*) from t1; +count(*) +0 +select count(*) from t1 for system_time all; +count(*) +5 +drop tables t1; +# Multi-table DELETE/UPDATE under sql_safe_updates: checks use the +# written WHERE and the join access types, as before +create table t1 (x int) with system versioning +partition by system_time (partition p0 history, partition pn current); +create table t2 (y int); +insert into t1 values (1), (2); +insert into t2 values (1), (2); +set sql_safe_updates= 1; +delete t1 from t1, t2; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +update t1, t2 set t1.x= t2.y; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +# LIMIT does not exempt multi-table UPDATE from the full join check +update t1, t2 set t1.x= t2.y order by t1.x limit 1; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +set sql_safe_updates= default; +drop tables t1, t2; +# As for a non-versioned table, an index on row_end does not exempt +# UPDATE/DELETE without WHERE: UPDATE needs LIMIT, DELETE is rejected +create table t1 ( +x int, +ts timestamp(6) as row start invisible, +te timestamp(6) as row end invisible, +period for system_time (ts, te), +key (te) +) with system versioning +partition by system_time (partition p0 history, partition pn current); +insert into t1 values (1), (2); +set sql_safe_updates= 1; +update t1 set x= x + 1; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +delete from t1; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +update t1 set x= x + 1 limit 1; +delete from t1 limit 1; +ERROR HY000: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column +set sql_safe_updates= default; +drop table t1; +# Corner cases: outer and sysvar-set FOR SYSTEM_TIME +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (id int) +with system versioning +partition by system_time interval 1 hour +starts timestamp'2000-01-01 00:00:00' ( +partition p0 history, +partition p1 history, +partition pn current); +insert into t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set id= 2; +# implicit query reads only the current partition (id=2): +select id from t1; +id +2 +# outer FOR SYSTEM_TIME must propagate into derived t1 and read history (id=1): +select * from (select id from t1) for system_time as of timestamp'2000-01-01 01:00:00' as dt; +id +1 +# system_versioning_asof must be honored and read history (id=1): +set system_versioning_asof= '2000-01-01 01:00:00'; +select id from t1; +id +1 +set system_versioning_asof= default; +# re-execution reads only the current partition (id=2): +prepare s from 'select id from t1'; +execute s; +id +2 +execute s; +id +2 +deallocate prepare s; +create procedure sp() select id from t1; +call sp; +id +2 +call sp; +id +2 +drop procedure sp; +# the same through merged derived table and view (id=2): +create view v1 as select id from t1; +prepare s from 'select id from (select id from t1) dt'; +execute s; +id +2 +execute s; +id +2 +deallocate prepare s; +prepare s from 'select id from v1'; +execute s; +id +2 +execute s; +id +2 +deallocate prepare s; +create procedure sp() select id from (select id from t1) dt; +call sp; +id +2 +call sp; +id +2 +drop procedure sp; +drop view v1; +# re-execution with system_versioning_asof reads history (id=1): +set system_versioning_asof= '2000-01-01 01:00:00'; +prepare s from 'select id from t1'; +execute s; +id +1 +execute s; +id +1 +deallocate prepare s; +set system_versioning_asof= default; +set timestamp= default; +drop table t1; +# End of 11.4 tests set global innodb_stats_persistent= @save_persistent; diff --git a/mysql-test/suite/versioning/r/sysvars.result b/mysql-test/suite/versioning/r/sysvars.result index a5a3f79990c..1766cdb2725 100644 --- a/mysql-test/suite/versioning/r/sysvars.result +++ b/mysql-test/suite/versioning/r/sysvars.result @@ -254,3 +254,24 @@ a DROP TABLE t1; SET system_versioning_asof= DEFAULT; # End of 10.4 tests +# +# MDEV-17613 Read sole now-partition for non-historical versioned query +# +# system_versioning_asof does not act as explicit FOR SYSTEM_TIME AS OF +create table t1 (x int) with system versioning; +insert into t1 values (1); +set system_versioning_asof= '2000-01-01 00:00:00'; +# Allowed, unlike explicit FOR SYSTEM_TIME AS OF: +select * from t1 for update; +x +select * from t1 for system_time as of timestamp'2000-01-01 00:00:00' for update; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +# Not printed as FOR SYSTEM_TIME AS OF: +explain extended select * from t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +Warnings: +Note 1003 select 1 AS `x` from dual where 0 +set system_versioning_asof= default; +drop table t1; +# End of 10.11 tests diff --git a/mysql-test/suite/versioning/t/partition.test b/mysql-test/suite/versioning/t/partition.test index 9460c82b44a..7a7b3d402d7 100644 --- a/mysql-test/suite/versioning/t/partition.test +++ b/mysql-test/suite/versioning/t/partition.test @@ -26,14 +26,17 @@ partition by range columns (x) ( partition p1 values less than (1000)); insert into t1 values (3), (300); +--sorted_result select * from t1; select * from t1 partition (p0); select * from t1 partition (p1); delete from t1; +--sorted_result select * from t1; select * from t1 partition (p0); select * from t1 partition (p1); +--sorted_result select * from t1 for system_time all; select * from t1 partition (p0) for system_time all; select * from t1 partition (p1) for system_time all; @@ -140,6 +143,7 @@ alter table t1 drop partition p1; --error ER_VERS_WRONG_PARTS alter table t1 drop partition p0; +--sorted_result select x from t1; --echo # rename works @@ -217,6 +221,7 @@ update t1 set x = x + 1; --source suite/versioning/wait_system_clock.inc +--sorted_result execute select_p0; execute select_pn; @@ -252,11 +257,14 @@ show create table t1; alter table t1 drop partition non_existent; insert into t1 values (1), (2), (3), (4), (5), (6); +--sorted_result select * from t1 partition (pn); delete from t1 where x < 4; delete from t1; --echo # You see warning above ^ +--sorted_result select * from t1 partition (p0); +--sorted_result select * from t1 partition (p1); insert into t1 values (7), (8); @@ -289,13 +297,17 @@ set @ts=(select partition_description from information_schema.partitions where table_schema='test' and table_name='t1' and partition_name='p0'); alter table t1 add column b int; +--sorted_result select partition_name,partition_ordinal_position,partition_method,timediff(partition_description, @ts) from information_schema.partitions where table_schema='test' and table_name='t1'; alter table t1 add partition (partition p1 history, partition p2 history); +--sorted_result select partition_name,partition_ordinal_position,partition_method,timediff(partition_description, @ts) from information_schema.partitions where table_schema='test' and table_name='t1'; alter table t1 drop partition p0; +--sorted_result select partition_name,partition_ordinal_position,partition_method,timediff(partition_description, @ts) from information_schema.partitions where table_schema='test' and table_name='t1'; --error ER_VERS_DROP_PARTITION_INTERVAL alter table t1 drop partition p2; +--sorted_result select partition_name,partition_ordinal_position,partition_method,timediff(partition_description, @ts) from information_schema.partitions where table_schema='test' and table_name='t1'; # @@ -311,7 +323,9 @@ insert t1 values (1); delete from t1; set timestamp=unix_timestamp('2001-02-04 10:20:50'); insert t1 values (2); delete from t1; +--sorted_result select subpartition_name, partition_description, table_rows from information_schema.partitions where table_schema='test' and table_name='t1'; +--sorted_result select * from t1 partition (p1); set timestamp=unix_timestamp('2001-02-04 10:20:55'); @@ -321,13 +335,16 @@ insert t1 values (4),(5); set timestamp=unix_timestamp('2001-02-04 10:30:10'); update t1 set i=6 where i=5; +--sorted_result select subpartition_name, partition_description, table_rows from information_schema.partitions where table_schema='test' and table_name='t1'; select * from t1 partition (p1); +--sorted_result select * from t1 partition (p0); select * from t1 partition (p2); alter table t1 rebuild partition p0, p1, p2; select * from t1 partition (p1); +--sorted_result select * from t1 partition (p0); select * from t1 partition (p2); @@ -467,6 +484,7 @@ set timestamp= unix_timestamp('2001-01-01 00:00:02'); update t1 set i= i + 1; select *, row_end from t1 partition (p0); +--sorted_result select *, row_end from t1 partition (p1); set timestamp= unix_timestamp('2000-01-01 00:00:00'); @@ -485,6 +503,7 @@ update t1 set i= i + 1; set timestamp= unix_timestamp('2000-01-04 00:00:01'); update t1 set i= i + 1; +--sorted_result select *, row_end from t1 partition (p0); select *, row_end from t1 partition (p1); @@ -520,7 +539,9 @@ subpartition by key (x) subpartitions 2; insert into t1 (x) values (1), (2), (3), (4), (5); +--sorted_result select * from t1 partition (pnsp0); +--sorted_result select * from t1 partition (pnsp1); delete from t1 where x < 3; @@ -530,6 +551,7 @@ delete from t1; --echo # You see warning above ^ (no matter if nothing was deleted) select * from t1 partition (p0sp0); select * from t1 partition (p0sp1); +--sorted_result select * from t1 partition (p1sp0); select * from t1 partition (p1sp1); @@ -874,6 +896,7 @@ create or replace table t1 ( insert into t1 (x) values (1), (2), (3), (4); update t1 set a= 'foo' limit 3; update t1 set a= 'bar' limit 4; +--sorted_result select * from t1; drop table t1; @@ -931,6 +954,7 @@ create or replace table t1 (pk int primary key, f int) engine=innodb partition by key() partitions 2; insert into t1 values (1,10),(2,20); --echo # expected to hit same partition +--sorted_result select * from t1 partition (p0); alter table t1 drop system versioning; @@ -970,6 +994,17 @@ update v1 set f= ''; --error ER_TABLE_NOT_LOCKED_FOR_WRITE delete from v1; +--echo # system_versioning_asof is ignored by DML, so it does not cause ER_TABLE_NOT_LOCKED_FOR_WRITE +create or replace view v1 as select * from t1; +set system_versioning_asof= '2000-01-01 00:00:00'; +select * from v1; +select * from v1 for update; +--enable_info +update v1 set f= ''; +delete from v1; +--disable_info +set system_versioning_asof= default; + # cleanup drop view v1; drop table t1; @@ -1131,17 +1166,20 @@ replace into t1 select seq from seq_1_to_70; # Puts 60 rows into p1 replace into t1 select seq from seq_1_to_60; +--sorted_result select partition_name, table_rows from information_schema.partitions where table_name = 't1'; rollback; +--sorted_result select partition_name, table_rows from information_schema.partitions where table_name = 't1'; # Should put 10 rows into the empty partition p0 replace into t1 select seq from seq_1_to_10; +--sorted_result select partition_name, table_rows from information_schema.partitions where table_name = 't1'; @@ -1623,6 +1661,7 @@ starts '2000-01-01 00:00:00'; --replace_result $default_engine DEFAULT_ENGINE show create table t1; select * from t1 partition (p0); +--sorted_result select * from t1 partition (p1); select * from t1 partition (p2); select * from t1 partition (pn); @@ -1638,6 +1677,7 @@ update t1 set x= x + 1; update t1 set x= x + 1; --echo # You see warning above ^ select * from t1 partition (p0); +--sorted_result select * from t1 partition (p1); select * from t1 partition (p2); select * from t1 partition (p3); @@ -1792,6 +1832,7 @@ select * from tp1 for system_time all; --replace_result $default_engine X ' PAGE_CHECKSUM=1' '' show create table t1; +--sorted_result select * from t1; select * from t1 for system_time all order by x; @@ -2647,6 +2688,7 @@ eval insert into t1 (a,row_start,row_end) values ('p1', '2021-09-30', '2021-10-01 10:00:00'), ('overflows, so also p1','2021-09-30', '2021-10-10 10:00:00'), ('pn, current', '2021-09-30', $sys_time_max); +--sorted_result select table_name,partition_name,partition_ordinal_position,partition_method,partition_description,table_rows from information_schema.partitions where table_schema='test'; drop table t1; @@ -2790,6 +2832,7 @@ update t1 set x= 3; --echo # CREATE result: row 1 got into p1 select *, row_start, row_end from t1 partition (p0); +--sorted_result select *, row_start, row_end from t1 partition (p1); flush tables; --echo # For CREATE pruning is affected by current timestamp, but SELECT works @@ -2809,6 +2852,7 @@ partition by system_time interval 1 hour ( --echo # ALTER result: row 1 got into p1 Select *, row_start, row_end from t1 partition (p0); +--sorted_result Select *, row_start, row_end from t1 partition (p1); flush tables; Explain partitions select * from t1 for system_time as of '2000-01-01 00:59:59'; @@ -2948,17 +2992,155 @@ select x, row_end from t1 partition (p2); alter table t1 add y int partition by system_time interval 1 hour starts '2000-01-01 00:00:00'; --disable_info -select x, y, row_end from t1 for system_time all order by x; +select x, y, if(current_row_ts(row_end), 'CURRENT', row_end) as row_end +from t1 for system_time all order by x; --echo # LIMIT with DROP COLUMN: the table is rebuilt --enable_info alter table t1 drop y partition by system_time limit 100; --disable_info -select x, row_end from t1 for system_time all order by x; +select x, if(current_row_ts(row_end), 'CURRENT', row_end) as row_end +from t1 for system_time all order by x; set timestamp= default; set time_zone= default; drop table t1; --echo # End of 10.11 tests +--echo # +--echo # MDEV-17613 Read sole now-partition for non-historical versioned query +--echo # +create table t1 ( + id int, + ts timestamp(6) generated always as row start invisible, + te timestamp(6) generated always as row end invisible, + primary key (id, te), + period for system_time (ts, te) +) with system versioning +partition by system_time interval 1 month ( + partition p0 history, partition p1 history, partition pcur current); + +insert t1 values (1), (2), (0); +delete from t1 where id = 1; +--sorted_result +select * from t1; +--echo # Below there should be: Select tables optimized away +--echo # (except heap combination) +explain extended select max(id) from t1; +--echo # Exact COUNT(*) (MyISAM, heap) must not count history rows +select count(*) from t1; +--echo # UPDATE without WHERE changes only current rows +update t1 set id= id + 10; +--sorted_result +select * from t1; +select count(*) from t1 for system_time all; +--echo # sql_safe_updates rejects DELETE without WHERE even with LIMIT, +--echo # as for a non-versioned table +set sql_safe_updates= 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +delete from t1 limit 1; +set sql_safe_updates= default; +--echo # DELETE without WHERE removes only current rows +delete from t1; +select count(*) from t1; +select count(*) from t1 for system_time all; + +drop tables t1; + +--echo # Multi-table DELETE/UPDATE under sql_safe_updates: checks use the +--echo # written WHERE and the join access types, as before +create table t1 (x int) with system versioning +partition by system_time (partition p0 history, partition pn current); +create table t2 (y int); +insert into t1 values (1), (2); +insert into t2 values (1), (2); +set sql_safe_updates= 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +delete t1 from t1, t2; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +update t1, t2 set t1.x= t2.y; +--echo # LIMIT does not exempt multi-table UPDATE from the full join check +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +update t1, t2 set t1.x= t2.y order by t1.x limit 1; +set sql_safe_updates= default; +drop tables t1, t2; + +--echo # As for a non-versioned table, an index on row_end does not exempt +--echo # UPDATE/DELETE without WHERE: UPDATE needs LIMIT, DELETE is rejected +create table t1 ( + x int, + ts timestamp(6) as row start invisible, + te timestamp(6) as row end invisible, + period for system_time (ts, te), + key (te) +) with system versioning +partition by system_time (partition p0 history, partition pn current); +insert into t1 values (1), (2); +set sql_safe_updates= 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +update t1 set x= x + 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +delete from t1; +update t1 set x= x + 1 limit 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +delete from t1 limit 1; +set sql_safe_updates= default; +drop table t1; + +--echo # Corner cases: outer and sysvar-set FOR SYSTEM_TIME +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (id int) +with system versioning +partition by system_time interval 1 hour +starts timestamp'2000-01-01 00:00:00' ( + partition p0 history, + partition p1 history, + partition pn current); +insert into t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set id= 2; +--echo # implicit query reads only the current partition (id=2): +select id from t1; +--echo # outer FOR SYSTEM_TIME must propagate into derived t1 and read history (id=1): +select * from (select id from t1) for system_time as of timestamp'2000-01-01 01:00:00' as dt; +--echo # system_versioning_asof must be honored and read history (id=1): +set system_versioning_asof= '2000-01-01 01:00:00'; +select id from t1; +set system_versioning_asof= default; +--echo # re-execution reads only the current partition (id=2): +prepare s from 'select id from t1'; +execute s; +execute s; +deallocate prepare s; +create procedure sp() select id from t1; +call sp; +call sp; +drop procedure sp; +--echo # the same through merged derived table and view (id=2): +create view v1 as select id from t1; +prepare s from 'select id from (select id from t1) dt'; +execute s; +execute s; +deallocate prepare s; +prepare s from 'select id from v1'; +execute s; +execute s; +deallocate prepare s; +create procedure sp() select id from (select id from t1) dt; +call sp; +call sp; +drop procedure sp; +drop view v1; +--echo # re-execution with system_versioning_asof reads history (id=1): +set system_versioning_asof= '2000-01-01 01:00:00'; +prepare s from 'select id from t1'; +execute s; +execute s; +deallocate prepare s; +set system_versioning_asof= default; +set timestamp= default; +drop table t1; + +--echo # End of 11.4 tests + set global innodb_stats_persistent= @save_persistent; --source suite/versioning/common_finish.inc diff --git a/mysql-test/suite/versioning/t/sysvars.test b/mysql-test/suite/versioning/t/sysvars.test index 91dd278a2e1..de3ddf758a5 100644 --- a/mysql-test/suite/versioning/t/sysvars.test +++ b/mysql-test/suite/versioning/t/sysvars.test @@ -204,3 +204,21 @@ DROP TABLE t1; SET system_versioning_asof= DEFAULT; --echo # End of 10.4 tests + +--echo # +--echo # MDEV-17613 Read sole now-partition for non-historical versioned query +--echo # +--echo # system_versioning_asof does not act as explicit FOR SYSTEM_TIME AS OF +create table t1 (x int) with system versioning; +insert into t1 values (1); +set system_versioning_asof= '2000-01-01 00:00:00'; +--echo # Allowed, unlike explicit FOR SYSTEM_TIME AS OF: +select * from t1 for update; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +select * from t1 for system_time as of timestamp'2000-01-01 00:00:00' for update; +--echo # Not printed as FOR SYSTEM_TIME AS OF: +explain extended select * from t1; +set system_versioning_asof= default; +drop table t1; + +--echo # End of 10.11 tests diff --git a/mysys_ssl/CMakeLists.txt b/mysys_ssl/CMakeLists.txt index 1c3f60b5bb0..6a9c29449c6 100644 --- a/mysys_ssl/CMakeLists.txt +++ b/mysys_ssl/CMakeLists.txt @@ -28,7 +28,6 @@ SET(MYSYS_SSL_HIDDEN_SOURCES my_sha384.cc my_sha512.cc my_md5.cc - openssl.c ) SET(MYSYS_SSL_SOURCES diff --git a/mysys_ssl/my_crypt.cc b/mysys_ssl/my_crypt.cc index 346eff51b1b..3bb30b190aa 100644 --- a/mysys_ssl/my_crypt.cc +++ b/mysys_ssl/my_crypt.cc @@ -30,27 +30,17 @@ #include -#define CTX_ALIGN 16 - class MyCTX { public: - char ctx_buf[EVP_CIPHER_CTX_SIZE + CTX_ALIGN]; EVP_CIPHER_CTX* ctx; MyCTX() { -#if CTX_ALIGN > 0 - uintptr_t p= ((uintptr_t)ctx_buf + (CTX_ALIGN - 1)) & ~(CTX_ALIGN - 1); - ctx = reinterpret_cast(p); -#else - ctx = (EVP_CIPHER_CTX*)ctx_buf; -#endif - - EVP_CIPHER_CTX_init(ctx); + ctx= EVP_CIPHER_CTX_new(); } virtual ~MyCTX() { - EVP_CIPHER_CTX_reset(ctx); + EVP_CIPHER_CTX_free(ctx); ERR_remove_state(0); } @@ -58,6 +48,8 @@ public: uint klen, const uchar *iv, uint ivlen) { compile_time_assert(MY_AES_CTX_SIZE >= sizeof(MyCTX)); + if (unlikely(!ctx)) + return MY_AES_OPENSSL_ERROR; if (unlikely(!cipher)) return MY_AES_BAD_KEYSIZE; @@ -116,9 +108,11 @@ public: DBUG_ASSERT(ivlen == 0 || ivlen == sizeof(oiv)); int res= MyCTX::init(cipher, encrypt, key, klen, iv, ivlen); + if (res) + return res; EVP_CIPHER_CTX_set_padding(ctx, 0); - return res; + return MY_AES_OK; } /** Update last partial source block, stored in source_tail array. */ @@ -214,10 +208,12 @@ public: { compile_time_assert(MY_AES_CTX_SIZE >= sizeof(MyCTX_gcm)); int res= MyCTX::init(cipher, encrypt, key, klen, iv, ivlen); + if (res) + return res; int real_ivlen= EVP_CIPHER_CTX_iv_length(ctx); aad= iv + real_ivlen; aadlen= ivlen - real_ivlen; - return res; + return MY_AES_OK; } int update(const uchar *src, uint slen, uchar *dst, uint *dlen) override @@ -299,20 +295,30 @@ int my_aes_crypt_init(void *ctx, enum my_aes_mode mode, int flags, new (ctx) MyCTX_nopad(); else new (ctx) MyCTX(); - return ((MyCTX*)ctx)->init(ciphers[mode](klen), flags & 1, - key, klen, iv, ivlen); + MyCTX *c= (MyCTX*)ctx; + int res= c->init(ciphers[mode](klen), flags & 1, key, klen, iv, ivlen); + if (res) + { + /* release ctx but keep the object: a later update()/finish() from a + caller that ignored the error is then a safe no-op, not a double free */ + EVP_CIPHER_CTX_free(c->ctx); + c->ctx= NULL; + } + return res; } int my_aes_crypt_update(void *ctx, const uchar *src, uint slen, uchar *dst, uint *dlen) { - return ((MyCTX*)ctx)->update(src, slen, dst, dlen); + MyCTX *c= (MyCTX*)ctx; + return c->ctx ? c->update(src, slen, dst, dlen) : MY_AES_OPENSSL_ERROR; } int my_aes_crypt_finish(void *ctx, uchar *dst, uint *dlen) { - int res= ((MyCTX*)ctx)->finish(dst, dlen); - ((MyCTX*)ctx)->~MyCTX(); + MyCTX *c= (MyCTX*)ctx; + int res= c->ctx ? c->finish(dst, dlen) : MY_AES_OPENSSL_ERROR; + c->~MyCTX(); return res; } diff --git a/mysys_ssl/my_md5.cc b/mysys_ssl/my_md5.cc index 75995bccf90..22682ddf705 100644 --- a/mysys_ssl/my_md5.cc +++ b/mysys_ssl/my_md5.cc @@ -19,70 +19,60 @@ @file @brief - Wrapper functions for OpenSSL and YaSSL. Also provides a Compatibility layer - to make available YaSSL's MD5 implementation. + Wrapper functions for the OpenSSL/WolfSSL MD5 implementation. */ #include #include #include - -#if defined(HAVE_WOLFSSL) -#include #include -typedef wc_Md5 EVP_MD_CTX; -static void md5_init(EVP_MD_CTX *context) -{ - wc_InitMd5(context);; -} -static void md5_input(EVP_MD_CTX *context, const uchar *buf, unsigned len) -{ - wc_Md5Update(context, buf, len); -} - -static void md5_result(EVP_MD_CTX *context, uchar digest[MD5_HASH_SIZE]) -{ - wc_Md5Final(context,digest); -} - -#elif defined(HAVE_OPENSSL) +#if defined(HAVE_OPENSSL10) && !defined(HAVE_WOLFSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) +/* + OpenSSL 1.0.x in FIPS mode aborts on the low-level MD5 API (md5 is not used + for crypto here), so go through EVP with the non-FIPS flag. MDEV-7788. +*/ #include -#include - -static void md5_init(EVP_MD_CTX *context) +typedef EVP_MD_CTX MY_MD5_CTX; +static void md5_init(MY_MD5_CTX *context) { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_MD *md5; - EVP_MD_CTX_init(context); - /* Ok to ignore FIPS: MD5 is not used for crypto here */ - /* In OpenSSL 3.0.0+ it is a different EVP_MD provider */ - md5 = EVP_MD_fetch(NULL, "MD5", "fips=no"); - EVP_DigestInit_ex(context, md5, NULL); - EVP_MD_free(md5); -#else EVP_MD_CTX_init(context); #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW - /* Ok to ignore FIPS: MD5 is not used for crypto here */ - /* In OpenSSL 1.1.1 the non FIPS allowed flag is context specific */ EVP_MD_CTX_set_flags(context, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); #endif EVP_DigestInit_ex(context, EVP_md5(), NULL); -#endif } -static void md5_input(EVP_MD_CTX *context, const uchar *buf, unsigned len) +static void md5_input(MY_MD5_CTX *context, const uchar *buf, unsigned len) { EVP_DigestUpdate(context, buf, len); } -static void md5_result(EVP_MD_CTX *context, uchar digest[MD5_HASH_SIZE]) +static void md5_result(MY_MD5_CTX *context, uchar digest[MD5_HASH_SIZE]) { EVP_DigestFinal_ex(context, digest, NULL); - EVP_MD_CTX_reset(context); + EVP_MD_CTX_cleanup(context); } -#endif /* HAVE_WOLFSSL */ +#else +#include +typedef MD5_CTX MY_MD5_CTX; +static void md5_init(MY_MD5_CTX *context) +{ + MD5_Init(context); +} + +static void md5_input(MY_MD5_CTX *context, const uchar *buf, unsigned len) +{ + MD5_Update(context, buf, len); +} + +static void md5_result(MY_MD5_CTX *context, uchar digest[MD5_HASH_SIZE]) +{ + MD5_Final(digest, context); +} +#endif /** Wrapper function to compute MD5 message digest. @@ -95,11 +85,10 @@ static void md5_result(EVP_MD_CTX *context, uchar digest[MD5_HASH_SIZE]) */ void my_md5(uchar *digest, const char *buf, size_t len) { - char ctx_buf[EVP_MD_CTX_SIZE]; - EVP_MD_CTX * const ctx= (EVP_MD_CTX*)ctx_buf; - md5_init(ctx); - md5_input(ctx, (const uchar *)buf, (uint) len); - md5_result(ctx, digest); + MY_MD5_CTX context; + md5_init(&context); + md5_input(&context, (const uchar *)buf, (uint) len); + md5_result(&context, digest); } @@ -119,34 +108,33 @@ void my_md5_multi(uchar *digest, ...) { va_list args; const uchar *str; - char ctx_buf[EVP_MD_CTX_SIZE]; - EVP_MD_CTX * const ctx= (EVP_MD_CTX*)ctx_buf; + MY_MD5_CTX context; va_start(args, digest); - md5_init(ctx); + md5_init(&context); for (str= va_arg(args, const uchar*); str; str= va_arg(args, const uchar*)) - md5_input(ctx, str, (uint) va_arg(args, size_t)); + md5_input(&context, str, (uint) va_arg(args, size_t)); - md5_result(ctx, digest); + md5_result(&context, digest); va_end(args); } size_t my_md5_context_size() { - return EVP_MD_CTX_SIZE; + return sizeof(MY_MD5_CTX); } void my_md5_init(void *context) { - md5_init((EVP_MD_CTX *)context); + md5_init((MY_MD5_CTX *)context); } void my_md5_input(void *context, const uchar *buf, size_t len) { - md5_input((EVP_MD_CTX *)context, buf, (uint) len); + md5_input((MY_MD5_CTX *)context, buf, (uint) len); } void my_md5_result(void *context, uchar *digest) { - md5_result((EVP_MD_CTX *)context, digest); + md5_result((MY_MD5_CTX *)context, digest); } diff --git a/mysys_ssl/openssl.c b/mysys_ssl/openssl.c deleted file mode 100644 index 3890e3524be..00000000000 --- a/mysys_ssl/openssl.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - Copyright (c) 2017, MariaDB Corporation. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include -#include -#include - -/* - The check is only done for OpenSSL 1.1.x. - It could run for OpenSSL 1.0.x but it doesn't make much sense - and it hits this bug: - https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/1594748 -*/ - -#ifndef HAVE_OPENSSL11 -int check_openssl_compatibility() -{ - return 0; -} -#else -#include - -static uint testing; -static size_t alloc_size, alloc_count; - -static void *coc_malloc(size_t size -#ifndef LIBRESSL_VERSION_NUMBER - , const char *f __attribute__((unused)), - int l __attribute__((unused)) -#endif -) -{ - if (unlikely(testing)) - { - alloc_size+= size; - alloc_count++; - } - return malloc(size); -} - -static void *coc_realloc(void *addr, size_t num -#ifndef LIBRESSL_VERSION_NUMBER - , const char *file __attribute__((unused)), - int line __attribute__((unused)) -#endif -) -{ - return realloc(addr, num); -} - -static void coc_free(void *addr -#ifndef LIBRESSL_VERSION_NUMBER - , const char *file __attribute__((unused)), - int line __attribute__((unused)) -#endif -) -{ - free(addr); -} - -int check_openssl_compatibility() -{ - EVP_CIPHER_CTX *evp_ctx; - EVP_MD_CTX *md5_ctx; - - if (!CRYPTO_set_mem_functions(coc_malloc, coc_realloc, coc_free)) - return 0; - - testing= 1; - alloc_size= alloc_count= 0; - evp_ctx= EVP_CIPHER_CTX_new(); - EVP_CIPHER_CTX_free(evp_ctx); - if (alloc_count != 1 || !alloc_size || alloc_size > EVP_CIPHER_CTX_SIZE) - return 1; - - alloc_size= alloc_count= 0; - md5_ctx= EVP_MD_CTX_new(); - EVP_MD_CTX_free(md5_ctx); - if (alloc_count != 1 || !alloc_size || alloc_size > EVP_MD_CTX_SIZE) - return 1; - - testing= 0; - return 0; -} -#endif diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 1d208dbe86a..2cdeb78111f 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -1358,7 +1358,7 @@ Event_job_data::execute(THD *thd, bool drop) { String sp_sql; #ifndef NO_EMBEDDED_ACCESS_CHECKS - Security_context event_sctx, *save_sctx= NULL; + Security_context *save_sctx= NULL; #endif List empty_item_list; bool ret= TRUE; diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index d66707c0eaa..e6db04e4125 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -24,9 +24,9 @@ #include "event_parse_data.h" #include "thr_lock.h" /* thr_lock_type */ +#include "sql_class.h" /* Security_context */ class Field; -class THD; class Time_zone; struct TABLE; @@ -173,6 +173,17 @@ public: class Stored_program_creation_ctx *creation_ctx; + /* + DEFINER security context, active in thd->security_ctx during execute(). + Kept here, not in execute(), to outlive it: a concurrent PROCESSLIST + scan may still read it. See Event_worker_thread::run(). + + acl_getroot() aliases user/host/ip to definer_user/definer_host + instead of copying them, so they must live in this object. + Never call event_sctx.destroy(): it would free strings not owned here. + */ + Security_context event_sctx; + Event_job_data(); virtual bool diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index ef410b7d99b..cb81ae2dec3 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -319,6 +319,9 @@ Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event) res= job_data.execute(thd, event->dropped); + /* Test hook: execute() has returned, job_data is still alive. */ + DEBUG_SYNC(thd, "event_worker_thread_after_execute_returned"); + print_warnings(thd, &job_data); if (res && global_system_variables.log_warnings > 2) diff --git a/sql/ha_partition.h b/sql/ha_partition.h index 414a8eb027b..de373fa4fea 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -1632,9 +1632,9 @@ public: ha_rows part_records(partition_element *part_elem) { DBUG_ASSERT(m_part_info); - uint32 sub_factor= m_part_info->num_subparts ? m_part_info->num_subparts : 1; - uint32 part_id= part_elem->id * sub_factor; - uint32 part_id_end= part_id + sub_factor; + uint32 part_id_end; + uint32 part_id= part_elem->bitmap_range(m_part_info->num_subparts, + part_id_end); DBUG_ASSERT(part_id_end <= m_tot_parts); ha_rows part_recs= 0; for (; part_id < part_id_end; ++part_id) diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 46ea68d6a31..ce6f3c6fb32 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4111,14 +4111,6 @@ static int init_common_variables() exit(1); } -#ifdef HAVE_OPENSSL - if (check_openssl_compatibility()) - { - sql_print_error("Incompatible OpenSSL version. Cannot continue..."); - exit(1); - } -#endif - if (init_thread_environment() || mysql_init_variables()) exit(1); diff --git a/sql/opt_range.cc b/sql/opt_range.cc index d29296863b4..d7820e35d3c 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -4012,7 +4012,7 @@ void store_key_image_to_rec(Field *field, uchar *ptr, uint len) struct st_part_prune_param; struct st_part_opt_info; -typedef void (*mark_full_part_func)(partition_info*, uint32); +typedef void (*mark_full_part_func)(struct st_part_prune_param*, uint32); /* Partition pruning operation context @@ -4073,6 +4073,12 @@ typedef struct st_part_prune_param /* Iterator to be used to obtain the "current" set of used partitions */ PARTITION_ITERATOR part_iter; + /* + Initialized bitmap of read_partitions size. + find_used_partitions() will save the set of used partitions here. + */ + MY_BITMAP parts_bitmap; + /* Initialized bitmap of num_subparts size */ MY_BITMAP subparts_bitmap; @@ -4088,7 +4094,6 @@ static int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge); static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, List &merges); -static void mark_all_partitions_as_used(partition_info *part_info); #ifndef DBUG_OFF static void print_partitioning_index(KEY_PART *parts, KEY_PART *parts_end); @@ -4105,13 +4110,30 @@ static void dbug_print_singlepoint_range(SEL_ARG **start, uint num); @param table Table to perform partition pruning for @param pprune_cond Condition to use for partition pruning + @param[in] part_info->read_partitions + Candidate partitions to scan; normally all partitions (or the + PARTITION() set) + + @param[out] part_info->read_partitions + Narrowed to the partitions that may hold records matching the + condition. For a versioned table with history partitions + accessed without FOR SYSTEM_TIME, PARTITION() and + @@system_versioning_asof, also narrowed to the now-partition, + with or without a condition. Never widened. + @note This function assumes that lock_partitions are setup when it is invoked. The function analyzes the condition, finds partitions that need to be used to retrieve the records that match the condition, and - marks them as used by setting appropriate bit in part_info->read_partitions - In the worst case all partitions are marked as used. If the table is not - yet locked, it will also unset bits in part_info->lock_partitions that is - not set in read_partitions. + narrows part_info->read_partitions to them. + If the table is not yet locked, it will also unset bits in + part_info->lock_partitions that is not set in read_partitions. + + The matching set is computed independently of the incoming + read_partitions/lock_partitions: the analysis walks the whole partition + list (or only the value interval derived from the condition for + range-capable partitioning), and the result is then intersected into + read_partitions. A restriction already present in read_partitions + therefore narrows the outcome but does not reduce the pruning work. This function returns promptly if called for non-partitioned table. @@ -4129,11 +4151,40 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) if (!part_info) DBUG_RETURN(FALSE); /* not a partitioned table */ - if (!pprune_cond) + /* + Read only now_part, as vers_setup_conds() did not add the row_end condition. + Done here as prune_partitions() runs on each execution, also for merged + derived tables and views, for which vers_setup_conds() is not called + again. + */ + if (TABLE_LIST *tl= table->pos_in_table_list) { - mark_all_partitions_as_used(part_info); - DBUG_RETURN(FALSE); + if (part_info->vers_info && !tl->partition_names && + !tl->vers_conditions.was_set() && !tl->vers_conditions.from_sysvar) + { + DBUG_ASSERT(part_info->vers_info->initialized()); + uint32 end; + const uint32 start= + part_info->vers_info->now_part->bitmap_range(part_info->num_subparts, + end); + for (uint32 i= 0; i < part_info->read_partitions.n_bits; i++) + if (i < start || i >= end) + bitmap_clear_bit(&part_info->read_partitions, i); + } } + + /* + Must be a subset of the locked partitions. + lock_partitions contains the partitions marked by explicit partition + selection (... t PARTITION (pX) ...) and we must only use partitions + within that set. read_partitions may be wider on entry: e.g. + check_insert_or_replace_autoincrement() sets all bits for + INSERT/REPLACE ... SELECT. Done before the early returns below. + */ + bitmap_intersect(&part_info->read_partitions, &part_info->lock_partitions); + + if (!pprune_cond) + DBUG_RETURN(FALSE); PART_PRUNE_PARAM prune_param; MEM_ROOT alloc; @@ -4149,7 +4200,6 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) if (create_partition_index_description(&prune_param)) { - mark_all_partitions_as_used(part_info); free_root(&alloc,MYF(0)); // Return memory & allocator DBUG_RETURN(FALSE); } @@ -4174,7 +4224,7 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) thd->no_errors=1; // Don't warn about NULL thd->mem_root=&alloc; - bitmap_clear_all(&part_info->read_partitions); + bitmap_clear_all(&prune_param.parts_bitmap); prune_param.key= prune_param.range_param.key_parts; SEL_TREE *tree; @@ -4251,20 +4301,15 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) all_used: retval= FALSE; // some partitions are used - mark_all_partitions_as_used(prune_param.part_info); + goto all_used_end; end: + bitmap_intersect(&prune_param.part_info->read_partitions, + &prune_param.parts_bitmap); +all_used_end: dbug_tmp_restore_column_maps(&table->read_set, &table->write_set, old_sets); thd->no_errors=0; thd->mem_root= range_par->old_root; free_root(&alloc,MYF(0)); // Return memory & allocator - /* - Must be a subset of the locked partitions. - lock_partitions contains the partitions marked by explicit partition - selection (... t PARTITION (pX) ...) and we must only use partitions - within that set. - */ - bitmap_intersect(&prune_param.part_info->read_partitions, - &prune_param.part_info->lock_partitions); /* If not yet locked, also prune partitions to lock if not UPDATEing partition key fields. This will also prune lock_partitions if we are under @@ -4330,20 +4375,21 @@ static void store_selargs_to_rec(PART_PRUNE_PARAM *ppar, SEL_ARG **start, /* Mark a partition as used in the case when there are no subpartitions */ -static void mark_full_partition_used_no_parts(partition_info* part_info, +static void mark_full_partition_used_no_parts(PART_PRUNE_PARAM *ppar, uint32 part_id) { DBUG_ENTER("mark_full_partition_used_no_parts"); DBUG_PRINT("enter", ("Mark partition %u as used", part_id)); - bitmap_set_bit(&part_info->read_partitions, part_id); + bitmap_set_bit(&ppar->parts_bitmap, part_id); DBUG_VOID_RETURN; } /* Mark a partition as used in the case when there are subpartitions */ -static void mark_full_partition_used_with_parts(partition_info *part_info, +static void mark_full_partition_used_with_parts(PART_PRUNE_PARAM *ppar, uint32 part_id) { + partition_info *part_info= ppar->part_info; uint32 start= part_id * part_info->num_subparts; uint32 end= start + part_info->num_subparts; DBUG_ENTER("mark_full_partition_used_with_parts"); @@ -4351,7 +4397,7 @@ static void mark_full_partition_used_with_parts(partition_info *part_info, for (; start != end; start++) { DBUG_PRINT("info", ("1:Mark subpartition %u as used", start)); - bitmap_set_bit(&part_info->read_partitions, start); + bitmap_set_bit(&ppar->parts_bitmap, start); } DBUG_VOID_RETURN; } @@ -4379,7 +4425,7 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, MY_BITMAP all_merges; uint bitmap_bytes; my_bitmap_map *bitmap_buf; - uint n_bits= ppar->part_info->read_partitions.n_bits; + uint n_bits= ppar->parts_bitmap.n_bits; bitmap_bytes= bitmap_buffer_size(n_bits); if (!(bitmap_buf= (my_bitmap_map*) alloc_root(ppar->range_param.mem_root, bitmap_bytes))) @@ -4405,15 +4451,15 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, } if (res != -1) - bitmap_intersect(&all_merges, &ppar->part_info->read_partitions); + bitmap_intersect(&all_merges, &ppar->parts_bitmap); if (bitmap_is_clear_all(&all_merges)) return 0; - bitmap_clear_all(&ppar->part_info->read_partitions); + bitmap_clear_all(&ppar->parts_bitmap); } - memcpy(ppar->part_info->read_partitions.bitmap, all_merges.bitmap, + memcpy(ppar->parts_bitmap.bitmap, all_merges.bitmap, bitmap_bytes); return 1; } @@ -4566,6 +4612,8 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) -1 Couldn't infer any partition pruning "intervals" from the passed SEL_ARG* tree (which means that all partitions should be marked as used) Marking partitions as used is the responsibility of the caller. + + The set of used partitions is saved in PART_PRUNE_PARAM::parts_bitmap. */ static @@ -4784,7 +4832,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) { for (uint i= 0; i < ppar->part_info->num_subparts; i++) if (bitmap_is_set(&ppar->subparts_bitmap, i)) - bitmap_set_bit(&ppar->part_info->read_partitions, + bitmap_set_bit(&ppar->parts_bitmap, part_id * ppar->part_info->num_subparts + i); } goto pop_and_go_right; @@ -4846,7 +4894,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != NOT_A_PARTITION_ID) { - bitmap_set_bit(&part_info->read_partitions, + bitmap_set_bit(&ppar->parts_bitmap, part_id * part_info->num_subparts + subpart_id); } res= 1; /* Some partitions were marked as used */ @@ -4901,7 +4949,7 @@ process_next_key_part: while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != NOT_A_PARTITION_ID) { - ppar->mark_full_partition_used(ppar->part_info, part_id); + ppar->mark_full_partition_used(ppar, part_id); found= TRUE; } res= MY_TEST(found); @@ -4930,13 +4978,6 @@ pop_and_go_right: } -static void mark_all_partitions_as_used(partition_info *part_info) -{ - bitmap_copy(&(part_info->read_partitions), - &(part_info->lock_partitions)); -} - - /* Check if field types allow to construct partitioning index description @@ -5041,6 +5082,15 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) !(ppar->is_subpart_keypart= (my_bool*)alloc_root(alloc, sizeof(my_bool)* total_parts))) return TRUE; + + { + my_bitmap_map *buf; + uint32 bufsize= bitmap_buffer_size(part_info->read_partitions.n_bits); + if (!(buf= (my_bitmap_map*) alloc_root(alloc, bufsize))) + return TRUE; + my_bitmap_init(&ppar->parts_bitmap, buf, + part_info->read_partitions.n_bits); + } if (ppar->subpart_fields) { diff --git a/sql/partition_element.h b/sql/partition_element.h index ea1452027ff..05453c6ca28 100644 --- a/sql/partition_element.h +++ b/sql/partition_element.h @@ -172,6 +172,19 @@ public: DBUG_ASSERT(ev->col_val_array); return ev->col_val_array[idx]; } + + /* + Leaf-bitmap (read/lock_partitions) range covered by this top-level + partition: a single bit if not subpartitioned, num_subparts bits + otherwise. Returns the first bit; bit_no_end receives one past the last. + */ + uint32 bitmap_range(uint num_subparts, uint32 &bit_no_end) const + { + uint32 sub_factor= num_subparts ? num_subparts : 1; + uint32 first= id * sub_factor; + bit_no_end= first + sub_factor; + return first; + } }; #endif /* PARTITION_ELEMENT_INCLUDED */ diff --git a/sql/partition_info.cc b/sql/partition_info.cc index b7cb7a5282e..1f4e8fcc849 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -1024,10 +1024,9 @@ void partition_info::vers_check_limit(THD *thd) of partitions. */ #ifndef DBUG_OFF - const uint32 sub_factor= num_subparts ? num_subparts : 1; - uint32 part_id= vers_info->hist_part->id * sub_factor; - const uint32 part_id_end __attribute__((unused)) = part_id + sub_factor; - DBUG_ASSERT(part_id_end <= num_parts * sub_factor); + uint32 part_id_end; + vers_info->hist_part->bitmap_range(num_subparts, part_id_end); + DBUG_ASSERT(part_id_end <= get_tot_partitions()); #endif ha_partition *hp= (ha_partition*)(table->file); diff --git a/sql/proxy_protocol.cc b/sql/proxy_protocol.cc index 117d9b9a107..a0dabd0a66a 100644 --- a/sql/proxy_protocol.cc +++ b/sql/proxy_protocol.cc @@ -92,6 +92,15 @@ static int parse_v1_header(char *hdr, size_t len, proxy_peer_info *peer_info) } +/* + PROXY v2 header is 16 bytes, followed by the address block with source and + destination address and port. Whole header length for TCP over IPv4, IPv6. +*/ +#define PROXY_V2_HEADER_LEN 16 +#define PROXY_V2_TCP4_LEN (PROXY_V2_HEADER_LEN + 2 * 4 + 2 * 2) +#define PROXY_V2_TCP6_LEN (PROXY_V2_HEADER_LEN + 2 * 16 + 2 * 2) + + /* Parse proxy protocol V2 (binary) header */ @@ -129,14 +138,14 @@ static int parse_v2_header(uchar *hdr, size_t len,proxy_peer_info *peer_info) switch (fam) { case 0x11: /* TCPv4 */ - if (len < 25) + if (len < PROXY_V2_TCP4_LEN) return -1; sin->sin_family= AF_INET; memcpy(&(sin->sin_addr), hdr + 16, 4); peer_info->port= (hdr[24] << 8) + hdr[25]; break; case 0x21: /* TCPv6 */ - if (len < 49) + if (len < PROXY_V2_TCP6_LEN) return -1; sin6->sin6_family= AF_INET6; memcpy(&(sin6->sin6_addr), hdr + 16, 16); @@ -201,8 +210,8 @@ int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info) /* Read until end of header (newline character)*/ while(pos < sizeof(hdr) - 1) { - long len= (long)vio_read(vio, hdr + pos, 1); - if (len < 0) + ssize_t len= vio_read(vio, hdr + pos, 1); + if (len != 1) return -1; pos++; if (hdr[pos-1] == '\n') @@ -216,10 +225,9 @@ int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info) } else // if (have_v2_header) { -#define PROXY_V2_HEADER_LEN 16 /* read off 16 bytes of the header.*/ ssize_t len= vio_read(vio, hdr + pos, PROXY_V2_HEADER_LEN - pos); - if (len < 0) + if (len != PROXY_V2_HEADER_LEN - ssize_t(pos)) return -1; // 2 last bytes are the length in network byte order of the part following header ushort trail_len= ((ushort)hdr[PROXY_V2_HEADER_LEN-2] << 8) + hdr[PROXY_V2_HEADER_LEN-1]; @@ -228,7 +236,7 @@ int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info) if (trail_len > 0) { len= vio_read(vio, hdr + PROXY_V2_HEADER_LEN, trail_len); - if (len < 0) + if (len != trail_len) return -1; } pos= PROXY_V2_HEADER_LEN + trail_len; diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index 57d98e6c3f1..f8e84ae3a7d 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -378,6 +378,7 @@ class Master_info : public Slave_reporting_capability it must be ignored similarly to the replicate-same-server-id rule. */ bool do_accept_own_server_id= false; + bool semi_sync_enabled= false; /* Set to 1 when semi_sync is enabled. Set to 0 if there is any transmit problems to the slave, in which case any furter semi-sync reply is diff --git a/sql/semisync_slave.cc b/sql/semisync_slave.cc index 32e53dab758..45e03fa6f4c 100644 --- a/sql/semisync_slave.cc +++ b/sql/semisync_slave.cc @@ -46,7 +46,8 @@ int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff, system_status_var *status_var, enum_var_type scope) { - local_semi_sync_enabled= repl_semisync_slave.get_slave_enabled(); + //TODO MDEV-40941 + local_semi_sync_enabled= repl_semisync_slave.m_slave_enabled; var->type= SHOW_BOOL; var->value= (char*) &local_semi_sync_enabled; return 0; @@ -55,14 +56,14 @@ int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff, int Repl_semi_sync_slave::slave_read_sync_header(const uchar *header, unsigned long total_len, - int *semi_flags, + Master_info *mi, const uchar **payload, unsigned long *payload_len) { int read_res = 0; DBUG_ENTER("Repl_semi_sync_slave::slave_read_sync_header"); - if (get_slave_enabled()) + if (get_slave_enabled(mi)) { if (!DBUG_IF("semislave_corrupt_log") && header[0] == k_packet_magic_num) @@ -76,9 +77,9 @@ int Repl_semi_sync_slave::slave_read_sync_header(const uchar *header, semi_sync_need_reply)); if (semi_sync_need_reply) - *semi_flags |= SEMI_SYNC_NEED_ACK; + mi->semi_ack |= SEMI_SYNC_NEED_ACK; if (is_delay_master()) - *semi_flags |= SEMI_SYNC_SLAVE_DELAY_SYNC; + mi->semi_ack |= SEMI_SYNC_SLAVE_DELAY_SYNC; } else { @@ -111,7 +112,7 @@ void Repl_semi_sync_slave::slave_start(Master_info *mi) */ bool semi_sync= global_rpl_semi_sync_slave_enabled; - set_slave_enabled(semi_sync); + set_slave_enabled(mi, semi_sync); mi->semi_sync_reply_enabled= 0; sql_print_information("Slave I/O thread: Start %s replication to\ @@ -127,7 +128,7 @@ void Repl_semi_sync_slave::slave_start(Master_info *mi) void Repl_semi_sync_slave::slave_stop(Master_info *mi) { - if (get_slave_enabled()) + if (get_slave_enabled(mi)) { #ifdef ENABLED_DEBUG_SYNC /* @@ -144,7 +145,7 @@ void Repl_semi_sync_slave::slave_stop(Master_info *mi) kill_connection(mi); } - set_slave_enabled(0); + set_slave_enabled(mi, 0); } void Repl_semi_sync_slave::slave_reconnect(Master_info *mi) @@ -153,7 +154,7 @@ void Repl_semi_sync_slave::slave_reconnect(Master_info *mi) Start semi-sync either if it globally enabled or if was enabled before the reconnect. */ - if (global_rpl_semi_sync_slave_enabled || get_slave_enabled()) + if (global_rpl_semi_sync_slave_enabled || get_slave_enabled(mi)) slave_start(mi); } @@ -215,7 +216,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi) MYSQL_ROW row; const char *query; - if (!get_slave_enabled()) + if (!get_slave_enabled(mi)) return 0; query= "SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled'"; @@ -223,7 +224,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi) !(res= mysql_store_result(mysql))) { sql_print_error("Execution failed on master: %s, error :%s", query, mysql_error(mysql)); - set_slave_enabled(0); + set_slave_enabled(mi, 0); return 1; } @@ -234,7 +235,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi) if (!row) sql_print_warning("Master server does not support semi-sync, " "fallback to asynchronous replication"); - set_slave_enabled(0); + set_slave_enabled(mi, 0); mysql_free_result(res); return 0; } @@ -253,7 +254,7 @@ int Repl_semi_sync_slave::request_transmit(Master_info *mi) if (mysql_real_query(mysql, query, (ulong)strlen(query))) { sql_print_error("%s on master failed", query); - set_slave_enabled(0); + set_slave_enabled(mi, 0); return 1; } mi->semi_sync_reply_enabled= 1; @@ -276,7 +277,7 @@ int Repl_semi_sync_slave::slave_reply(Master_info *mi) int reply_res = 0; size_t name_len = strlen(binlog_filename); DBUG_ENTER("Repl_semi_sync_slave::slave_reply"); - DBUG_ASSERT(get_slave_enabled() && mi->semi_sync_reply_enabled); + DBUG_ASSERT(get_slave_enabled(mi) && mi->semi_sync_reply_enabled); /* Prepare the buffer of the reply. */ reply_buffer[REPLY_MAGIC_NUM_OFFSET] = k_packet_magic_num; diff --git a/sql/semisync_slave.h b/sql/semisync_slave.h index 79949067de8..776891e1e09 100644 --- a/sql/semisync_slave.h +++ b/sql/semisync_slave.h @@ -17,6 +17,7 @@ #ifndef SEMISYNC_SLAVE_H #define SEMISYNC_SLAVE_H +#ifdef HAVE_REPLICATION #include "semisync.h" #include "my_global.h" @@ -45,12 +46,12 @@ public: */ int init_object(); - inline bool get_slave_enabled() { - return m_slave_enabled; + inline bool get_slave_enabled(Master_info *mi) { + return mi->semi_sync_enabled; } - void set_slave_enabled(bool enabled) { - m_slave_enabled = enabled; + void set_slave_enabled(Master_info *mi, bool enabled) { + m_slave_enabled= mi->semi_sync_enabled= enabled; } inline bool is_delay_master(){ @@ -71,8 +72,8 @@ public: * Input: * header - (IN) packet header pointer * total_len - (IN) total packet length: metadata + payload - * semi_flags - (IN) store flags: SEMI_SYNC_SLAVE_DELAY_SYNC and - SEMI_SYNC_NEED_ACK + * mi - (IN) store flags: SEMI_SYNC_SLAVE_DELAY_SYNC and + SEMI_SYNC_NEED_ACK, to Master_info::semi_ack * payload - (IN) payload: the replication event * payload_len - (IN) payload length * @@ -80,7 +81,7 @@ public: * 0: success; non-zero: error */ int slave_read_sync_header(const uchar *header, unsigned long total_len, - int *semi_flags, + Master_info *mi, const uchar **payload, unsigned long *payload_len); /* A slave replies to the master indicating its replication process. It @@ -94,10 +95,11 @@ public: int request_transmit(Master_info *mi); void kill_connection(Master_info *mi); + /// @deprecated For `Rpl_semi_sync_slave_status` compatibility only + bool m_slave_enabled; private: /* True when init_object has been called */ bool m_init_done; - bool m_slave_enabled; /* semi-sync is enabled on the slave */ bool m_delay_master; unsigned int m_kill_conn_timeout; }; @@ -115,4 +117,5 @@ extern unsigned long long rpl_semi_sync_slave_send_ack; extern int rpl_semi_sync_enabled(THD *thd, SHOW_VAR *var, void *buff, system_status_var *status_var, enum_var_type scope); +#endif /* HAVE_REPLICATION */ #endif /* SEMISYNC_SLAVE_H */ diff --git a/sql/slave.cc b/sql/slave.cc index d0bb3b4e924..f5d14d1be50 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -4977,7 +4977,7 @@ Stopping slave I/O thread due to out-of-memory error from master"); if (repl_semisync_slave. slave_read_sync_header((const uchar*) mysql->net.read_pos + 1, event_len, - &(mi->semi_ack), &event_buf, &event_len)) + mi, &event_buf, &event_len)) { mi->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, NULL, ER_THD(thd, ER_SLAVE_FATAL_ERROR), @@ -5034,7 +5034,7 @@ Stopping slave I/O thread due to out-of-memory error from master"); goto err; } - if (repl_semisync_slave.get_slave_enabled() && + if (repl_semisync_slave.get_slave_enabled(mi) && mi->semi_sync_reply_enabled && (mi->semi_ack & SEMI_SYNC_NEED_ACK)) { @@ -5078,7 +5078,7 @@ Stopping slave I/O thread due to out-of-memory error from master"); master info only when ack is needed. This may lead to at least one group transaction delay but affords better performance improvement. */ - (!repl_semisync_slave.get_slave_enabled() || + (!repl_semisync_slave.get_slave_enabled(mi) || (!(mi->semi_ack & SEMI_SYNC_SLAVE_DELAY_SYNC) || (mi->semi_ack & (SEMI_SYNC_NEED_ACK)))) && (DBUG_IF("failed_flush_master_info") || @@ -6754,7 +6754,7 @@ dbug_gtid_accept: */ mi->do_accept_own_server_id= (s_id == global_system_variables.server_id && - repl_semisync_slave.get_slave_enabled() && opt_gtid_strict_mode && + repl_semisync_slave.get_slave_enabled(mi) && opt_gtid_strict_mode && mi->using_gtid != Master_info::USE_GTID_NO && !mysql_bin_log.check_strict_gtid_sequence(event_gtid.domain_id, event_gtid.server_id, diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 118b361eb79..85e5f006c29 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -244,6 +244,16 @@ void mysql_client_binlog_statement(THD* thd) thd->lex->comment.length : 2048), thd->lex->comment.str)); + /* + The parser refuses BINLOG in a routine body; this catches dynamic SQL + (EXECUTE IMMEDIATE, EXECUTE) run from a routine. + */ + if (thd->spcont) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "BINLOG"); + DBUG_VOID_RETURN; + } + if (check_global_access(thd, PRIV_STMT_BINLOG)) DBUG_VOID_RETURN; @@ -421,8 +431,12 @@ void mysql_client_binlog_statement(THD* thd) applied in PS mode. So, reset the current lex temporary to point to thd->main_lex before running ev->apply_event() and restore its original value on return. + + Likewise, point thd->stmt_arena to thd: otherwise a Query event's + statement leaks its allocations onto the PS's persistent arena. */ LEX *backup_lex; + SCOPE_VALUE(thd->stmt_arena, static_cast(thd)); thd->backup_and_reset_current_lex(&backup_lex); err= save_restore_context_apply_event(ev, rgi); diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 760516e6482..b634d778eb5 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -1195,11 +1195,6 @@ void Explain_select::print_explain_json(Explain_query *query, writer->add_member("outer_ref_condition"); write_item(writer, outer_ref_cond); } - if (pseudo_bits_cond) - { - writer->add_member("pseudo_bits_condition"); - write_item(writer, pseudo_bits_cond); - } /* we do not print HAVING which always evaluates to TRUE */ if (having || (having_value == Item::COND_FALSE)) diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 855f7d93d39..161b4fa00ab 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -248,7 +248,6 @@ public: /* Expensive constant condition */ Item *exec_const_cond; Item *outer_ref_cond; - Item *pseudo_bits_cond; /* HAVING condition */ Item *having; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a602632c615..7befbcb8c0b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -560,7 +560,7 @@ void JOIN::init(THD *thd_arg, List &fields_arg, first_select= sub_select; group_sent= 0; - outer_ref_cond= pseudo_bits_cond= NULL; + outer_ref_cond= NULL; in_to_exists_where= NULL; in_to_exists_having= NULL; emb_sjm_nest= NULL; @@ -980,6 +980,7 @@ bool vers_select_conds_t::init_from_sysvar(THD *thd) { vers_asof_timestamp_t &in= thd->variables.vers_asof_timestamp; type= (vers_system_time_t) in.type; + from_sysvar= (type != SYSTEM_TIME_UNSPECIFIED); delete_history= false; start.unit= VERS_TIMESTAMP; if (type != SYSTEM_TIME_UNSPECIFIED && type != SYSTEM_TIME_ALL) @@ -1302,6 +1303,21 @@ int SELECT_LEX::vers_setup_conds(THD *thd, TABLE_LIST *tables) DBUG_RETURN(-1); } +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info= table->table->part_info; + if (part_info && part_info->vers_info && + !table->partition_names && + !vers_conditions.was_set() && + !vers_conditions.from_sysvar) + { + /* + No row_end condition: prune_partitions() restricts read_partitions + to now_part on each execution, with the same conditions. + */ + vers_conditions.set_all(); + } +#endif + if (vers_conditions.is_set()) { if (vers_conditions.was_set() && @@ -14529,6 +14545,26 @@ bool build_tmp_join_prefix_cond(JOIN *join, JOIN_TAB *last_tab, Item **ret) } +/* + Find the last top level table in the join order that is neither + const nor a materialized semijoin nest. Return its index in + join->join_tab, or a value below join->const_tables when no such + table exists. +*/ + +static uint +get_last_top_base_tab_idx(const JOIN *join) +{ + uint i; + for (i= join->top_join_tab_count - 1; i >= join->const_tables; i--) + { + if (!join->join_tab[i].bush_children) + break; + } + return i; +} + + static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { @@ -14542,6 +14578,10 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) "attached_conditions_computation"); add_not_null_conds(join); table_map used_tables; + + /* Step #1 and Step #2 both decide from this index. */ + const uint last_top_base_tab_idx= get_last_top_base_tab_idx(join); + /* Step #1: Extract constant condition - Extract and check the constant part of the WHERE @@ -14607,40 +14647,36 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) join->exec_const_cond= const_cond; } - if (join->table_count != join->const_tables) { - COND *outer_ref_cond= make_cond_for_table(thd, cond, - join->const_table_map | - OUTER_REF_TABLE_BIT, - OUTER_REF_TABLE_BIT, - -1, FALSE, FALSE); - if (outer_ref_cond) + /* + Collect the conjuncts that are checked once for each execution + of the query, before the join reads a table that is not const. + Each refers to at least one pseudo table in pseudo_bits and to + no table that is not const. + + A conjunct that refers to a column of an outer query is always + collected, because that column has one value for each + execution. rand() takes a new value on each call, and a + conjunct that calls it is checked for each row of the last top + level table that is neither const nor a materialized semijoin + nest. When no such table exists, the join returns at most one + row, and its rand() conjuncts are collected here. + */ + table_map pseudo_bits= OUTER_REF_TABLE_BIT; + if (last_top_base_tab_idx < join->const_tables) + pseudo_bits= PSEUDO_TABLE_BITS; + COND *pseudo_cond= make_cond_for_table(thd, cond, + join->const_table_map | + pseudo_bits, + pseudo_bits, + -1, FALSE, FALSE); + if (pseudo_cond) { - add_cond_and_fix(thd, &outer_ref_cond, join->outer_ref_cond); - join->outer_ref_cond= outer_ref_cond; + add_cond_and_fix(thd, &pseudo_cond, join->outer_ref_cond); + join->outer_ref_cond= pseudo_cond; Json_writer_object trace(thd); - trace.add("outer_ref_cond", outer_ref_cond); - } - else if (thd->is_error()) - DBUG_RETURN(1); - } - else - { - COND *pseudo_bits_cond= - make_cond_for_table(thd, cond, - join->const_table_map | - PSEUDO_TABLE_BITS, - PSEUDO_TABLE_BITS, - -1, FALSE, FALSE); - if (pseudo_bits_cond) - { - add_cond_and_fix(thd, &pseudo_bits_cond, - join->pseudo_bits_cond); - join->pseudo_bits_cond= pseudo_bits_cond; - - Json_writer_object trace(thd); - trace.add("pseudo_bits_cond", pseudo_bits_cond); + trace.add("outer_ref_cond", pseudo_cond); } else if (thd->is_error()) DBUG_RETURN(1); @@ -14653,13 +14689,6 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) */ uint i; - for (i= join->top_join_tab_count - 1; i >= join->const_tables; i--) - { - if (!join->join_tab[i].bush_children) - break; - } - uint last_top_base_tab_idx= i; - table_map save_used_tables= 0; used_tables=((select->const_tables=join->const_table_map) | OUTER_REF_TABLE_BIT | RAND_TABLE_BIT); @@ -24080,12 +24109,11 @@ do_select(JOIN *join, Procedure *procedure) HAVING will be checked after processing aggregate functions, But WHERE should checked here (we alredy have read tables). Notice that make_join_select() splits all conditions in this case - into two groups exec_const_cond and outer_ref_cond. - If join->table_count == join->const_tables then it is - sufficient to check only the condition pseudo_bits_cond. + into two groups exec_const_cond and outer_ref_cond. exec_const_cond + is checked before the join starts, and it is sufficient to check + only the condition outer_ref_cond. */ - DBUG_ASSERT(join->outer_ref_cond == NULL); - if (!join->pseudo_bits_cond || join->pseudo_bits_cond->val_bool()) + if (!join->outer_ref_cond || join->outer_ref_cond->val_bool()) { // HAVING will be checked by end_select error= (*end_select)(join, 0, 0); @@ -31713,7 +31741,6 @@ int JOIN::save_explain_data_intern(Explain_query *output, xpl_sel->exec_const_cond= exec_const_cond; xpl_sel->outer_ref_cond= outer_ref_cond; - xpl_sel->pseudo_bits_cond= pseudo_bits_cond; if (tmp_having) xpl_sel->having= tmp_having; else diff --git a/sql/sql_select.h b/sql/sql_select.h index ccff0aab714..4086555d313 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1653,8 +1653,15 @@ public: ORDER *order, *group_list, *proc_param; //hold parameters of mysql_select COND *conds; // ---"--- Item *conds_history; // store WHERE for explain - COND *outer_ref_cond; /// *join_list; ///< list of joined tables in reverse order COND_EQUAL *cond_equal; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 2db3381de3f..dc4d3e3aeac 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -3453,6 +3453,13 @@ static my_bool processlist_callback(THD *tmp, processlist_callback_arg *arg) ulonglong max_counter; bool got_thd_data; + /* Test hook: pause after tmp_sctx is captured, before it is read. */ + DBUG_EXECUTE_IF("processlist_callback_pause_after_sctx_capture", + if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER && + tmp_sctx != &tmp->main_security_ctx) + DEBUG_SYNC(arg->thd, + "processlist_callback_captured_sctx");); + if (!thd_visible_in_processlist(arg->thd->security_ctx, tmp)) return 0; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 8b02697d243..40c6f2d811a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8542,6 +8542,8 @@ table_index_name: binlog_base64_event: BINLOG_SYM TEXT_STRING_sys { + if (Lex->sphead) + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "BINLOG")); Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; Lex->comment= $2; Lex->ident.str= NULL; @@ -8550,6 +8552,8 @@ binlog_base64_event: | BINLOG_SYM '@' ident_or_text ',' '@' ident_or_text { + if (Lex->sphead) + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "BINLOG")); Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; Lex->comment= $3; Lex->ident= $6; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 2e93a890c5b..dce5fd81138 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2503,7 +2503,7 @@ Sys_var_slave_parallel_mode::global_update(THD *thd, set_var *var) Master_info *mi; bool res= false; - if (!base_name->length) + if (!base_name->str) base_name= &thd->variables.default_master_connection; mysql_mutex_unlock(&LOCK_global_system_variables); @@ -2549,7 +2549,7 @@ Sys_var_slave_parallel_mode::global_value_ptr(THD *thd, enum_slave_parallel_mode val= (enum_slave_parallel_mode)opt_slave_parallel_mode; - if (!base_name->length) + if (!base_name->str) base_name= &thd->variables.default_master_connection; mysql_mutex_unlock(&LOCK_global_system_variables); @@ -5742,7 +5742,7 @@ bool Sys_var_rpl_filter::global_update(THD *thd, set_var *var) bool result= true; // Assume error LEX_CSTRING *base_name= &var->base; - if (!base_name->length) + if (!base_name->str) base_name= &thd->variables.default_master_connection; mysql_mutex_unlock(&LOCK_global_system_variables); diff --git a/sql/table.h b/sql/table.h index 2bebc2f86cf..68d9b18bb62 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2345,11 +2345,47 @@ public: struct vers_select_conds_t { + /* + Effective system-time selection used to build the actual row condition + (row_start/row_end predicate). is_set() tests it against SYSTEM_TIME_UNSPECIFIED. + set_all() forces it to SYSTEM_TIME_ALL in three situations: + + 1) once the row_start/row_end predicate for the requested time has been + generated and attached to the query - marking it applied so a later pass + or the next prepared-statement execution neither rebuilds nor re-applies + it (guarded by "if (type == SYSTEM_TIME_ALL) continue"); + + 2) a versioned table whose history is stored in partitions is queried with + an explicit PARTITION(...) clause - the named partitions are read as-is, + without a versioning predicate; + + 3) the same kind of table accessed without FOR SYSTEM_TIME, PARTITION(...) + and @@system_versioning_asof - + read_partitions is narrowed to the now-partition, which takes the place + of the row_end predicate. + */ vers_system_time_t type; + /* + Originally requested system-time selection, set by init() from an explicit + FOR SYSTEM_TIME or DELETE HISTORY clause, or copied with the conditions of + an outer derived table or view. Unlike 'type' it is NOT overwritten by + set_all(), so it still reflects the request after the predicate has been + applied. + Tested by was_set(); used by print() to reconstruct the FOR SYSTEM_TIME + clause. + */ vers_system_time_t orig_type; bool used:1; bool delete_history:1; bool has_param:1; + /* + Set by init_from_sysvar() when @@system_versioning_asof supplied the + selection. Tested to keep the now-partition narrowing off, so the query + still reads history. Unlike orig_type it affects neither print() nor + the write lock check, and it persists to prepared-statement + re-execution, where init_from_sysvar() is skipped. + */ + bool from_sysvar:1; Vers_history_point start; Vers_history_point end; Lex_ident_column name; @@ -2366,6 +2402,7 @@ struct vers_select_conds_t used= false; delete_history= false; has_param= false; + from_sysvar= false; start.empty(); end.empty(); } @@ -2381,6 +2418,7 @@ struct vers_select_conds_t delete_history= (type == SYSTEM_TIME_HISTORY || type == SYSTEM_TIME_BEFORE); has_param= false; + from_sysvar= false; start= _start; end= _end; name= _name; diff --git a/storage/connect/mysql-test/connect/r/xml2.result b/storage/connect/mysql-test/connect/r/xml2.result index 5ce32e14277..d21b60ccd63 100644 --- a/storage/connect/mysql-test/connect/r/xml2.result +++ b/storage/connect/mysql-test/connect/r/xml2.result @@ -396,3 +396,56 @@ CAST(@a AS CHAR CHARACTER SET latin1) +# +# MDEV-41405 ASAN stack-buffer-overflow TDBXML::Initialize/TDBXML::OpenDB +# after insert into CONNECT table +# +CREATE TABLE t1 (id INT) +ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='x.xml' + OPTION_LIST='XmlDB=root89,xmlsup=libxml2'; +INSERT INTO t1 VALUES (1); +DROP TABLE t1; +CREATE TABLE t2 (id INT) +ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='x.xml' + OPTION_LIST='XmlDB=XMLDB,xmlsup=libxml2'; +INSERT INTO t2 VALUES (2); +ERROR HY000: Got error 174 'Missing XMLDB' from CONNECT +DROP TABLE t2; +CREATE TABLE t3 (id INT) +ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=XMLDB,xmlsup=libxml2'; +CREATE TABLE t4 (id INT) +ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=XMLDB,xmlsup=libxml2'; +CREATE TABLE t5 (id INT) +ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=/XMLDB,xmlsup=libxml2'; +INSERT INTO t3 VALUES (3); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +DROP TABLE t3, t4, t5; +SET @a=LOAD_FILE('MYSQLD_DATADIR/test/y.xml'); +SELECT REPLACE(@a, REPEAT('A', 240), 'XMLDB') AS y; +y + + + + + 3 + + + + + 4 + + + + + 5 + + + + +# +# End of 10.11 tests +# diff --git a/storage/connect/mysql-test/connect/t/xml2.test b/storage/connect/mysql-test/connect/t/xml2.test index 3c6acb82b88..57d20f71794 100644 --- a/storage/connect/mysql-test/connect/t/xml2.test +++ b/storage/connect/mysql-test/connect/t/xml2.test @@ -318,3 +318,52 @@ SELECT CAST(@a AS CHAR CHARACTER SET latin1); --remove_file $MYSQLD_DATADIR/test/xsample.xml --remove_file $MYSQLD_DATADIR/test/latin1.xml --remove_file $MYSQLD_DATADIR/test/cp1251.xml + +--echo # +--echo # MDEV-41405 ASAN stack-buffer-overflow TDBXML::Initialize/TDBXML::OpenDB +--echo # after insert into CONNECT table +--echo # +# Create x.xml so that the inserts below go into an existing file +CREATE TABLE t1 (id INT) + ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='x.xml' + OPTION_LIST='XmlDB=root89,xmlsup=libxml2'; +INSERT INTO t1 VALUES (1); +DROP TABLE t1; + +let $xmldb= `SELECT REPEAT('A', 240)`; +--replace_result $xmldb XMLDB +eval CREATE TABLE t2 (id INT) + ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='x.xml' + OPTION_LIST='XmlDB=$xmldb,xmlsup=libxml2'; +--replace_regex /A{60,}/XMLDB/ +--error ER_GET_ERRMSG +INSERT INTO t2 VALUES (2); +DROP TABLE t2; +--remove_file $MYSQLD_DATADIR/test/x.xml + +# Long XmlDB node created by t3 must be found by t4 and t5 +--replace_result $xmldb XMLDB +eval CREATE TABLE t3 (id INT) + ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=$xmldb,xmlsup=libxml2'; +--replace_result $xmldb XMLDB +eval CREATE TABLE t4 (id INT) + ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=$xmldb,xmlsup=libxml2'; +--replace_result $xmldb XMLDB +eval CREATE TABLE t5 (id INT) + ENGINE=CONNECT TABLE_TYPE=XML FILE_NAME='y.xml' + OPTION_LIST='XmlDB=/$xmldb,xmlsup=libxml2'; +INSERT INTO t3 VALUES (3); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +DROP TABLE t3, t4, t5; +--chmod 0777 $MYSQLD_DATADIR/test/y.xml +--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR +--eval SET @a=LOAD_FILE('$MYSQLD_DATADIR/test/y.xml') +SELECT REPLACE(@a, REPEAT('A', 240), 'XMLDB') AS y; +--remove_file $MYSQLD_DATADIR/test/y.xml + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/storage/connect/tabxml.cpp b/storage/connect/tabxml.cpp index 188dbcdde02..ed952988adc 100644 --- a/storage/connect/tabxml.cpp +++ b/storage/connect/tabxml.cpp @@ -819,6 +819,23 @@ int TDBXML::LoadTableFile(PGLOBAL g, char *filename) return rc; } // end of LoadTableFile +/***********************************************************************/ +/* Return name as an Xpath. If name is not an Xpath, construct one */ +/* that will find it anywhere. */ +/***********************************************************************/ +static char *MakeXpath(PGLOBAL g, PCSZ name) + { + size_t len = strlen(name) + 3; + char *xpath = (char*)PlugSubAlloc(g, NULL, len); + + if (!strchr(name, '/')) + snprintf(xpath, len, "//%s", name); + else + snprintf(xpath, len, "%s", name); + + return xpath; + } // end of MakeXpath + /***********************************************************************/ /* Initialize the processing of the XML file. */ /* Note: this function can be called several times, eventally before */ @@ -850,7 +867,7 @@ bool TDBXML::Initialize(PGLOBAL g) #else if (!Root) { #endif - char tabpath[64], filename[_MAX_PATH]; + char filename[_MAX_PATH]; // We used the file name relative to recorded datapath PlugSetPath(filename, Xfile, GetPath()); @@ -866,15 +883,8 @@ bool TDBXML::Initialize(PGLOBAL g) goto error; } // endif Root - // If tabname is not an Xpath, - // construct one that will find it anywhere - if (!strchr(Tabname, '/')) - snprintf(tabpath, sizeof(tabpath), "//%s", Tabname); - else - snprintf(tabpath, sizeof(tabpath), "%s", Tabname); - // Evaluate table xpath - if ((TabNode = Root->SelectSingleNode(g, tabpath))) { + if ((TabNode = Root->SelectSingleNode(g, MakeXpath(g, Tabname)))) { if (TabNode->GetType() != XML_ELEMENT_NODE) { snprintf(g->Message, sizeof(g->Message), MSG(BAD_NODE_TYPE), TabNode->GetType()); goto error; @@ -882,15 +892,7 @@ bool TDBXML::Initialize(PGLOBAL g) } else if (Mode == MODE_INSERT && XmlDB) { // We are adding a new table to a multi-table file - - // If XmlDB is not an Xpath, - // construct one that will find it anywhere - if (!strchr(XmlDB, '/')) - strcat(strcpy(tabpath, "//"), XmlDB); - else - strcpy(tabpath, XmlDB); - - if (!(DBnode = Root->SelectSingleNode(g, tabpath))) { + if (!(DBnode = Root->SelectSingleNode(g, MakeXpath(g, XmlDB)))) { // DB node does not exist yet; we cannot create it // because we don't know where it should be placed snprintf(g->Message, sizeof(g->Message), MSG(MISSING_NODE), XmlDB, Xfile); diff --git a/storage/duckdb/cmake/duckdb.cmake b/storage/duckdb/cmake/duckdb.cmake index 3987960b4a2..22c4ba7579d 100644 --- a/storage/duckdb/cmake/duckdb.cmake +++ b/storage/duckdb/cmake/duckdb.cmake @@ -104,7 +104,10 @@ ExternalProject_Add(duckdb_build -DBUILD_TPCE=OFF -DEXTENSION_STATIC_BUILD=1 "-DDUCKDB_EXTENSION_CONFIGS=${CMAKE_CURRENT_SOURCE_DIR}/cmake/duckdb_extensions.cmake" - "-DCMAKE_CXX_FLAGS=${_DUCKDB_EXTRA_CXX_FLAGS}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${_DUCKDB_EXTRA_CXX_FLAGS}" + "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" + "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}" + "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS}" -DENABLE_SANITIZER=${ADDRESS_SANITIZER} -DENABLE_UBSAN=${UB_SANITIZER} -DOVERRIDE_GIT_DESCRIBE=${_DUCKDB_GIT_DESCRIBE} diff --git a/storage/duckdb/ha_duckdb.cc b/storage/duckdb/ha_duckdb.cc index 96442413cb0..4242378ecc0 100644 --- a/storage/duckdb/ha_duckdb.cc +++ b/storage/duckdb/ha_duckdb.cc @@ -1165,6 +1165,15 @@ int ha_duckdb::external_lock(THD *thd, int lock_type) DBUG_ENTER("ha_duckdb::external_lock"); if (lock_type != F_UNLCK) { + /* DuckDB has no row locks: reject SELECT ... FOR UPDATE / LOCK IN SHARE MODE. */ + if (thd_sql_command(thd) == SQLCOM_SELECT && + (table->reginfo.lock_type == TL_READ_WITH_SHARED_LOCKS || + table->reginfo.lock_type >= TL_FIRST_WRITE)) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "locking SELECT"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } + /* DuckDB does not support XA transactions. Reject DML early. */ if (myduck::reject_xa_if_active(thd)) DBUG_RETURN(HA_ERR_WRONG_COMMAND); diff --git a/storage/duckdb/mysql-test/duckdb/r/duckdb_sql_syntax.result b/storage/duckdb/mysql-test/duckdb/r/duckdb_sql_syntax.result index a46a1766f74..3bda1811423 100644 --- a/storage/duckdb/mysql-test/duckdb/r/duckdb_sql_syntax.result +++ b/storage/duckdb/mysql-test/duckdb/r/duckdb_sql_syntax.result @@ -1,6 +1,7 @@ CREATE TABLE t1 (id INT PRIMARY KEY, col1 INT, col2 INT); SELECT id, col1, col2 FROM t1 GROUP BY id, col1, col2 WITH ROLLUP; id col1 col2 +NULL NULL NULL ALTER TABLE t1 ENGINE=DuckDB; SELECT id, col1, col2 FROM t1 GROUP BY id, col1, col2 WITH ROLLUP; id col1 col2 diff --git a/storage/duckdb/mysql-test/duckdb/r/select_for_update.result b/storage/duckdb/mysql-test/duckdb/r/select_for_update.result new file mode 100644 index 00000000000..dc41c28fb88 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/r/select_for_update.result @@ -0,0 +1,7 @@ +CREATE TABLE t (id INT PRIMARY KEY, v INT) ENGINE=DuckDB; +INSERT INTO t VALUES (1, 1),(2, 2); +SELECT v FROM t WHERE id=1 FOR UPDATE; +ERROR 42000: This version of MariaDB doesn't yet support 'locking SELECT' +SELECT v FROM t WHERE id=1 LOCK IN SHARE MODE; +ERROR 42000: This version of MariaDB doesn't yet support 'locking SELECT' +DROP TABLE t; diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test b/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test index 7ebcc4d25ed..a1a5cec984c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_default_debug.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE t(id INT PRIMARY KEY, a INT, b INT, c INT) ENGINE = DuckDB; INSERT INTO t VALUES(1, 1, 1, 1); diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test index 2e5076e8b19..0ae239d698c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo #################### --echo # TEST FOR INSTANT # --echo #################### diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test index dd862e488dc..2a88f36f9c8 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_column_copy.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo ################# --echo # TEST FOR COPY # --echo ################# diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test index 5d08f86baa4..8df5bdd951b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_duckdb_index.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo #################### --echo # TEST FOR INSTANT # --echo #################### diff --git a/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test b/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test index 6240c455bf7..7d047686055 100644 --- a/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test +++ b/storage/duckdb/mysql-test/duckdb/t/alter_engine_duckdb.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc SET GLOBAL duckdb_copy_ddl_in_batch=ON; --source ../include/alter_engine_duckdb.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test b/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test index 0ef4f782ea7..6a4de6d2282 100644 --- a/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test +++ b/storage/duckdb/mysql-test/duckdb/t/batch_disconnect.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; diff --git a/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test b/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test index 94bf421487c..60bc3e591d6 100644 --- a/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test +++ b/storage/duckdb/mysql-test/duckdb/t/batch_rollback.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log SET @saved_duckdb_dml_in_batch = @@GLOBAL.duckdb_dml_in_batch; diff --git a/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test b/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test index da058f62026..bc6757d2401 100644 --- a/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test +++ b/storage/duckdb/mysql-test/duckdb/t/bugfix_crash_after_commit_error.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --skip TODO create table t1(id int primary key) engine = duckdb; diff --git a/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test b/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test index 420fd8c26aa..e802f5c4c35 100644 --- a/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test +++ b/storage/duckdb/mysql-test/duckdb/t/bugfix_temp_and_system_database.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # DuckDB has default databases named 'temp' and 'system', if we try to create # schema named 'temp' or 'system', we get an error. diff --git a/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test b/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test index 54ea1077c1d..38ee163154c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test +++ b/storage/duckdb/mysql-test/duckdb/t/charset_and_collation.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # --echo # check collation config when execute --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_column.test b/storage/duckdb/mysql-test/duckdb/t/create_table_column.test index fadf29f6c0d..4d56714c484 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_column.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_column.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc # file: duckdb_create_table.test # # Test case for creating tables with various MySQL field types in DuckDB. diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test b/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test index ffdc20b657d..1b9ecfc4bcd 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_column_timestamp.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --source include/have_debug.inc --disable_query_log diff --git a/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test b/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test index 3875082690b..cccf1e06d35 100644 --- a/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test +++ b/storage/duckdb/mysql-test/duckdb/t/create_table_constraint.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --source include/have_debug.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test index 0806df98e96..facf513363f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_join.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # Cross-engine join: DuckDB + InnoDB via _mdb_scan replacement scan --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test index 5ba8e936e3e..c0ef2086739 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test index d572787dfa0..b19b13d33d1 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_dup.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # --echo # Cross-engine RYOW: the same external table referenced more than once. diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test index 84cc677bd9c..c7daf2b5e3c 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_ryow_limit.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test index 0ae97958ccd..d303b974cb0 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_union.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # Cross-engine UNION: DuckDB + InnoDB via select_handler unit pushdown --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test b/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test index 51102d6b29f..4736fb64a97 100644 --- a/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test +++ b/storage/duckdb/mysql-test/duckdb/t/cross_engine_where.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test b/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test index 3570bf4a2db..ec7bb24c7bd 100644 --- a/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test +++ b/storage/duckdb/mysql-test/duckdb/t/decimal_high_precision.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test b/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test index 1ec9348e5d0..2ea44599475 100644 --- a/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test +++ b/storage/duckdb/mysql-test/duckdb/t/decimal_precision_all_possibilities.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc set global duckdb_dml_in_batch = OFF; --source ../include/decimal_precision_all_possibilities.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test index d822cb86b29..0c49ad96e63 100644 --- a/storage/duckdb/mysql-test/duckdb/t/dml_delete.test +++ b/storage/duckdb/mysql-test/duckdb/t/dml_delete.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # DuckDB DELETE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/dml_update.test b/storage/duckdb/mysql-test/duckdb/t/dml_update.test index 681c86315a9..360dcfc1436 100644 --- a/storage/duckdb/mysql-test/duckdb/t/dml_update.test +++ b/storage/duckdb/mysql-test/duckdb/t/dml_update.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # DuckDB UPDATE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/drop_database.test b/storage/duckdb/mysql-test/duckdb/t/drop_database.test index 9f3ed4955b0..75877300a67 100644 --- a/storage/duckdb/mysql-test/duckdb/t/drop_database.test +++ b/storage/duckdb/mysql-test/duckdb/t/drop_database.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # DuckDB DROP DATABASE operations test --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test index a38ef13a7a9..e354231ed18 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_add_backticks.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc SET GLOBAL duckdb_require_primary_key=OFF; --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test index 8a97c539156..bc50c1bf8cd 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_agg_func.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE t_innodb ( id INT PRIMARY KEY, diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test index f0144c977ee..8f7ba7574ed 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_allow_encryption.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --disable_warnings DROP DATABASE IF EXISTS encryption_test; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test index 52a5cdb78d6..80ef3297927 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_alter_table_engine.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # --echo # Test DDL: alter table engine = innodb from duckdb tables diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test index d3d8a8a2b77..3a154d82679 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_appender_allocator_flush_threshold.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc SHOW GLOBAL VARIABLES LIKE "duckdb_appender_allocator_flush_threshold"; SELECT run_in_duckdb("FROM duckdb_settings() WHERE name = 'allocator_flush_threshold'"); diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test index 4648aa05efa..b0614b1992e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_auto_increment.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log CREATE DATABASE IF NOT EXISTS db_autoinc CHARACTER SET utf8mb4; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test index f7c8543a54d..22e82e4c3e5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_bit_string.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE t1 (id INT PRIMARY KEY, col1 VARCHAR(100)) ENGINE=DuckDB; CREATE TABLE t2 (id INT PRIMARY KEY, col1 BLOB) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test index 1d29484ce32..c575cbc9201 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_collate.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --disable_query_log --disable_warnings DROP TABLE IF EXISTS t1; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test index c714205c845..b0210d0b558 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_cte.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc CREATE DATABASE IF NOT EXISTS duckdb_cte; USE duckdb_cte; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test index 884e10f721f..0d50b5a1c9b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_db_table_strconvert.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc # Test for escape characters in the table name or database name. --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test index 1d192f2e214..b0146bf54cb 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_ddl_during_transaction.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo ########################################################################### --echo # Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test index c5a8ae25ec3..15cc00024f8 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_expr.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log CREATE DATABASE IF NOT EXISTS db_default_expr CHARACTER SET utf8mb4; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test index 170e8b809c6..8713fecb3f5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_default_value_injection.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # --echo # MDEV-40610: an unescaped column DEFAULT string must not inject DuckDB SQL diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test index 38acb142797..a005b802703 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_fix_sql.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE fake_innodb_table (id INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO fake_innodb_table VALUES (0); diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test index 7bd522a04f7..f607d971e4f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_json.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE t1_innodb (id INT PRIMARY KEY, col1 JSON) ENGINE=InnoDB; CREATE TABLE t1_duckdb (id INT PRIMARY KEY, col1 JSON) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test index a9344b6be5b..2915977740b 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_kill.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --source include/have_debug.inc --source include/have_debug_sync.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test index f8fad1d37b1..4e9627f958a 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_monitor.test @@ -1,7 +1,6 @@ --source ../include/have_duckdb.inc # MDEV-40449: MSan reports use-of-uninitialized-value in # duckdb::JemallocExtension::ThreadFlush (false positive from uninstrumented jemalloc). ---source include/not_msan.inc # # DuckDB monitoring status variables test. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test index 3d0fdaf1ac8..5f992cb23b5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_numeric_func.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # 1. CONV(), CRC32(), MOD, DIV, TRUNCATE() is not support yet. # 2. The RAND() function in duckdb and mysql uses different random number generation algorithms, so it cannot support the seed argument. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test index 4489a54fd33..0e34bd12c61 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_refuse_xa.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5)) ENGINE=duckdb; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test index f972509960a..cc8ffb15acb 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_require_primary_key.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test index 04db6e8cc53..e245e7c9575 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_set_operation.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc CREATE TABLE t1 (id INT PRIMARY KEY, col1 INT) ENGINE=DuckDB; INSERT INTO t1 VALUES (1, 1), (2, 1), (3, 2), (4, 2); CREATE TABLE t2 (id INT PRIMARY KEY, col1 INT) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test index 772254a650e..baeca66b9db 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_mode.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo --echo 1. ONLY_FULL_GROUP_BY diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test index aee922d26f1..c9db95ddd25 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_sql_syntax.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc # WITH ROLLUP CREATE TABLE t1 (id INT PRIMARY KEY, col1 INT, col2 INT); SELECT id, col1, col2 FROM t1 GROUP BY id, col1, col2 WITH ROLLUP; diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test index 59489ef568b..a14478edc65 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_string_func.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # 1. In Duckdb, some string function does not support blob fields as input # 2. Regex-related functions are basically incompatible, and regex functions will be processed uniformly in the future. diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test index 8a57fd81aa5..bc437121db8 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # Compared with MySQL, duckdb's date functions generally have the following incompatibilities: # 1. Conversion between integer type and date/timestamp/time type diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test index 61b390edfd7..0c21437ed4d 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_time_func_unsupported.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # Unsupported / incompatible date-time functions for DuckDB pushdown. # This is the companion of duckdb_time_func.test which keeps only the diff --git a/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test b/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test index c65fca8b8a5..161d588487f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test +++ b/storage/duckdb/mysql-test/duckdb/t/duckdb_uuid.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test b/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test index 06f0163991b..e21b2cb311e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test +++ b/storage/duckdb/mysql-test/duckdb/t/feature_duckdb_data_type.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # Feature ORC data type test # Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test b/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test index 14c5ce546ec..ffdfff0e07e 100644 --- a/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test +++ b/storage/duckdb/mysql-test/duckdb/t/ha_duckdb.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # Basic DuckDB handler test: DDL and DML operations --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test index 82a48ceac59..0c21a7d0c60 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40379.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc # # MDEV-40379: Assertion failure in CreateTableConvertor::translate # upon CREATE TEMPORARY TABLE with ENGINE=DuckDB diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test index 08624224661..5be863f0401 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40651.test @@ -1,6 +1,5 @@ --source ../include/have_duckdb.inc --source include/have_partition.inc ---source include/not_msan.inc # MDEV-40651: keep DuckDB DDL atomic and preserve implicit defaults. CREATE TABLE t (c1 INT KEY) ENGINE=DuckDB; diff --git a/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test b/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test index 1909a4c3c5f..0eb1da4bfa7 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test +++ b/storage/duckdb/mysql-test/duckdb/t/mdev_40957.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log --disable_warnings diff --git a/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test b/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test index b555c1ff9e4..d25a7879178 100644 --- a/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test +++ b/storage/duckdb/mysql-test/duckdb/t/mixed_dml.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_query_log --disable_warnings diff --git a/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test b/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test index d01a7e29a9e..bfc8cbf4750 100644 --- a/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test +++ b/storage/duckdb/mysql-test/duckdb/t/pushdown_rewrite.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --disable_warnings DROP DATABASE IF EXISTS pushdown_rewrite; diff --git a/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test b/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test index 2511b3b41c1..6ea744b0fd0 100644 --- a/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test +++ b/storage/duckdb/mysql-test/duckdb/t/rename_duckdb_table.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --echo # --echo # 1) Prepare diff --git a/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test b/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test index 1e7afdabdfa..268b9858205 100644 --- a/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test +++ b/storage/duckdb/mysql-test/duckdb/t/run_in_duckdb_access.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc SET @saved_allow_run_in_duckdb = @@GLOBAL.duckdb_allow_run_in_duckdb; # The suite enables duckdb_allow_run_in_duckdb globally (suite.opt), and MTR may diff --git a/storage/duckdb/mysql-test/duckdb/t/select_for_update.test b/storage/duckdb/mysql-test/duckdb/t/select_for_update.test new file mode 100644 index 00000000000..7d845e40e14 --- /dev/null +++ b/storage/duckdb/mysql-test/duckdb/t/select_for_update.test @@ -0,0 +1,11 @@ +--source ../include/have_duckdb.inc + +CREATE TABLE t (id INT PRIMARY KEY, v INT) ENGINE=DuckDB; +INSERT INTO t VALUES (1, 1),(2, 2); + +--error ER_NOT_SUPPORTED_YET +SELECT v FROM t WHERE id=1 FOR UPDATE; + +--error ER_NOT_SUPPORTED_YET +SELECT v FROM t WHERE id=1 LOCK IN SHARE MODE; +DROP TABLE t; diff --git a/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test b/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test index 2748253d487..f23b82603fd 100644 --- a/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test +++ b/storage/duckdb/mysql-test/duckdb/t/space_reclaim.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc --source include/have_sequence.inc --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/streaming_select.test b/storage/duckdb/mysql-test/duckdb/t/streaming_select.test index 4999fa997d5..bf4e0d8fffc 100644 --- a/storage/duckdb/mysql-test/duckdb/t/streaming_select.test +++ b/storage/duckdb/mysql-test/duckdb/t/streaming_select.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --source include/have_sequence.inc --disable_warnings diff --git a/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test b/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test index ad37d55159b..97069c1b09f 100644 --- a/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test +++ b/storage/duckdb/mysql-test/duckdb/t/supported_copy_ddl.test @@ -1,5 +1,4 @@ --source ../include/have_duckdb.inc ---source include/not_msan.inc # Test for DDL which are supported by DuckDB using COPY algorithm --echo # --echo # RENAME TABLE WITH DIFFERENT DATABASES diff --git a/storage/duckdb/mysql-test/duckdb/t/system_timezone.test b/storage/duckdb/mysql-test/duckdb/t/system_timezone.test index 1d034ce0d6e..59aa9d106a5 100644 --- a/storage/duckdb/mysql-test/duckdb/t/system_timezone.test +++ b/storage/duckdb/mysql-test/duckdb/t/system_timezone.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --source ../include/have_mysqld_safe.inc # 1) Set valiables to be used in parameters of mysqld_safe. diff --git a/storage/duckdb/mysql-test/duckdb/t/transaction.test b/storage/duckdb/mysql-test/duckdb/t/transaction.test index 84688d7a379..7f080ce7456 100644 --- a/storage/duckdb/mysql-test/duckdb/t/transaction.test +++ b/storage/duckdb/mysql-test/duckdb/t/transaction.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # DuckDB transaction tests --echo # diff --git a/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test b/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test index d3f9ee37e4d..3c2108ff356 100644 --- a/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test +++ b/storage/duckdb/mysql-test/duckdb/t/truncate_and_maintenance_duckdb_table.test @@ -1,4 +1,3 @@ ---source include/not_msan.inc --echo # --echo # 1) PREPARE --echo # diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 678de2eb553..cf539e9b6c6 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1818,12 +1818,16 @@ ATTRIBUTE_COLD buf_pool_t::shrink_status buf_pool_t::shrink(size_t size) { withdraw_done: first_to_withdraw= nullptr; + uint64_t base{0}; while (buf_page_t *b= UT_LIST_GET_FIRST(withdrawn)) { UT_LIST_REMOVE(withdrawn, b); + base= std::max(base, b->modify_clock_low | + uint64_t{b->modify_clock_high} << 32); /* satisfy the check in lazy_allocate() */ ut_d(memset((void*) b, 0, sizeof(buf_block_t))); } + withdrawn_modify_clock= std::max(withdrawn_modify_clock, base); return SHRINK_DONE; } @@ -2128,6 +2132,7 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept const size_t ssize= srv_page_size_shift - UNIV_PAGE_SIZE_SHIFT_MIN; const size_t pages= pages_in_extent[ssize]; const size_t first_extent= n_blocks / pages; + const uint64_t modify_clock= withdrawn_modify_clock + 1; char *extent= memory + first_extent * innodb_buffer_pool_extent_size; @@ -2149,6 +2154,8 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept { block->page.frame= frame; block->page.lock.init(); + block->page.modify_clock_low= uint32_t(modify_clock); + block->page.modify_clock_high= uint16_t(modify_clock >> 32); UT_LIST_ADD_LAST(free, &block->page); ut_d(block->page.in_free_list= true); } @@ -2172,6 +2179,8 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept { block->page.frame= frame; block->page.lock.init(); + block->page.modify_clock_low= uint32_t(modify_clock); + block->page.modify_clock_high= uint16_t(modify_clock >> 32); UT_LIST_ADD_LAST(free, &block->page); ut_d(block->page.in_free_list= true); } diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 1e07ee0ecae..dd80aee7f65 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -1610,6 +1610,13 @@ public: /** number of index page splits */ Atomic_counter pages_split; + /** the maximum modify_clock of any block that shrink() withdrew; + resize() initializes the modify_clock of the blocks that it adds to + one more than this, so that the modify_clock at a block descriptor + address never repeats an earlier value and a stale + btr_pcur_t::restore_position() cannot succeed optimistically; + protected by mutex */ + uint64_t withdrawn_modify_clock; /** @name Page flushing algorithm fields */ /* @{ */ diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index 3251b5bc1d2..d2d6af99cd1 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -3518,6 +3518,13 @@ Row_sel_get_clust_rec_for_mysql::operator()( LOCK_REC_NOT_GAP, thr); + if (err == DB_LOCK_WAIT && prebuilt->skip_locked) { + /* Cancel the lock wait, unless the lock was granted + meanwhile; then continue with a normal locking read + of clust_rec. */ + err = lock_trx_handle_wait(trx); + } + switch (err) { case DB_SUCCESS: case DB_SUCCESS_LOCKED_REC: @@ -5554,9 +5561,6 @@ requires_clust_rec: &offsets, &heap, need_vrow ? &vrow : NULL, &mtr); - if (err == DB_LOCK_WAIT && prebuilt->skip_locked) { - err = lock_trx_handle_wait(trx); - } switch (err) { case DB_SUCCESS: if (clust_rec == NULL) { diff --git a/storage/perfschema/pfs_program.cc b/storage/perfschema/pfs_program.cc index f08aaf8da93..b0eee7694ba 100644 --- a/storage/perfschema/pfs_program.cc +++ b/storage/perfschema/pfs_program.cc @@ -236,6 +236,8 @@ search: pfs->m_schema_name_length= schema_name_length; pfs->m_enabled= is_enabled; pfs->m_timed= is_timed; + pfs->m_stmt_stat.reset(); + pfs->m_sp_stat.reset(); /* Insert this record. */ pfs->m_lock.dirty_to_allocated(& dirty_state); diff --git a/storage/rocksdb/CMakeLists.txt b/storage/rocksdb/CMakeLists.txt index ddca7d156f3..30a8e82739a 100644 --- a/storage/rocksdb/CMakeLists.txt +++ b/storage/rocksdb/CMakeLists.txt @@ -11,6 +11,7 @@ at maximising storage efficiency while maintaining InnoDB-like performance." PAR MY_CHECK_AND_SET_COMPILER_FLAG(-Wno-range-loop-construct) MY_CHECK_AND_SET_COMPILER_FLAG(-Wno-invalid-offsetof) +MY_CHECK_AND_SET_COMPILER_FLAG(-Wno-redundant-move) MY_CHECK_AND_SET_COMPILER_FLAG(-Wno-effc++ DEBUG RELWITHDEBINFO) MACRO(SKIP_ROCKSDB_PLUGIN msg) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 9b90759427b..3aea42cc6e4 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -20576,6 +20576,22 @@ typedef struct { } v2_proxy_header; #ifndef EMBEDDED_LIBRARY +/* + Zero the header, and set the part that is common to all PROXY v2 headers: + the signature, version 2 with the command (0x1 PROXY, 0x0 LOCAL), the + address family and protocol, and the length of the address block. +*/ +static void init_v2_header(v2_proxy_header *header, int command, int fam, + size_t len) +{ + memset(header, 0, sizeof(*header)); + memcpy(header->sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", + sizeof(header->sig)); + header->ver_cmd= (0x2 << 4) | command; + header->fam= fam; + header->len= htons(len); +} + static void test_proxy_header_tcp(const char *ipaddr, int port) { int rc; @@ -20584,26 +20600,22 @@ static void test_proxy_header_tcp(const char *ipaddr, int port) char query[256]; char text_header[256], bad_text_header[256]; char addr_bin[16]; - v2_proxy_header v2_header, bad_v2_header; - void *header_data[4]; - size_t header_lengths[4]; + v2_proxy_header v2_header, bad_v2_header, short_v2_header; + void *header_data[5]; + size_t header_lengths[5]; size_t i; // normalize IPv4-mapped IPv6 addresses, e.g ::ffff:127.0.0.2 to 127.0.0.2 const char *normalized_addr= strncmp(ipaddr, "::ffff:", 7)?ipaddr : ipaddr + 7; myheader("test_proxy_header_tcp"); - memset(&v2_header, 0, sizeof(v2_header)); snprintf(text_header, sizeof(text_header), "PROXY %s %s %s %d 3306\r\n",family == AF_INET?"TCP4":"TCP6", ipaddr, ipaddr, port); - + inet_pton(family,ipaddr,addr_bin); - memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - v2_header.ver_cmd = (0x2 << 4) | 0x1; /* Version (0x2) , Command = PROXY (0x1) */ if(family == AF_INET) { - v2_header.fam= 0x11; - v2_header.len= htons(12); + init_v2_header(&v2_header, 0x1, 0x11, sizeof(v2_header.addr.ip4)); v2_header.addr.ip4.src_port= htons(port); v2_header.addr.ip4.dst_port= htons(3306); memcpy(&v2_header.addr.ip4.src_addr,addr_bin, sizeof (v2_header.addr.ip4.src_addr)); @@ -20611,8 +20623,7 @@ static void test_proxy_header_tcp(const char *ipaddr, int port) } else { - v2_header.fam= 0x21; - v2_header.len= htons(36); + init_v2_header(&v2_header, 0x1, 0x21, sizeof(v2_header.addr.ip6)); v2_header.addr.ip6.src_port= htons(port); v2_header.addr.ip6.dst_port= htons(3306); memcpy(v2_header.addr.ip6.src_addr,addr_bin, sizeof (v2_header.addr.ip6.src_addr)); @@ -20627,11 +20638,14 @@ static void test_proxy_header_tcp(const char *ipaddr, int port) header_data[1]= &v2_header; header_data[2]= bad_text_header; header_data[3]= &bad_v2_header; + header_data[4]= &short_v2_header; header_lengths[0]= strlen(text_header); header_lengths[1]= family == AF_INET ? 28 : 52; header_lengths[2]= sizeof(text_header)-4; header_lengths[3]= header_lengths[1]; + /* MDEV-41508: address block ends before the source port's low byte */ + header_lengths[4]= family == AF_INET ? 25 : 49; memset(bad_text_header, ' ', sizeof(bad_text_header)); memcpy(bad_text_header, text_header, header_lengths[1]-6); @@ -20639,6 +20653,9 @@ static void test_proxy_header_tcp(const char *ipaddr, int port) bad_v2_header= v2_header; bad_v2_header.len= 0; + short_v2_header= v2_header; + short_v2_header.len= htons(header_lengths[4] - 16); + for (i = 0; i < 2; i++) { MYSQL *m; @@ -20679,8 +20696,7 @@ static void test_proxy_header_tcp(const char *ipaddr, int port) DIE(0); printf("pass %zu error %i - %s\n", i, mysql_errno(m), mysql_error(m)); - DIE_IF(i == 2 && mysql_errno(m) != ER_UNKNOWN_ERROR); - DIE_IF(i == 3 && mysql_errno(m) != ER_UNKNOWN_ERROR); + DIE_IF(mysql_errno(m) != ER_UNKNOWN_ERROR); mysql_close(m); } snprintf(query, sizeof(query), "DROP USER 'u'@'%s'",normalized_addr); @@ -20701,11 +20717,7 @@ static void test_proxy_header_localhost() int rc; myheader("test_proxy_header_localhost"); - memset(&v2_header, 0, sizeof(v2_header)); - memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - v2_header.ver_cmd = (0x2 << 4) | 0x1; /* Version (0x2) , Command = PROXY (0x1) */ - v2_header.fam= 0x31; - v2_header.len= htons(216); + init_v2_header(&v2_header, 0x1, 0x31, sizeof(v2_header.addr.unx)); strcpy(v2_header.addr.unx.src_addr,"/tmp/mysql.sock"); strcpy(v2_header.addr.unx.dst_addr,"/tmp/mysql.sock"); rc = mysql_query(mysql, "CREATE USER 'u'@'localhost' IDENTIFIED BY 'password'"); @@ -20738,10 +20750,8 @@ static void test_proxy_header_ignore() DIE_UNLESS(mysql_real_connect(m, opt_host, "root", "", NULL, opt_port, opt_unix_socket, 0) == m); mysql_close(m); - memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - v2_header.ver_cmd = (0x2 << 4) | 0x0; /* Version (0x2) , Command = LOCAL (0x0) */ - v2_header.fam= 0x0; /* AF_UNSPEC*/ - v2_header.len= htons(0); + /* Command LOCAL (0x0), AF_UNSPEC, no address block */ + init_v2_header(&v2_header, 0x0, 0x0, 0); m = mysql_client_init(NULL); mysql_optionsv(m, MARIADB_OPT_PROXY_HEADER, &v2_header,16); DIE_UNLESS(mysql_real_connect(m, opt_host, "root", "", NULL, opt_port, opt_unix_socket, 0) == m); @@ -20760,6 +20770,113 @@ static void test_proxy_header_ignore() myquery(rc); } +/* Open a raw TCP connection to opt_host:opt_port */ +static my_socket connect_tcp(void) +{ + struct addrinfo hints, *ai= NULL, *p; + char portbuf[MY_INT32_NUM_DECIMAL_DIGITS]; + int connected= 0; + my_socket s= INVALID_SOCKET; + + snprintf(portbuf, sizeof(portbuf), "%u", opt_port); + memset(&hints, 0, sizeof(hints)); + hints.ai_family= AF_UNSPEC; + hints.ai_socktype= SOCK_STREAM; + DIE_UNLESS(getaddrinfo(opt_host ? opt_host : "localhost", portbuf, + &hints, &ai) == 0); + + for (p= ai; p; p= p->ai_next) + { + s= socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (s == INVALID_SOCKET) + continue; + if (connect(s, p->ai_addr, (int) p->ai_addrlen) == 0) + { + connected= 1; + break; + } + closesocket(s); + s= INVALID_SOCKET; + } + freeaddrinfo(ai); + DIE_UNLESS(connected); + return s; +} + + +/* + MDEV-41508 PROXY v2 header, shorter than it declares, must be rejected. + Raw socket is used, as the client library would follow the header with + its login packet, which would be taken for the missing header bytes. +*/ +static void test_proxy_header_short() +{ + v2_proxy_header v2_header; + /* 1024 is enough for the greeting packet plus the error packet */ + char buf[1024]; + /* Error packet payload: 0xff, error code, '#' and SQLSTATE, then message */ + const size_t error_payload_header_len= 1 + 2 + 1 + SQLSTATE_LENGTH; + size_t total= 0, error_packet_pos, error_packet_len, err_msg_len; + const uchar *error_packet, *error_payload; + const char *err_msg; + static const LEX_CSTRING expected_msg= + { STRING_WITH_LEN("Failed to parse proxy header") }; + uint err_code; + my_socket s; + + myheader("test_proxy_header_short"); + + /* Command PROXY, TCP over IPv4, declares the whole IPv4 address block */ + init_v2_header(&v2_header, 0x1, 0x11, sizeof(v2_header.addr.ip4)); + + s= connect_tcp(); + /* + Send an incomplete PROXY v2 header, 25 bytes: the signature, 2 IPv4 + addresses, and the first byte of the source port (16 + 4 + 4 + 1). + The complete header is 28 bytes. + */ + DIE_UNLESS(send(s, (const char *) &v2_header, 25, 0) == 25); + + /* + The server sends its greeting first. Then it rejects the header with an + error packet and closes the connection, so read until EOF. + */ + for (;;) + { + int n= recv(s, buf + total, (int) (sizeof(buf) - total), 0); + if (n <= 0) + break; + total+= n; + } + closesocket(s); + + /* + Every packet is: 3 bytes of payload length, 1 byte of sequence number, + and the payload. Skip the greeting packet. + */ + DIE_UNLESS(total >= NET_HEADER_SIZE); + error_packet_pos= NET_HEADER_SIZE + uint3korr(buf); + + /* The error packet, after its header: 0xff, error code, '#', SQLSTATE */ + DIE_UNLESS(total >= error_packet_pos + NET_HEADER_SIZE); + error_packet= (const uchar *) buf + error_packet_pos; + error_packet_len= uint3korr(error_packet); + error_payload= error_packet + NET_HEADER_SIZE; + DIE_UNLESS(error_packet_len >= error_payload_header_len); + DIE_UNLESS(total >= error_packet_pos + NET_HEADER_SIZE + error_packet_len); + DIE_UNLESS(error_payload[0] == 0xff); + DIE_UNLESS(error_payload[3] == '#'); + err_code= uint2korr(error_payload + 1); + err_msg= (const char *) error_payload + error_payload_header_len; + err_msg_len= error_packet_len - error_payload_header_len; + printf("error %u: %.*s\n", err_code, (int) err_msg_len, err_msg); + + DIE_UNLESS(err_code == ER_UNKNOWN_ERROR); + DIE_UNLESS(err_msg_len == expected_msg.length); + DIE_UNLESS(!memcmp(err_msg, expected_msg.str, expected_msg.length)); +} + + static void test_proxy_header_limits() { MYSQL *m; @@ -20823,11 +20940,7 @@ static void test_proxy_header_dbug_remote_connection() myquery(rc); /* Build a v2 proxy header announcing proxy_ipv6 as the client address. */ - memset(&v2_header, 0, sizeof(v2_header)); - memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - v2_header.ver_cmd = (0x2 << 4) | 0x1; /* PROXY command */ - v2_header.fam = 0x21; /* TCPv6 */ - v2_header.len = htons(36); + init_v2_header(&v2_header, 0x1, 0x21, sizeof(v2_header.addr.ip6)); inet_pton(AF_INET6, proxy_ipv6, addr_bin); memcpy(v2_header.addr.ip6.src_addr, addr_bin, 16); memcpy(v2_header.addr.ip6.dst_addr, addr_bin, 16); @@ -20872,33 +20985,8 @@ static void test_proxy_header_dbug_remote_connection() */ static void proxy_send_handshake_error(const char *client_ip) { - struct addrinfo hints, *ai= NULL, *p; - char portbuf[20], header[128]; - int connected= 0; - my_socket s= INVALID_SOCKET; - - snprintf(portbuf, sizeof(portbuf), "%u", opt_port); - memset(&hints, 0, sizeof(hints)); - hints.ai_family= AF_UNSPEC; - hints.ai_socktype= SOCK_STREAM; - DIE_UNLESS(getaddrinfo(opt_host ? opt_host : "localhost", portbuf, - &hints, &ai) == 0); - - for (p= ai; p; p= p->ai_next) - { - s= socket(p->ai_family, p->ai_socktype, p->ai_protocol); - if (s == INVALID_SOCKET) - continue; - if (connect(s, p->ai_addr, (int) p->ai_addrlen) == 0) - { - connected= 1; - break; - } - closesocket(s); - s= INVALID_SOCKET; - } - freeaddrinfo(ai); - DIE_UNLESS(connected); + char header[128]; + my_socket s= connect_tcp(); if (client_ip) { @@ -21198,6 +21286,7 @@ static void test_proxy_header() test_proxy_header_localhost(); test_proxy_header_ignore(); test_proxy_header_limits(); + test_proxy_header_short(); test_proxy_header_dbug_remote_connection(); test_proxy_header_connect_errors_reset(); test_proxy_header_proxy_host_connect_errors_reset();